From 9f4eac6af5c6a145e3fa3e1545bc0e3bc6774ee8 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 29 Mar 2006 22:37:35 +0000 Subject: Initial import --- module/bsnmp-ping.c | 832 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 832 insertions(+) create mode 100644 module/bsnmp-ping.c (limited to 'module/bsnmp-ping.c') diff --git a/module/bsnmp-ping.c b/module/bsnmp-ping.c new file mode 100644 index 0000000..029acdb --- /dev/null +++ b/module/bsnmp-ping.c @@ -0,0 +1,832 @@ +/* + * Copyright (c) 2006, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen + */ + +#include "usuals.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "ping_tree.h" +#include "ping_oid.h" + +/* our module handle */ +static struct lmodule *module; + +/* OIDs */ +static const struct asn_oid oid_ping = OIDX_pingData; + +/* the Object Resource registration index */ +static u_int reg_index = 0; + +enum { + RESPONSE_ERR = -1, + RESPONSE_NONE = 0, + RESPONSE_REPLY = 1 +}; + +#define A(t, bit) (t)[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(t, bit) (A((t), bit) |= B(bit)) +#define CLR(t, bit) (A((t), bit) &= (~B(bit))) +#define TST(t, bit) (A((t), bit) & B(bit)) +#define MAX_DUP_CHK 8 * 64 + +struct pinger { + uint32_t index; + TAILQ_ENTRY(pinger) link; + + /* Configuration */ + u_char *host; + struct sockaddr_in addr; + u_int interval; + u_int history; + + /* Stats gathered */ + u_int at; + int *latencies; + + /* For generating and filtering pings */ + void *timer; + u_int transmitted; + u_int received; + char ck_table[MAX_DUP_CHK / 8]; +}; + +TAILQ_HEAD(pinger_list, pinger); + +/* list of regexes */ +static struct pinger_list pingers = TAILQ_HEAD_INITIALIZER(pingers); +static u_int pinger_count = 0; + +/* The ICMP socket */ +static int icmp_sock = -1; +static void *icmp_sel = NULL; + +/* The ICMP packet */ +static u_char icmp_packet[IP_MAXPACKET]; + +/* ----------------------------------------------------------------------------- + * HELPERS + */ + +static void +emsg(const char *format, ...) +{ + va_list va; + va_start(va, format); + vsyslog(LOG_ERR, format, va); + va_end(va); +} + +/* ----------------------------------------------------------------------------- + * ICMP STUFF + */ + +static void +process_icmp (u_char *packet, int len, struct sockaddr_in *from) +{ + struct pinger *ping; + struct icmp *icp; + struct ip *ip; + uint64_t ptime; + uint64_t ctime; + const void *tp; + int triptime; + int hlen, seq; + + /* Check the IP header */ + ip = (struct ip*)packet; + hlen = ip->ip_hl << 2; + if (len < hlen + ICMP_MINLEN) + return; + + /* Now the ICMP part */ + len -= hlen; + icp = (struct icmp *)(packet + hlen); + + if (icp->icmp_type != ICMP_ECHOREPLY) + return; /* An error or some such */ + + for (ping = TAILQ_FIRST (&pingers); ping; + ping = TAILQ_NEXT (ping, link)) { + if (ping->index == icp->icmp_id) + break; + } + + if (!ping) + return; /* 'Twas not our ECHO */ + + seq = ntohs (icp->icmp_seq); + if (TST (ping->ck_table, seq % MAX_DUP_CHK)) + return; + SET(ping->ck_table, seq % MAX_DUP_CHK); + seq %= ping->history; + + ping->received++; + triptime = -1; + + + ctime = get_ticks (); + +#ifndef icmp_data + tp = &icp->icmp_ip; +#else + tp = icp->icmp_data; +#endif + if (len - ICMP_MINLEN >= sizeof (ptime)) { + /* Copy to avoid alignment problems: */ + memcpy(&ptime, tp, sizeof(ptime)); + triptime = ((int)(ctime - ptime)); + + /* Some small rounding adjustments */ + if (triptime == 0) + triptime = 1; + if (triptime < 0) + triptime = 0; + } + + if (ping->latencies) + ping->latencies[seq] = triptime; +} + +static void +close_socket () +{ + if (icmp_sel) + fd_deselect (icmp_sel); + icmp_sel = NULL; + + if (icmp_sock != -1) + close (icmp_sock); + icmp_sock = -1; +} + +static void +io_icmp (int fd, void *unused) +{ + struct sockaddr_in from; + socklen_t fromlen; + int len; + + ASSERT (fd == icmp_sock); + + fromlen = sizeof (from); + len = recvfrom (icmp_sock, icmp_packet, sizeof (icmp_packet), 0, + (struct sockaddr*)&from, &fromlen); + if (len < 0) { + if (errno == EINTR) + return; + emsg ("couldn't receive packet: %s", strerror (errno)); + } + + process_icmp (icmp_packet, len, &from); +} + +static int +open_socket () +{ + int hold; + + close_socket (); + + icmp_sock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (icmp_sock == -1) { + emsg ("couldn't open ICMP socket: %s", strerror (errno)); + return errno; + } + + hold = IP_MAXPACKET + 128; + setsockopt(icmp_sock, SOL_SOCKET, SO_RCVBUF, (char*)&hold, sizeof(hold)); + setsockopt(icmp_sock, SOL_SOCKET, SO_SNDBUF, (char*)&hold, sizeof(hold)); + + icmp_sel = fd_select (icmp_sock, io_icmp, NULL, module); + if (!icmp_sel) { + emsg ("couldn't listen on ICMP socket: %s", strerror (errno)); + return errno; + } + + return 0; +} + +static u_short +in_cksum (u_short *addr, int len) +{ + int nleft, sum; + u_short *w; + union { + u_short us; + u_char uc[2]; + } last; + u_short answer; + + nleft = len; + sum = 0; + w = addr; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) { + last.uc[0] = *(u_char *)w; + last.uc[1] = 0; + sum += last.us; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return answer ; +} + + +static void +ping_fire (void *user_data) +{ + struct pinger *ping = (struct pinger*)user_data; + uint64_t ptime; + struct icmp *icp; + int cc, i, seq; + + memset (icmp_packet, 0, sizeof (icmp_packet)); + + ping->at = (ping->at + ((ping->transmitted > 1) ? 1 : 0)) % ping->history; + seq = ping->transmitted; + ASSERT (ping->transmitted == 0 || + (ping->transmitted % ping->history) == ((ping->at + 1) % ping->history)); + + if (ping->latencies) + ping->latencies[seq % ping->history] = -1; + + icp = (struct icmp*)icmp_packet; + icp->icmp_type = ICMP_ECHO; + icp->icmp_code = 0; + icp->icmp_cksum = 0; + icp->icmp_seq = htons (seq); + icp->icmp_id = ping->index; /* ID */ + + /* Include the current time in the packet */ + ptime = get_ticks (); + memcpy (icmp_packet + ICMP_MINLEN, &ptime, sizeof (ptime)); + + CLR (ping->ck_table, seq % MAX_DUP_CHK); + + /* compute ICMP checksum here */ + cc = ICMP_MINLEN + sizeof (ptime); + icp->icmp_cksum = in_cksum ((u_short *)icp, cc); + + i = sendto(icmp_sock, icmp_packet, cc, 0, (struct sockaddr*)&(ping->addr), + sizeof (ping->addr)); + + if (i < 0) + emsg ("couldn't send ICMP packet to: %s: %s", ping->host, strerror (errno)); + else if (i != cc) + emsg ("partial write sending ICMP packet to: %", ping->host); + + ping->transmitted++; + + ping->timer = timer_start (ping->interval, ping_fire, ping, module); + if (!ping->timer) + emsg ("couldn't setup timer to ping host: %s", ping->host); +} + +static void +ping_stop () +{ + struct pinger *ping; + + for (ping = TAILQ_FIRST (&pingers); ping; + ping = TAILQ_NEXT (ping, link)) { + + if (ping->timer) + timer_stop (ping->timer); + ping->timer = NULL; + + memset (ping->ck_table, 0, sizeof (ping->ck_table)); + ping->transmitted = 0; + + if (ping->latencies) + memset (ping->latencies, 0, sizeof (u_int) * ping->history); + } +} + +static void +ping_start () +{ + struct pinger *ping; + + ping_stop (); + + for (ping = TAILQ_FIRST (&pingers); ping; + ping = TAILQ_NEXT (ping, link)) { + + /* Need to have an address configured */ + if (!ping->host || !ping->host[0]) + return; + + /* Allocate memory where needed */ + if (!ping->latencies) + ping->latencies = (u_int*)calloc (ping->history, sizeof (u_int)); + if (!ping->latencies) { + emsg ("out of memory"); + continue; + } + + ping->at = 0; + + ping_fire (ping); + } +} + +/* ----------------------------------------------------------------------------- + * CALLBACKS/CONFIG + */ + +static int +calc_average (struct pinger *ping) +{ + double sum = 0; + double num = 0; + int i; + + if (!ping->latencies) + return -1; + + for (i = 0; i < ping->history; i++) { + + /* If not wrapped around yet, then don't wrap around */ + if (ping->transmitted <= ping->history && i >= ping->at) + break; + if (ping->latencies[i] < 0) + continue; + + sum += ping->latencies[i]; + num ++; + } + + return num ? (sum / num) : 0; +} + +static int +calc_minimum (struct pinger *ping) +{ + int min = -1; + int i; + + if (!ping->latencies) + return -1; + + for (i = 0; i < ping->history; i++) { + + /* If not wrapped around yet, then don't wrap around */ + if (ping->transmitted <= ping->history && i >= ping->at) + break; + + if (ping->latencies[i] < 0) + continue; + if (min > 0 && min < ping->latencies[i]) + continue; + + min = ping->latencies[i]; + } + + return min > 0 ? min : 0; +} + +static int +calc_maximum (struct pinger *ping) +{ + int max = -1; + int i; + + if (!ping->latencies) + return -1; + + for (i = 0; i < ping->history; i++) { + + /* If not wrapped around yet, then don't wrap around */ + if (ping->transmitted <= ping->history && i >= ping->at) + break; + if (ping->latencies[i] < 0) + continue; + if (max > ping->latencies[i]) + continue; + + max = ping->latencies[i]; + } + + return max > 0 ? max : 0; +} + +static int +calc_responses (struct pinger *ping) +{ + u_int num = 0; + int i; + + if (!ping->latencies) + return -1; + + for (i = 0; i < ping->history; i++) { + + /* If not wrapped around yet, then don't wrap around */ + if (ping->transmitted <= ping->history && i >= ping->at) + break; + if (ping->latencies[i] < 0) + continue; + + num ++; + } + + return num; +} + +static int +calc_dropped (struct pinger *ping) +{ + u_int num = 0; + int i; + + if (!ping->latencies) + return -1; + + for (i = 0; i < ping->history; i++) { + + /* If not wrapped around yet, then don't wrap around */ + if (ping->transmitted <= ping->history && i >= ping->at) + break; + if (ping->latencies[i] >= 0) + continue; + + num++; + } + + return num; +} + + +static void +free_pinger (struct pinger *ping) +{ + if (ping->host) + free (ping->host); + ping->host = NULL; + + if (ping->latencies) + free (ping->latencies); + ping->latencies = NULL; + + TAILQ_REMOVE (&pingers, ping, link); + free (ping); +} + +static struct pinger* +alloc_pinger (u_int index) +{ + struct pinger *ping; + + ping = (struct pinger*)calloc (1, sizeof (struct pinger)); + if (!ping) + return NULL; + + ping->host = strdup (""); + if (!ping->host) { + free (ping); + return NULL; + } + + ping->interval = 100; + ping->history = 5; + ping->index = index; + ping->addr.sin_family = AF_INET; + ping->addr.sin_len = sizeof (ping->addr); + ping->addr.sin_port = 0; + + INSERT_OBJECT_INT (ping, &pingers); + return ping; +} + +static int +ping_config (struct pinger *ping, struct snmp_context *ctx, + struct snmp_value *value, u_int sub, u_int iidx, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + int index, r; + + /* Just return values, no creation */ + if (op == SNMP_OP_GET || op == SNMP_OP_GETNEXT) { + + if (!ping) + return SNMP_ERR_NOSUCHNAME; + + switch (which) { + case LEAF_pingIndex: + value->v.integer = ping->index; + return SNMP_ERR_NOERROR; + case LEAF_pingHost: + return string_get (value, ping->host, -1); + case LEAF_pingHistory: + value->v.integer = ping->history; + return SNMP_ERR_NOERROR; + case LEAF_pingInterval: + value->v.integer = ping->interval; + return SNMP_ERR_NOERROR; + default: + ASSERT (0); + return SNMP_ERR_NOSUCHNAME; + } + } + + /* Remainder only at initialization */ + if (community != COMM_INITIALIZE) + return ping ? SNMP_ERR_NOT_WRITEABLE : SNMP_ERR_NO_CREATION; + + /* No writing to pingIndex */ + if (which == LEAF_pingIndex) + return SNMP_ERR_NOT_WRITEABLE; + + if (index_decode (&value->var, sub, iidx, &index)) + return SNMP_ERR_NO_CREATION; + + /* Create it if necessary */ + if (!ping) { + ping = alloc_pinger (index); + if (!ping) { + emsg ("out of memory"); + return SNMP_ERR_GENERR; + } + } + + switch (which) { + + /* pingHistory */ + case LEAF_pingHistory: + switch (op) { + case SNMP_OP_SET: + ctx->scratch->int1 = ping->history; + ping->history = value->v.integer; + return SNMP_ERR_NOERROR; + case SNMP_OP_ROLLBACK: + ping->history = ctx->scratch->int1; + return SNMP_ERR_NOERROR; + case SNMP_OP_COMMIT: + return SNMP_ERR_NOERROR; + default: + ASSERT (0); + return SNMP_ERR_GENERR; + } + break; + + /* pingInterval */ + case LEAF_pingInterval: + switch (op) { + case SNMP_OP_SET: + ctx->scratch->int1 = ping->interval; + ping->interval = value->v.integer; + return SNMP_ERR_NOERROR; + case SNMP_OP_ROLLBACK: + ping->interval = ctx->scratch->int1; + return SNMP_ERR_NOERROR; + case SNMP_OP_COMMIT: + return SNMP_ERR_NOERROR; + default: + ASSERT (0); + return SNMP_ERR_GENERR; + } + break; + + /* pingHost */ + case LEAF_pingHost: + switch (op) { + case SNMP_OP_SET: + if ((r = string_save (value, ctx, -1, &ping->host)) == SNMP_ERR_NOERROR) { + if (!inet_aton (ping->host, &(ping->addr.sin_addr))) + r = SNMP_ERR_WRONG_VALUE; + } + if (r != SNMP_ERR_NOERROR) + string_rollback (ctx, &ping->host); + return r; + case SNMP_OP_ROLLBACK: + string_rollback (ctx, &ping->host); + inet_aton (ping->host, &(ping->addr.sin_addr)); + return SNMP_ERR_NOERROR; + case SNMP_OP_COMMIT: + string_commit (ctx); + return SNMP_ERR_NOERROR; + default: + ASSERT (0); + return SNMP_ERR_GENERR; + } + break; + + /* Unknown OID */ + default: + ASSERT (0); + return SNMP_ERR_NOSUCHNAME; + } +} + +int +op_pingentry (struct snmp_context *ctx, struct snmp_value *value, + u_int sub, u_int iidx, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + struct pinger *ping = NULL; + + switch (op) { + case SNMP_OP_GETNEXT: + ping = NEXT_OBJECT_INT (&pingers, &value->var, sub); + if (ping == NULL) + return SNMP_ERR_NOSUCHNAME; + value->var.len = sub + 1; + value->var.subs[sub] = ping->index; + break; + + case SNMP_OP_GET: + ping = FIND_OBJECT_INT (&pingers, &value->var, sub); + if (ping == NULL) + return SNMP_ERR_NOSUCHNAME; + break; + + default: + ping = FIND_OBJECT_INT (&pingers, &value->var, sub); + break; + } + + /* Send configuration stuff off elsewhere */ + switch (which) { + case LEAF_pingIndex: + case LEAF_pingHost: + case LEAF_pingHistory: + case LEAF_pingInterval: + return ping_config (ping, ctx, value, sub, iidx, op); + } + + if (op != SNMP_OP_GET && op != SNMP_OP_GETNEXT) + return SNMP_ERR_NOT_WRITEABLE; + + switch (which) { + case LEAF_pingLatencyAvg: + value->v.integer = calc_average (ping); + return SNMP_ERR_NOERROR; + case LEAF_pingLatencyMin: + value->v.integer = calc_minimum (ping); + return SNMP_ERR_NOERROR; + case LEAF_pingLatencyMax: + value->v.integer = calc_maximum (ping); + return SNMP_ERR_NOERROR; + case LEAF_pingResponses: + value->v.integer = calc_responses (ping); + return SNMP_ERR_NOERROR; + case LEAF_pingDropped: + value->v.integer = calc_dropped (ping); + return SNMP_ERR_NOERROR; + default: + ASSERT (0); + return SNMP_ERR_NOSUCHNAME; + } +} + +int +op_ping (struct snmp_context *ctx, struct snmp_value *value, + u_int sub, u_int iidx, enum snmp_op op) +{ + asn_subid_t which = value->var.subs[sub - 1]; + + switch (op) { + case SNMP_OP_GET: + break; + + case SNMP_OP_SET: + return SNMP_ERR_NOT_WRITEABLE; + + case SNMP_OP_ROLLBACK: + case SNMP_OP_COMMIT: + return SNMP_ERR_NOERROR; + + default: + ASSERT(0); + break; + } + + switch (which) { + case LEAF_pingCount: + value->v.integer = pinger_count; + break; + + default: + ASSERT(0); + break; + } + + return SNMP_ERR_NOERROR; +} + + +/* ----------------------------------------------------------------------------- + * MODULE + */ + +/* the initialisation function */ +static int +module_init (struct lmodule *mod, int argc, char *argv[]) +{ + module = mod; + + if (argc != 0) { + syslog(LOG_ERR, "bad number of arguments for %s", __func__); + return EINVAL; + } + + return open_socket (); +} + +/* Module is started */ +static void +module_start (void) +{ + reg_index = or_register (&oid_ping, "The MIB for ping data.", module); + + /* Start timers on them all */ + ping_start (); +} + +/* Called, when the module is to be unloaded after it was successfully loaded */ +static int +module_fini (void) +{ + struct pinger *ping; + + if (reg_index) + or_unregister (reg_index); + + while ((ping = TAILQ_FIRST(&pingers)) != NULL) + free_pinger (ping); + + return 0; +} + +const struct snmp_module config = { + .comment = "This module implements SNMP listing of ping responses and latencies", + .init = module_init, + .start = module_start, + .fini = module_fini, + .tree = ping_ctree, + .tree_size = ping_CTREE_SIZE, +}; -- cgit v1.2.3