diff options
-rw-r--r-- | plugins/check_ntp.c | 269 |
1 files changed, 247 insertions, 22 deletions
diff --git a/plugins/check_ntp.c b/plugins/check_ntp.c index 5037786a..2954aa88 100644 --- a/plugins/check_ntp.c +++ b/plugins/check_ntp.c | |||
@@ -43,6 +43,12 @@ int process_arguments (int, char **); | |||
43 | void print_help (void); | 43 | void print_help (void); |
44 | void print_usage (void); | 44 | void print_usage (void); |
45 | 45 | ||
46 | /* number of times to perform each request to get a good average. */ | ||
47 | #define AVG_NUM 4 | ||
48 | |||
49 | /* max size of control message data */ | ||
50 | #define MAX_CM_SIZE 468 | ||
51 | |||
46 | /* this structure holds everything in an ntp request/response as per rfc1305 */ | 52 | /* this structure holds everything in an ntp request/response as per rfc1305 */ |
47 | typedef struct { | 53 | typedef struct { |
48 | uint8_t flags; /* byte with leapindicator,vers,mode. see macros */ | 54 | uint8_t flags; /* byte with leapindicator,vers,mode. see macros */ |
@@ -58,6 +64,25 @@ typedef struct { | |||
58 | uint64_t txts; /* time at which request departed server */ | 64 | uint64_t txts; /* time at which request departed server */ |
59 | } ntp_message; | 65 | } ntp_message; |
60 | 66 | ||
67 | /* this structure holds everything in an ntp control message as per rfc1305 */ | ||
68 | typedef struct { | ||
69 | uint8_t flags; /* byte with leapindicator,vers,mode. see macros */ | ||
70 | uint8_t op; /* R,E,M bits and Opcode */ | ||
71 | uint16_t seq; /* Packet sequence */ | ||
72 | uint16_t status; /* Clock status */ | ||
73 | uint16_t assoc; /* Association */ | ||
74 | uint16_t offset; /* Similar to TCP sequence # */ | ||
75 | uint16_t count; /* # bytes of data */ | ||
76 | char data[MAX_CM_SIZE]; /* ASCII data of the request */ | ||
77 | /* NB: not necessarily NULL terminated! */ | ||
78 | } ntp_control_message; | ||
79 | |||
80 | /* this is an association/status-word pair found in control packet reponses */ | ||
81 | typedef struct { | ||
82 | uint16_t assoc; | ||
83 | uint16_t status; | ||
84 | } ntp_assoc_status_pair; | ||
85 | |||
61 | /* bits 1,2 are the leap indicator */ | 86 | /* bits 1,2 are the leap indicator */ |
62 | #define LI_MASK 0xc0 | 87 | #define LI_MASK 0xc0 |
63 | #define LI(x) ((x&LI_MASK)>>6) | 88 | #define LI(x) ((x&LI_MASK)>>6) |
@@ -71,12 +96,28 @@ typedef struct { | |||
71 | #define VN_MASK 0x38 | 96 | #define VN_MASK 0x38 |
72 | #define VN(x) ((x&VN_MASK)>>3) | 97 | #define VN(x) ((x&VN_MASK)>>3) |
73 | #define VN_SET(x,y) do{ x |= ((y<<3)&VN_MASK); }while(0) | 98 | #define VN_SET(x,y) do{ x |= ((y<<3)&VN_MASK); }while(0) |
99 | #define VN_RESERVED 0x02 | ||
74 | /* bits 6,7,8 are the ntp mode */ | 100 | /* bits 6,7,8 are the ntp mode */ |
75 | #define MODE_MASK 0x07 | 101 | #define MODE_MASK 0x07 |
76 | #define MODE(x) (x&MODE_MASK) | 102 | #define MODE(x) (x&MODE_MASK) |
77 | #define MODE_SET(x,y) do{ x |= (y&MODE_MASK); }while(0) | 103 | #define MODE_SET(x,y) do{ x |= (y&MODE_MASK); }while(0) |
78 | /* here are some values */ | 104 | /* here are some values */ |
79 | #define MODE_CLIENT 0x03 | 105 | #define MODE_CLIENT 0x03 |
106 | #define MODE_CONTROLMSG 0x06 | ||
107 | /* In control message, bits 8-10 are R,E,M bits */ | ||
108 | #define REM_MASK 0xe0 | ||
109 | #define REM_RESP 0x80 | ||
110 | #define REM_ERROR 0x40 | ||
111 | #define REM_MORE 0x20 | ||
112 | /* In control message, bits 11 - 15 are opcode */ | ||
113 | #define OP_MASK 0x1f | ||
114 | #define OP_SET(x,y) do{ x |= (y&OP_MASK); }while(0) | ||
115 | #define OP_READSTAT 0x01 | ||
116 | #define OP_READVAR 0x02 | ||
117 | /* In peer status bytes, bytes 6,7,8 determine clock selection status */ | ||
118 | #define PEER_SEL(x) (x&0x07) | ||
119 | #define PEER_INCLUDED 0x04 | ||
120 | #define PEER_SYNCSOURCE 0x06 | ||
80 | 121 | ||
81 | /** | 122 | /** |
82 | ** a note about the 32-bit "fixed point" numbers: | 123 | ** a note about the 32-bit "fixed point" numbers: |
@@ -116,7 +157,7 @@ typedef struct { | |||
116 | do{ if(!n) t.tv_sec = t.tv_usec = 0; \ | 157 | do{ if(!n) t.tv_sec = t.tv_usec = 0; \ |
117 | else { \ | 158 | else { \ |
118 | t.tv_sec=ntohl(L32(n))-EPOCHDIFF; \ | 159 | t.tv_sec=ntohl(L32(n))-EPOCHDIFF; \ |
119 | t.tv_usec=(int)(0.5+(double)(ntohl(R32(n))/4294.967296)); \ | 160 | t.tv_usec=(int)(0.5+(double)(ntohl(R32(n))/4294.967296)); \ |
120 | } \ | 161 | } \ |
121 | }while(0) | 162 | }while(0) |
122 | 163 | ||
@@ -129,6 +170,17 @@ typedef struct { | |||
129 | } \ | 170 | } \ |
130 | } while(0) | 171 | } while(0) |
131 | 172 | ||
173 | /* NTP control message header is 12 bytes, plus any data in the data | ||
174 | * field, plus null padding to the nearest 32-bit boundary per rfc. | ||
175 | */ | ||
176 | #define SIZEOF_NTPCM(m) (12+ntohs(m.count)+((m.count)?4-(ntohs(m.count)%4):0)) | ||
177 | |||
178 | /* finally, a little helper or two for debugging: */ | ||
179 | #define DBG(x) do{if(verbose>1){ x; }}while(0); | ||
180 | #define PRINTSOCKADDR(x) \ | ||
181 | do{ \ | ||
182 | printf("%u.%u.%u.%u", (x>>24)&0xff, (x>>16)&0xff, (x>>8)&0xff, x&0xff);\ | ||
183 | }while(0); | ||
132 | 184 | ||
133 | /* calculate the offset of the local clock */ | 185 | /* calculate the offset of the local clock */ |
134 | static inline double calc_offset(const ntp_message *m, const struct timeval *t){ | 186 | static inline double calc_offset(const ntp_message *m, const struct timeval *t){ |
@@ -142,7 +194,7 @@ static inline double calc_offset(const ntp_message *m, const struct timeval *t){ | |||
142 | } | 194 | } |
143 | 195 | ||
144 | /* print out a ntp packet in human readable/debuggable format */ | 196 | /* print out a ntp packet in human readable/debuggable format */ |
145 | void print_packet(const ntp_message *p){ | 197 | void print_ntp_message(const ntp_message *p){ |
146 | struct timeval ref, orig, rx, tx; | 198 | struct timeval ref, orig, rx, tx; |
147 | 199 | ||
148 | NTP64toTV(p->refts,ref); | 200 | NTP64toTV(p->refts,ref); |
@@ -167,6 +219,42 @@ void print_packet(const ntp_message *p){ | |||
167 | printf("\ttxts = %-.16g\n", NTP64asDOUBLE(p->txts)); | 219 | printf("\ttxts = %-.16g\n", NTP64asDOUBLE(p->txts)); |
168 | } | 220 | } |
169 | 221 | ||
222 | void print_ntp_control_message(const ntp_control_message *p){ | ||
223 | int i=0, numpeers=0; | ||
224 | const ntp_assoc_status_pair *peer=NULL; | ||
225 | |||
226 | printf("control packet contents:\n"); | ||
227 | printf("\tflags: 0x%.2x , 0x%.2x\n", p->flags, p->op); | ||
228 | printf("\t li=%d (0x%.2x)\n", LI(p->flags), p->flags&LI_MASK); | ||
229 | printf("\t vn=%d (0x%.2x)\n", VN(p->flags), p->flags&VN_MASK); | ||
230 | printf("\t mode=%d (0x%.2x)\n", MODE(p->flags), p->flags&MODE_MASK); | ||
231 | printf("\t response=%d (0x%.2x)\n", (p->op&REM_RESP)>0, p->op&REM_RESP); | ||
232 | printf("\t more=%d (0x%.2x)\n", (p->op&REM_MORE)>0, p->op&REM_MORE); | ||
233 | printf("\t error=%d (0x%.2x)\n", (p->op&REM_ERROR)>0, p->op&REM_ERROR); | ||
234 | printf("\t op=%d (0x%.2x)\n", p->op&OP_MASK, p->op&OP_MASK); | ||
235 | printf("\tsequence: %d (0x%.2x)\n", ntohs(p->seq), ntohs(p->seq)); | ||
236 | printf("\tstatus: %d (0x%.2x)\n", ntohs(p->status), ntohs(p->status)); | ||
237 | printf("\tassoc: %d (0x%.2x)\n", ntohs(p->assoc), ntohs(p->assoc)); | ||
238 | printf("\toffset: %d (0x%.2x)\n", ntohs(p->offset), ntohs(p->offset)); | ||
239 | printf("\tcount: %d (0x%.2x)\n", ntohs(p->count), ntohs(p->count)); | ||
240 | numpeers=ntohs(p->count)/(sizeof(ntp_assoc_status_pair)); | ||
241 | if(p->op&REM_RESP && p->op&OP_READSTAT){ | ||
242 | peer=(ntp_assoc_status_pair*)p->data; | ||
243 | for(i=0;i<numpeers;i++){ | ||
244 | printf("\tpeer id %.2x status %.2x", | ||
245 | ntohs(peer[i].assoc), ntohs(peer[i].status)); | ||
246 | if (PEER_SEL(peer[i].status) >= PEER_INCLUDED){ | ||
247 | if(PEER_SEL(peer[i].status) >= PEER_SYNCSOURCE){ | ||
248 | printf(" <-- current sync source"); | ||
249 | } else { | ||
250 | printf(" <-- current sync candidate"); | ||
251 | } | ||
252 | } | ||
253 | printf("\n"); | ||
254 | } | ||
255 | } | ||
256 | } | ||
257 | |||
170 | void setup_request(ntp_message *p){ | 258 | void setup_request(ntp_message *p){ |
171 | struct timeval t; | 259 | struct timeval t; |
172 | 260 | ||
@@ -189,7 +277,8 @@ double offset_request(const char *host){ | |||
189 | double next_offset=0., avg_offset=0.; | 277 | double next_offset=0., avg_offset=0.; |
190 | struct timeval recv_time; | 278 | struct timeval recv_time; |
191 | 279 | ||
192 | for(i=0; i<4; i++){ | 280 | for(i=0; i<AVG_NUM; i++){ |
281 | if(verbose) printf("offset run: %d/%d\n", i+1, AVG_NUM); | ||
193 | setup_request(&req); | 282 | setup_request(&req); |
194 | my_udp_connect(server_address, 123, &conn); | 283 | my_udp_connect(server_address, 123, &conn); |
195 | write(conn, &req, sizeof(ntp_message)); | 284 | write(conn, &req, sizeof(ntp_message)); |
@@ -201,12 +290,134 @@ double offset_request(const char *host){ | |||
201 | if(verbose) printf("offset: %g\n", next_offset); | 290 | if(verbose) printf("offset: %g\n", next_offset); |
202 | avg_offset+=next_offset; | 291 | avg_offset+=next_offset; |
203 | } | 292 | } |
204 | return avg_offset/4.; | 293 | avg_offset/=AVG_NUM; |
294 | if(verbose) printf("average offset: %g\n", avg_offset); | ||
295 | return avg_offset; | ||
296 | } | ||
297 | |||
298 | void | ||
299 | setup_control_request(ntp_control_message *p, uint8_t opcode, uint16_t seq){ | ||
300 | memset(p, 0, sizeof(ntp_control_message)); | ||
301 | LI_SET(p->flags, LI_NOWARNING); | ||
302 | VN_SET(p->flags, VN_RESERVED); | ||
303 | MODE_SET(p->flags, MODE_CONTROLMSG); | ||
304 | OP_SET(p->op, opcode); | ||
305 | p->seq = htons(seq); | ||
306 | /* Remaining fields are zero for requests */ | ||
205 | } | 307 | } |
206 | 308 | ||
207 | /* not yet implemented yet */ | 309 | /* XXX handle responses with the error bit set */ |
208 | double jitter_request(const char *host){ | 310 | double jitter_request(const char *host){ |
209 | return 0.; | 311 | int conn=-1, i, npeers=0, num_candidates=0, syncsource_found=0; |
312 | int run=0, min_peer_sel=PEER_INCLUDED, num_selected=0, num_valid=0; | ||
313 | ntp_assoc_status_pair *peers; | ||
314 | ntp_control_message req; | ||
315 | double rval = 0.0, jitter = -1.0; | ||
316 | char *startofvalue=NULL, *nptr=NULL; | ||
317 | |||
318 | /* Long-winded explanation: | ||
319 | * Getting the jitter requires a number of steps: | ||
320 | * 1) Send a READSTAT request. | ||
321 | * 2) Interpret the READSTAT reply | ||
322 | * a) The data section contains a list of peer identifiers (16 bits) | ||
323 | * and associated status words (16 bits) | ||
324 | * b) We want the value of 0x06 in the SEL (peer selection) value, | ||
325 | * which means "current synchronizatin source". If that's missing, | ||
326 | * we take anything better than 0x04 (see the rfc for details) but | ||
327 | * set a minimum of warning. | ||
328 | * 3) Send a READVAR request for information on each peer identified | ||
329 | * in 2b greater than the minimum selection value. | ||
330 | * 4) Extract the jitter value from the data[] (it's ASCII) | ||
331 | */ | ||
332 | my_udp_connect(server_address, 123, &conn); | ||
333 | setup_control_request(&req, OP_READSTAT, 1); | ||
334 | |||
335 | DBG(printf("sending READSTAT request")); | ||
336 | write(conn, &req, SIZEOF_NTPCM(req)); | ||
337 | DBG(print_ntp_control_message(&req)); | ||
338 | /* Attempt to read the largest size packet possible | ||
339 | * Is it possible for an NTP server to have more than 117 synchronization | ||
340 | * sources? If so, we will receive a second datagram with additional | ||
341 | * peers listed, since 117 is the maximum number that can fit in a | ||
342 | * single NTP control datagram. This code doesn't handle that case */ | ||
343 | /* XXX check the REM_MORE bit */ | ||
344 | req.count=htons(MAX_CM_SIZE); | ||
345 | DBG(printf("recieving READSTAT response")) | ||
346 | read(conn, &req, SIZEOF_NTPCM(req)); | ||
347 | DBG(print_ntp_control_message(&req)); | ||
348 | /* Each peer identifier is 4 bytes in the data section, which | ||
349 | * we represent as a ntp_assoc_status_pair datatype. | ||
350 | */ | ||
351 | npeers=ntohs(req.count)/sizeof(ntp_assoc_status_pair); | ||
352 | peers=(ntp_assoc_status_pair*)malloc(sizeof(ntp_assoc_status_pair)*npeers); | ||
353 | memcpy((void*)peers, (void*)req.data, sizeof(ntp_assoc_status_pair)*npeers); | ||
354 | /* first, let's find out if we have a sync source, or if there are | ||
355 | * at least some candidates. in the case of the latter we'll issue | ||
356 | * a warning but go ahead with the check on them. */ | ||
357 | for (i = 0; i < npeers; i++){ | ||
358 | if (PEER_SEL(peers[i].status) >= PEER_INCLUDED){ | ||
359 | num_candidates++; | ||
360 | if(PEER_SEL(peers[i].status) >= PEER_SYNCSOURCE){ | ||
361 | syncsource_found=1; | ||
362 | min_peer_sel=PEER_SYNCSOURCE; | ||
363 | } | ||
364 | } | ||
365 | } | ||
366 | if(verbose) printf("%d candiate peers available\n", num_candidates); | ||
367 | if(verbose && syncsource_found) printf("synchronization source found\n"); | ||
368 | /* XXX if ! syncsource_found set status to warning */ | ||
369 | |||
370 | for (run=0; run<AVG_NUM; run++){ | ||
371 | if(verbose) printf("jitter run %d of %d\n", run+1, AVG_NUM); | ||
372 | for (i = 0; i < npeers; i++){ | ||
373 | /* Only query this server if it is the current sync source */ | ||
374 | if (PEER_SEL(peers[i].status) >= min_peer_sel){ | ||
375 | setup_control_request(&req, OP_READVAR, 2); | ||
376 | req.assoc = peers[i].assoc; | ||
377 | /* By spec, putting the variable name "jitter" in the request | ||
378 | * should cause the server to provide _only_ the jitter value. | ||
379 | * thus reducing net traffic, guaranteeing us only a single | ||
380 | * datagram in reply, and making intepretation much simpler | ||
381 | */ | ||
382 | strncpy(req.data, "jitter", 6); | ||
383 | req.count = htons(6); | ||
384 | DBG(printf("sending READVAR request...\n")); | ||
385 | write(conn, &req, SIZEOF_NTPCM(req)); | ||
386 | DBG(print_ntp_control_message(&req)); | ||
387 | |||
388 | req.count = htons(MAX_CM_SIZE); | ||
389 | DBG(printf("recieving READVAR response...\n")); | ||
390 | read(conn, &req, SIZEOF_NTPCM(req)); | ||
391 | DBG(print_ntp_control_message(&req)); | ||
392 | |||
393 | /* get to the float value */ | ||
394 | if(verbose) { | ||
395 | printf("parsing jitter from peer %.2x: ", peers[i].assoc); | ||
396 | } | ||
397 | startofvalue = strchr(req.data, '=') + 1; | ||
398 | jitter = strtod(startofvalue, &nptr); | ||
399 | num_selected++; | ||
400 | if(jitter == 0 && startofvalue==nptr){ | ||
401 | printf("warning: unable to parse server response.\n"); | ||
402 | /* XXX errors value ... */ | ||
403 | } else { | ||
404 | if(verbose) printf("%g\n", jitter); | ||
405 | num_valid++; | ||
406 | rval += jitter; | ||
407 | } | ||
408 | } | ||
409 | } | ||
410 | if(verbose){ | ||
411 | printf("jitter parsed from %d/%d peers\n", num_selected, num_valid); | ||
412 | } | ||
413 | } | ||
414 | |||
415 | rval /= num_valid; | ||
416 | |||
417 | close(conn); | ||
418 | free(peers); | ||
419 | /* If we return -1.0, it means no synchronization source was found */ | ||
420 | return rval; | ||
210 | } | 421 | } |
211 | 422 | ||
212 | int process_arguments(int argc, char **argv){ | 423 | int process_arguments(int argc, char **argv){ |
@@ -247,7 +458,7 @@ int process_arguments(int argc, char **argv){ | |||
247 | exit(STATE_OK); | 458 | exit(STATE_OK); |
248 | break; | 459 | break; |
249 | case 'v': | 460 | case 'v': |
250 | verbose = 1; | 461 | verbose++; |
251 | break; | 462 | break; |
252 | case 'w': | 463 | case 'w': |
253 | owarn = atof(optarg); | 464 | owarn = atof(optarg); |
@@ -321,35 +532,49 @@ int main(int argc, char *argv[]){ | |||
321 | 532 | ||
322 | offset = offset_request(server_address); | 533 | offset = offset_request(server_address); |
323 | if(offset > ocrit){ | 534 | if(offset > ocrit){ |
324 | printf("NTP CRITICAL: "); | ||
325 | result = STATE_CRITICAL; | 535 | result = STATE_CRITICAL; |
326 | } else if(offset > owarn) { | 536 | } else if(offset > owarn) { |
327 | printf("NTP WARNING: "); | ||
328 | result = STATE_WARNING; | 537 | result = STATE_WARNING; |
329 | } else { | 538 | } else { |
330 | printf("NTP OK: "); | ||
331 | result = STATE_OK; | 539 | result = STATE_OK; |
332 | } | 540 | } |
333 | 541 | ||
334 | /* not implemented yet: */ | 542 | /* If not told to check the jitter, we don't even send packets. |
335 | jitter=jitter_request(server_address); | 543 | * jitter is checked using NTP control packets, which not all |
336 | 544 | * servers recognize. Trying to check the jitter on OpenNTPD | |
337 | /* not implemented yet: | 545 | * (for example) will result in an error |
546 | */ | ||
338 | if(do_jitter){ | 547 | if(do_jitter){ |
548 | jitter=jitter_request(server_address); | ||
339 | if(jitter > jcrit){ | 549 | if(jitter > jcrit){ |
340 | printf("NTP CRITICAL: "); | 550 | result = max_state(result, STATE_CRITICAL); |
341 | result = STATE_CRITICAL; | ||
342 | } else if(jitter > jwarn) { | 551 | } else if(jitter > jwarn) { |
552 | result = max_state(result, STATE_WARNING); | ||
553 | } else if(jitter == -1.0 && result == STATE_OK){ | ||
554 | /* -1 indicates that we couldn't calculate the jitter | ||
555 | * Only overrides STATE_OK from the offset */ | ||
556 | result = STATE_UNKNOWN; | ||
557 | } | ||
558 | } | ||
559 | |||
560 | switch (result) { | ||
561 | case STATE_CRITICAL : | ||
562 | printf("NTP CRITICAL: "); | ||
563 | break; | ||
564 | case STATE_WARNING : | ||
343 | printf("NTP WARNING: "); | 565 | printf("NTP WARNING: "); |
344 | result = STATE_WARNING; | 566 | break; |
345 | } else { | 567 | case STATE_OK : |
346 | printf("NTP OK: "); | 568 | printf("NTP OK: "); |
347 | result = STATE_OK; | 569 | break; |
348 | } | 570 | default : |
571 | printf("NTP UNKNOWN: "); | ||
572 | break; | ||
349 | } | 573 | } |
350 | */ | ||
351 | 574 | ||
352 | printf("Offset %g secs|offset=%g\n", offset, offset); | 575 | printf("Offset %g secs|offset=%g", offset, offset); |
576 | if (do_jitter) printf("|jitter=%f", jitter); | ||
577 | printf("\n"); | ||
353 | 578 | ||
354 | if(server_address!=NULL) free(server_address); | 579 | if(server_address!=NULL) free(server_address); |
355 | return result; | 580 | return result; |