From 2fa44680485d4caecdfc305ef072dd10c636e703 Mon Sep 17 00:00:00 2001 From: octo Date: Sun, 28 May 2006 15:15:43 +0000 Subject: [PATCH] First (incomplete) code for querying an ntpd. --- configure.in | 2 + src/Makefile.am | 11 ++ src/ntpd.c | 443 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 456 insertions(+) create mode 100644 src/ntpd.c diff --git a/configure.in b/configure.in index de7dbea5..8b44f5e3 100644 --- a/configure.in +++ b/configure.in @@ -833,6 +833,7 @@ AC_COLLECTD([load], [disable], [module], [system load statistics]) AC_COLLECTD([memory], [disable], [module], [memory statistics]) AC_COLLECTD([mysql], [disable], [module], [mysql statistics]) AC_COLLECTD([nfs], [disable], [module], [nfs statistics]) +AC_COLLECTD([ntpd], [disable], [module], [nfs statistics]) AC_COLLECTD([ping], [disable], [module], [ping statistics]) AC_COLLECTD([processes], [disable], [module], [processes statistics]) AC_COLLECTD([sensors], [disable], [module], [lm_sensors statistics]) @@ -878,6 +879,7 @@ Configuration: memory . . . . . . $enable_memory mysql . . . . . . . $enable_mysql nfs . . . . . . . . $enable_nfs + ntpd . . . . . . . $enable_ntpd ping . . . . . . . $enable_ping processes . . . . . $enable_processes sensors . . . . . . $enable_sensors diff --git a/src/Makefile.am b/src/Makefile.am index 5646a062..845ecc1c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -236,6 +236,17 @@ collectd_LDADD += "-dlopen" nfs.la collectd_DEPENDENCIES += nfs.la endif +if BUILD_MODULE_NTPD +pkglib_LTLIBRARIES += ntpd.la +ntpd_la_SOURCES = ntpd.c +ntpd_la_LDFLAGS = -module -avoid-version +if BUILD_WITH_LIBSOCKET +ntpd_la_LDFLAGS += -lsocket +endif +collectd_LDADD += "-dlopen" ntpd.la +collectd_DEPENDENCIES += ntpd.la +endif + if BUILD_MODULE_PING pkglib_LTLIBRARIES += ping.la ping_la_SOURCES = ping.c diff --git a/src/ntpd.c b/src/ntpd.c new file mode 100644 index 00000000..eb871e14 --- /dev/null +++ b/src/ntpd.c @@ -0,0 +1,443 @@ +/** + * collectd - src/ntpd.c + * Copyright (C) 2006 Florian octo Forster + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Florian octo Forster + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" + +#include "ntp_request.h" + +#define MODULE_NAME "ntpd" + +#if HAVE_SYS_SOCKET_H +# define NTPD_HAVE_READ 1 +#else +# define NTPD_HAVE_READ 0 +#endif + +#if HAVE_NETDB_H +# include +#endif +#if HAVE_SYS_SOCKET_H +# include +#endif +#if HAVE_NETINET_IN_H +# include +#endif +#if HAVE_NETINET_TCP_H +# include +#endif + +/* drift */ +static char *time_offset_file = "ntpd/time_offset-%s.rrd"; +static char *time_offset_ds_def[] = +{ + "DS:ms:GAUGE:"COLLECTD_HEARTBEAT":0:100", + NULL +}; +static int time_offset_ds_num = 1; + +static char *frequency_offset_file = "ntpd/frequency_offset-%s.rrd"; +static char *frequency_offset_ds_def[] = +{ + "DS:ppm:GAUGE:"COLLECTD_HEARTBEAT":0:100", + NULL +}; +static int frequency_offset_ds_num = 1; + +#if NTPD_HAVE_READ +# define NTPD_DEFAULT_HOST "localhost" +# define NTPD_DEFAULT_PORT "123" +static int sock_descr = -1; +static char *ntpd_host = NULL; +static char *ntpd_port = NULL; +#endif + +static void ntpd_init (void) +{ + return; +} + +static void ntpd_write (char *host, char *inst, char *val) +{ + rrd_update_file (host, ntpd_file, val, ds_def, ds_num); +} + +#if NTPD_HAVE_READ +static void ntpd_submit (double snum, double mnum, double lnum) +{ + char buf[256]; + + if (snprintf (buf, 256, "%u:%.2f:%.2f:%.2f", (unsigned int) curtime, + snum, mnum, lnum) >= 256) + return; + + plugin_submit (MODULE_NAME, "-", buf); +} + +static void ntpd_connect (void) +{ + char *host; + char *port; + + struct addrinfo ai_hints; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + int status; + + if (sock_descr >= 0) + return (sock_descr); + + host = ntpd_host; + if (host == NULL) + host = NTPD_DEFAULT_HOST; + + port = ntpd_port; + if (port == NULL) + port = NTPD_DEFAULT_PORT; + + memset (&ai_hints, '\0', sizeof (ai_hints)); + ai_hints.ai_flags = AI_ADDRCONFIG; + ai_hints.ai_family = PF_UNSPEC; + ai_hints.ai_socktype = SOCK_DGRAM; + ai_hints.ai_protocol = IPPROTO_UDP; + + if ((status = getaddrinfo (host, port, &ai_hints, &ai_list)) != 0) + { + syslog (LOG_ERR, "ntpd plugin: getaddrinfo (%s, %s): %s", + host, port, + status == EAI_SYSTEM ? strerror (errno) : gai_strerror (status)); + return (-1); + } + + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) + { + /* create our socket descriptor */ + if ((sock_descr = socket (ai_ptr->ai_family, + ai_ptr->ai_socktype, + ai_ptr->ai_protocol)) < 0) + continue; + + /* connect to the ntpd */ + if (connect (sock_descr, ai_ptr->ai_addr, ai_ptr->ai_addrlen)) + { + close (sock_descr); + sock_descr = -1; + continue; + } + + break; + } + + freeaddrinfo (ai_list); + + if (sock_descr < 0) + syslog (LOG_ERR, "ntpd plugin: Unable to connect to server."); + + return (sock_descr); +} + +/* For a description of the arguments see `ntpd_do_query' below. */ +static int ntpd_receive_response (int req_code, int *res_items, int *res_size, + char **res_data, int res_item_size) +{ + int sd; + struct resp_pkt res; + ssize_t status; + int done; + + char *items; + size_t items_num; + + int pkt_item_num; /* items in this packet */ + int pkt_item_len; /* size of the items in this packet */ + int pkt_sequence; + char pkt_recvd[MAXSEQ+1]; /* sequence numbers that have been received */ + int pkt_recvd_num; /* number of packets that have been received */ + int pkt_lastseq; /* the last sequence number */ + ssize_t pkt_padding; /* Padding in this packet */ + + if ((sd = ntpd_connect ()) < 0) + return (-1); + + items = NULL; + items_num = 0; + + memset (pkt_recvd, '\0', sizeof (pkt_recvd)); + pkt_recvd_num = 0; + pkt_lastseq = -1; + + *res_items = 0; + *res_size = 0; + *res_data = NULL; + + done = 0; + while (done == 0) + { + /* TODO calculate time */ + /* TODO poll(2) */ + + memset ((void *) &res, '\0', sizeof (res)); + status = recv (sd, (void *) &res, sizeof (res), 0 /* no flags */); + + if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR))) + continue; + + if (status < 0) + return (-1); + + /* + * Do some sanity checks first + */ + if (status < RESP_HEADER_SIZE) + { + syslog (LOG_WARNING, "ntpd plugin: Short (%i bytes) packet received", + (int) status); + continue; + } + if (INFO_MODE (res.rm_vn_mode) != MODE_PRIVATE) + { + syslog (LOG_NOTICE, "ntpd plugin: Packet received with mode %i", + INFO_MODE (res.rm_vn_mode)); + continue; + } + if (INFO_IS_AUTH (res.auth_seq)) + { + syslog (LOG_NOTICE, "ntpd plugin: Encrypted packet received"); + continue; + } + if (!ISRESPONSE (res.rm_vn_mode)) + { + syslog (LOG_NOTICE, "ntpd plugin: Received request packet, " + "wanted response"); + continue; + } + if (INFO_MBZ (res.mbz_itemsize)) + { + syslog (LOG_WARNING, "ntpd plugin: Received packet with nonzero " + "MBZ field!"); + continue; + } + if (res.implementation != req_code) + { + syslog (LOG_WARNING, "ntpd plugin: Asked for request of type %i, " + "got %i", (int) req_code, (int) res.implementation); + continue; + } + + /* Check for error code */ + if (INFO_ERR (res.err_nitems) != INFO_OKAY) + { + syslog (LOG_ERR, "ntpd plugin: Received error code %i", + (int) INFO_ERR(res.err_nitems)); + return ((int) INFO_ERR (res.err_nitems)); + } + + /* extract number of items in this packet and the size of these items */ + pkt_item_num = INFO_NITEMS (res.err_nitems); + pkt_item_len = INFO_ITEMSIZE (res.mbz_itemsize); + + /* Check if the reported items fit in the packet */ + if ((pkt_item_num * pkt_item_len) > (status - RESP_HEADER_SIZE)) + { + syslog (LOG_ERR, "ntpd plugin: %i items * %i bytes > " + "%i bytes - %i bytes header", + (int) pkt_item_num, (int) pkt_item_len, + (int) status, (int) RESP_HEADER_SIZE); + continue; + } + + /* If this is the first packet (time wise, not sequence wise), + * set `res_size'. If it's not the first packet check if the + * items have the same size. Discard invalid packets. */ + if (items_num == 0) /* first packet */ + { + *res_size = pkt_item_len; + } + else if (*res_size != pkt_item_len) + { + syslog (LOG_ERR, "Item sizes differ."); + continue; + } + + /* Calculate the padding. No idea why there might be any padding.. */ + pkt_padding = 0; + if (res_item_size > pkt_item_len) + pkt_padding = res_item_size - pkt_item_len; + + /* Extract the sequence number */ + pkt_sequence = INFO_SEQ (res.auth_seq); + if ((pkt_sequence < 0) || (pkt_sequence > MAXSEQ)) + { + syslog (LOG_ERR, "ntpd plugin: Received packet with sequence %i", + pkt_sequence); + continue; + } + + /* Check if this sequence has been received before. If so, discard it. */ + if (pkt_recvd[pkt_sequence] != '\0') + { + syslog (LOG_NOTICE, "ntpd plugin: Sequence %i received twice", + pkt_sequence); + continue; + } + + /* If `pkt_lastseq != -1' another packet without `more bit' has + * been received. */ + if (!ISMORE (res.rm_vn_mode)) + { + if (pkt_lastseq != -1) + { + syslog (LOG_ERR, "ntpd plugin: Two packets which both " + "claim to be the last one in the " + "sequence have been received."); + continue; + } + pkt_lastseq = pkt_sequence; + } + + /* + * Enough with the checks. Copy the data now. + * We start by allocating some more memory. + */ + items = realloc ((void *) *res_data, + (items_num + pkt_item_num) * res_item_size); + if (items == NULL) + { + items = *res_data; + syslog (LOG_ERR, "ntpd plugin: realloc failed."); + continue; + } + *res_data = items; + + for (i = 0; i < pkt_item_num; i++) + { + void *dst = (void *) (*res_data + ((*res_items) * res_item_size)); + void *src = (void *) (((char *) res.data) + (i * pkt_item_len)); + + /* Set the padding to zeros */ + if (pkt_padding != 0) + memset (dst, '\0', res_item_size); + memcpy (dst, src, (size_t) pkt_item_len); + + (*res_items)++; + } + + pkt_recvd[pkt_sequence] = (char) 1; + pkt_recvd_num++; + + if ((pkt_recvd_num - 1) == pkt_lastseq) + done = 1; + } /* while (done == 0) */ + + return (0); +} + +/* For a description of the arguments see `ntpd_do_query' below. */ +static int ntpd_send_request (int req_code, int req_items, int req_size, char *req_data) +{ + int sd; + struct req_pkt req; + size_t req_data_len; + int status; + + assert (req_items >= 0); + assert (req_size >= 0); + + if ((sd = ntpd_connect ()) < 0) + return (-1); + + memset ((void *) &req, '\0', sizeof (req)); + req.rm_vn_mode = RM_VN_MODE(0, 0, 0); + req.auth_seq = AUTH_SEQ (0, 0); + req.implementation = IMPL_XNTPD; + req.request = (unsigned char) req_code; + + req_data_len = (size_t) (req_items * req_size); + + assert (((req_data != NULL) && (req_data_len > 0)) + || ((req_data == NULL) && (req_items == 0) && (req_size == 0))); + + req.err_nitems = ERR_NITEMS (0, qitems); + req.mbz_itemsize = MBZ_ITEMSIZE (qsize); + + if (req_data != NULL) + memcpy ((void *) req.data, (const void *) req_data, req_data_len); + + status = swrite (fd, (const char *) &req, REQ_LEN_NOMAC); + if (status < 0) + return (status); + + return (0); +} + +/* + * ntpd_do_query: + * + * req_code: Type of request packet + * req_items: Numver of items in the request + * req_size: Size of one item in the request + * req_data: Data of the request packet + * res_items: Pointer to where the number returned items will be stored. + * res_size: Pointer to where the size of one returned item will be stored. + * res_data: This is where a pointer to the (allocated) data will be stored. + * res_item_size: Size of one returned item. (used to calculate padding) + * + * returns: zero upon success, non-zero otherwise. + */ +static int ntpd_do_query (int req_code, int req_items, int req_size, char *req_data, + int *res_items, int *res_size, char **res_data, int res_item_size) +{ + int status; + + status = ntpd_send_request (req_code, req_items, req_size, req_data); + if (status != 0) + return (status); + + status = ntpd_receive_response (req_code, res_items, res_size, res_data, + res_item_size); + return (status); +} + +static void ntpd_read (void) +{ + static sd = -1; + struct req_pkt req; + + ntpd_connect (&sd); + if (sd < 0) + return; + + memset ((void *) &req, '\0', sizeof (req)); + req.rm_vn_mode = RM_VN_MODE (0, 0, 0); /* response, more, version, mode */ + req.implementation = IMPL_XNTPD; +} +#else +# define ntpd_read NULL +#endif /* NTPD_HAVE_READ */ + +void module_register (void) +{ + plugin_register (MODULE_NAME, ntpd_init, ntpd_read, ntpd_write); +} + +#undef MODULE_NAME -- 2.30.2