net-snmp/apps/snmpping.c

656 lines
20 KiB
C

/*
* Portions of this file are subject to the following copyright(s). See
* the Net-SNMP's COPYING file for more details and other copyrights
* that may apply.
*/
/*
* Copyright (c) 2013, Arista Networks, Inc.
* 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.
*
* * Neither the name of Arista Networks, Inc. nor the names of its
* contributors may 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
* HOLDERS 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.
*/
#include <net-snmp/net-snmp-config.h>
#include <signal.h>
#include <ctype.h>
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <sys/types.h>
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#include <math.h>
#include <net-snmp/net-snmp-includes.h>
#include "inet_ntop.h"
/* XXX */
#define INETADDRESSTYPE_IPV4 1
#define INETADDRESSTYPE_IPV6 2
#define PINGCTLADMINSTATUS_ENABLED 1
/* Target info */
int targetAddrType;
u_char targetAddr[16];
int targetAddrLen;
char *targetName;
/* Parameters */
int pings = 15;
int datasize = 0;
/* todo: timeout, data fill, ownerindex, testname */
/* Control-C? */
int interrupted = 0;
void
usage(void)
{
fprintf(stderr, "Usage: snmpping ");
snmp_parse_args_usage(stderr);
fprintf(stderr, " DESTINATION\n\n");
snmp_parse_args_descriptions(stderr);
fprintf(stderr, "\nsnmpping options:\n");
fprintf(stderr, "\t-Cc<pings>\tSpecify the number of pings (1-15)\n");
fprintf(stderr, "\t-Cs<size>\tSpecify the amount of extra data (0-65507)\n");
}
static void
optProc(int argc, char *const *argv, int opt)
{
char *endptr = NULL;
switch (opt) {
case 'C':
while (*optarg) {
switch (*optarg++) {
case 'c':
pings = strtol(optarg, &endptr, 0);
if (pings < 1 || pings > 15) {
/* out of range */
usage();
exit(1);
}
optarg = endptr;
if (isspace((unsigned char)(*optarg))) {
return;
}
break;
case 's':
datasize = strtol(optarg, &endptr, 0);
if (datasize < 0 || datasize > 65507) {
/* out of range */
usage();
exit(1);
}
optarg = endptr;
if (isspace((unsigned char)(*optarg))) {
return;
}
break;
default:
fprintf(stderr,
"Unknown flag passed to -C: %c\n", optarg[-1]);
exit(1);
}
}
}
}
void sigint(int sig)
{
interrupted = 1;
printf("[interrupted]\n");
}
struct pingResultsTable {
int pingResultsOperStatus;
int pingResultsMinRtt;
int pingResultsMaxRtt;
int pingResultsAverageRtt;
int pingResultsProbeResponses;
int pingResultsSentProbes;
int pingResultsRttSumOfSquares;
char *pingResultsLastGoodProbe;
};
struct pingProbeHistoryTable {
int pingProbeHistoryIndex;
int pingProbeHistoryResponse;
int pingProbeHistoryStatus;
char *pingProbeHistoryTime;
};
const char *
inetaddresstop(u_char *addr, int addrlen, int addrtype)
{
int type;
static char buf[INET6_ADDRSTRLEN];
switch (addrtype) {
case INETADDRESSTYPE_IPV4:
type = AF_INET;
break;
case INETADDRESSTYPE_IPV6:
type = AF_INET6;
break;
default:
buf[0] = '?';
buf[1] = '\0';
return buf;
}
return inet_ntop(type, addr, buf, sizeof(buf));
}
int
add_var(netsnmp_pdu *pdu, const char *mibnodename,
oid * index, size_t indexlen,
u_char type, const void *value, size_t len)
{
oid base[MAX_OID_LEN];
size_t base_length = MAX_OID_LEN;
memset(base, 0, MAX_OID_LEN * sizeof(oid));
if (!snmp_parse_oid(mibnodename, base, &base_length)) {
snmp_perror(mibnodename);
fprintf(stderr, "couldn't find mib node %s, giving up\n",
mibnodename);
exit(1);
}
if (index && indexlen) {
memcpy(&(base[base_length]), index, indexlen * sizeof(oid));
base_length += indexlen;
}
DEBUGMSGTL(("add", "created: "));
DEBUGMSGOID(("add", base, base_length));
DEBUGMSG(("add", "\n"));
snmp_varlist_add_variable(&pdu->variables, base, base_length,
type, value, len);
return base_length;
}
int
add(netsnmp_pdu *pdu, const char *mibnodename,
oid * index, size_t indexlen)
{
return add_var(pdu, mibnodename, index, indexlen, ASN_NULL, NULL, 0);
}
int
my_synch_response(netsnmp_session *ss, netsnmp_pdu *pdu, netsnmp_pdu **response)
{
int status;
status = snmp_synch_response(ss, pdu, response);
if (status == STAT_SUCCESS) {
if (*response) {
if ((*response)->errstat == SNMP_ERR_NOERROR) {
return 0;
} else {
fprintf(stderr, "Error in packet.\nReason: %s\n",
snmp_errstring((*response)->errstat));
if ((*response)->errindex != 0) {
int count;
netsnmp_variable_list *vars;
fprintf(stderr, "Failed object: ");
for (count = 1, vars = (*response)->variables;
vars && count != (*response)->errindex;
vars = vars->next_variable, count++)
/*EMPTY*/;
if (vars)
fprint_objid(stderr, vars->name,
vars->name_length);
else
fprintf(stderr, "??? (errindex=%ld)",
(*response)->errindex);
fprintf(stderr, "\n");
}
return 2;
}
}
} else if (status == STAT_TIMEOUT) {
fprintf(stderr, "Timeout: No Response from %s\n",
ss->peername);
return 1;
} else { /* status == STAT_ERROR */
snmp_sess_perror("snmpping", ss);
return 1;
}
return 0;
}
int
cleanup_ctlTable(netsnmp_session *ss, oid * index, size_t indexlen)
{
netsnmp_pdu *pdu;
netsnmp_pdu *response;
int rowStatus;
int status;
pdu = snmp_pdu_create(SNMP_MSG_SET);
rowStatus = RS_DESTROY;
add_var(pdu, "DISMAN-PING-MIB::pingCtlRowStatus", index, indexlen, ASN_INTEGER,
&rowStatus, sizeof(rowStatus));
status = my_synch_response(ss, pdu, &response);
if (response)
snmp_free_pdu(response);
return status;
}
int
start_ping(netsnmp_session *ss, oid * index, size_t indexlen, char *pingDest)
{
netsnmp_pdu *pdu;
netsnmp_pdu *response;
int adminStatus, rowStatus, storageType;
int status;
struct addrinfo *dest, hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
status = getaddrinfo(pingDest, NULL, &hints, &dest);
if (status != 0) {
fprintf(stderr, "snmpping: %s: %s\n", pingDest, gai_strerror(status));
return 1;
}
/*
* Destroy any previously-existing row. We could get fancy
* and try to reuse it, but that is way more complex.
*/
cleanup_ctlTable(ss, index, indexlen);
switch (dest->ai_family) {
case AF_INET:
targetAddrType = INETADDRESSTYPE_IPV4;
targetAddrLen = sizeof(struct in_addr);
memcpy(targetAddr, &((struct sockaddr_in *)dest->ai_addr)->sin_addr, targetAddrLen);
break;
#ifdef NETSNMP_ENABLE_IPV6
case AF_INET6:
targetAddrType = INETADDRESSTYPE_IPV6;
targetAddrLen = sizeof(struct in6_addr);
memcpy(targetAddr, &((struct sockaddr_in6 *)dest->ai_addr)->sin6_addr, sizeof(struct in6_addr));
break;
#endif
default:
fprintf(stderr, "Unsupported address family\n");
return 3;
}
if (dest->ai_canonname) {
targetName = strdup(dest->ai_canonname);
} else {
targetName = strdup(pingDest);
}
freeaddrinfo(dest);
pdu = snmp_pdu_create(SNMP_MSG_SET);
add_var(pdu, "DISMAN-PING-MIB::pingCtlTargetAddressType", index, indexlen, ASN_INTEGER,
&targetAddrType, sizeof(targetAddrType));
add_var(pdu, "DISMAN-PING-MIB::pingCtlTargetAddress", index, indexlen, ASN_OCTET_STR,
&targetAddr, targetAddrLen);
/* Rely on DEFVAL to keep the PDU small */
if (pings != 1) {
add_var(pdu, "DISMAN-PING-MIB::pingCtlProbeCount", index, indexlen, ASN_UNSIGNED,
&pings, sizeof(pings));
}
if (datasize != 0) {
add_var(pdu, "DISMAN-PING-MIB::pingCtlDataSize", index, indexlen, ASN_UNSIGNED,
&datasize, sizeof(datasize));
}
adminStatus = PINGCTLADMINSTATUS_ENABLED;
add_var(pdu, "DISMAN-PING-MIB::pingCtlAdminStatus", index, indexlen, ASN_INTEGER,
&adminStatus, sizeof(adminStatus));
storageType = ST_VOLATILE; /* don't ask for this to be saved, we're only going to delete it */
add_var(pdu, "DISMAN-PING-MIB::pingCtlStorageType", index, indexlen, ASN_INTEGER,
&storageType, sizeof(storageType));
rowStatus = RS_CREATEANDGO;
add_var(pdu, "DISMAN-PING-MIB::pingCtlRowStatus", index, indexlen, ASN_INTEGER,
&rowStatus, sizeof(rowStatus));
status = my_synch_response(ss, pdu, &response);
if (response)
snmp_free_pdu(response);
if (status == 0) {
printf("PING %s (%s) from %s with %d bytes of extra data\n", targetName,
inetaddresstop(targetAddr, targetAddrLen, targetAddrType), ss->peername,
datasize);
}
return status;
}
int
wait_for_completion(netsnmp_session *ss, oid * index, size_t indexlen)
{
int running = 1;
int status;
int pingStatus;
int sent;
int responses, prev_responses = 0;
int tries = 0;
netsnmp_pdu *pdu;
netsnmp_pdu *response;
netsnmp_variable_list *vlp;
while (running && !interrupted) {
pdu = snmp_pdu_create(SNMP_MSG_GET);
add(pdu, "DISMAN-PING-MIB::pingResultsOperStatus", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsSentProbes", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsProbeResponses", index, indexlen);
status = snmp_synch_response(ss, pdu, &response);
if (status != STAT_SUCCESS || !response) {
snmp_sess_perror("snmpping", ss);
if (status == STAT_TIMEOUT)
goto retry;
running = 0;
goto out;
}
if (response->errstat != SNMP_ERR_NOERROR) {
fprintf(stderr, "snmpping: Error in packet: %s\n",
snmp_errstring(response->errstat));
running = 0;
goto out;
}
vlp = response->variables;
if (vlp->type == SNMP_NOSUCHINSTANCE) {
DEBUGMSGTL(("ping", "no-such-instance for pingResultsOperStatus\n"));
goto retry;
}
pingStatus = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHINSTANCE) {
DEBUGMSGTL(("ping", "no-such-instance for pingResultsSentProbes\n"));
goto retry;
}
sent = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHINSTANCE) {
DEBUGMSGTL(("ping", "no-such-instance for pingResultsProbeResponses\n"));
goto retry;
}
responses = *vlp->val.integer;
#define PINGRESULTSOPERSTATUS_ENABLED 1 /* XXX */
#define PINGRESULTSOPERSTATUS_DISABLED 2 /* XXX */
#define PINGRESULTSOPERSTATUS_COMPLETED 3 /* XXX */
if (responses > prev_responses || pingStatus == PINGRESULTSOPERSTATUS_COMPLETED) {
DEBUGMSGTL(("ping", "responses %d (was %d), status %d\n", responses, prev_responses, pingStatus));
/* collect results between prev_responses and responses by walking probeHistoryTable */
prev_responses = responses;
}
/*
* Observed behavior: before the test has run, operStatus can be
* disabled, and then can turn to enabled, so we can't just stop
* if it's disabled. However, it doesn't always go to completed.
* So, we say we're completed if it's completed, *or* if it's
* disabled and we've sent at least one probe.
*/
if (pingStatus == PINGRESULTSOPERSTATUS_COMPLETED ||
(pingStatus == PINGRESULTSOPERSTATUS_DISABLED && sent > 0)) {
running = 0;
goto out;
}
/* sleep before asking again */
sleep(1);
if (0) {
retry:
if (tries++ < 5) {
/* we can try again */
sleep(1);
} else {
if (status == STAT_TIMEOUT)
fprintf(stderr, "snmpping: too many timeouts.\n");
else
fprintf(stderr, "snmpping: pingResultsTable entry never created.\n");
running = 0;
}
}
out:
if (response)
snmp_free_pdu(response);
}
return 0;
}
int
overall_stats(netsnmp_session *ss, oid * index, size_t indexlen)
{
netsnmp_pdu *pdu;
netsnmp_pdu *response;
netsnmp_variable_list *vlp;
int status;
struct pingResultsTable result;
pdu = snmp_pdu_create(SNMP_MSG_GET);
add(pdu, "DISMAN-PING-MIB::pingResultsOperStatus", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsMinRtt", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsMaxRtt", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsAverageRtt", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsProbeResponses", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsSentProbes", index, indexlen);
add(pdu, "DISMAN-PING-MIB::pingResultsRttSumOfSquares", index, indexlen);
status = snmp_synch_response(ss, pdu, &response);
if (status != STAT_SUCCESS || !response) {
snmp_sess_perror("snmpping", ss);
goto out;
}
if (response->errstat != SNMP_ERR_NOERROR) {
fprintf(stderr, "snmpping: Error in packet: %s\n",
snmp_errstring(response->errstat));
goto out;
}
vlp = response->variables;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsOperStatus = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsMinRtt = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsMaxRtt = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsAverageRtt = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsProbeResponses = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsSentProbes = *vlp->val.integer;
vlp = vlp->next_variable;
if (vlp->type == SNMP_NOSUCHOBJECT) goto parseerr;
result.pingResultsRttSumOfSquares = *vlp->val.integer;
vlp = vlp->next_variable;
printf( "--- %s ping statistics ---\n", targetName );
printf( "%d packets transmitted, %d received, %d%% packet loss\n",
result.pingResultsSentProbes, result.pingResultsProbeResponses,
result.pingResultsSentProbes ?
( ( result.pingResultsSentProbes -
result.pingResultsProbeResponses ) * 100 /
result.pingResultsSentProbes ) : 0 );
if (result.pingResultsProbeResponses) {
double stddev;
stddev = result.pingResultsRttSumOfSquares;
stddev /= result.pingResultsProbeResponses;
stddev -= result.pingResultsAverageRtt * result.pingResultsAverageRtt;
/*
* If the RTT is less than 1.0, the sum of squares can be
* smaller than the number of responses, resulting in a
* negative stddev. Clamp the stddev to 0.
*/
if (stddev < 0)
stddev = 0.0;
printf( "rtt min/avg/max/stddev = %d/%d/%d/%d ms\n",
result.pingResultsMinRtt,
result.pingResultsAverageRtt,
result.pingResultsMaxRtt,
(int)sqrt( stddev ));
}
if (0) {
parseerr:
fprintf(stderr, "snmpping: Error parsing response packet\n");
}
out:
if (response)
snmp_free_pdu(response);
return 0;
}
#ifdef WIN32
/* To do: port this function to the Win32 platform. */
const char *getlogin(void)
{
return "";
}
#endif
int main(int argc, char **argv)
{
netsnmp_session session, *ss;
int ret;
int arg;
oid index[66], *idx;
int indexlen, i;
int usernameLen, testnameLen;
char username[33];
char testname[33];
char *p;
/*
* get the common command line arguments
*/
switch (arg = snmp_parse_args(argc, argv, &session, "C:", optProc)) {
case NETSNMP_PARSE_ARGS_ERROR:
exit(1);
case NETSNMP_PARSE_ARGS_SUCCESS_EXIT:
exit(0);
case NETSNMP_PARSE_ARGS_ERROR_USAGE:
usage();
exit(1);
default:
break;
}
if (arg >= argc) {
fprintf(stderr, "Please specify a destination host.\n");
usage();
exit(1);
}
SOCK_STARTUP;
/*
* open an SNMP session
*/
ss = snmp_open(&session);
if (ss == NULL) {
/*
* diagnose snmp_open errors with the input netsnmp_session pointer
*/
snmp_sess_perror("snmpping", &session);
exit(1);
}
if (session.securityModel == SNMP_SEC_MODEL_USM) {
strncpy(username, session.securityName, sizeof(username) - 1);
username[32] = '\0';
usernameLen = strlen(username); /* TODO session.securityNameLen */
} else {
strncpy(username, getlogin(), sizeof(username) - 1);
username[32] = '\0';
usernameLen = strlen(username);
}
if (1 /* !have-testname-arg */) {
snprintf(testname, sizeof(testname) - 1, "snmpping-%d", getpid());
testname[32] = '\0';
testnameLen = strlen(testname);
}
idx = index;
*idx++ = usernameLen;
p = username;
for (i = 0; i < usernameLen; i++) {
*idx++ = *p++;
}
*idx++ = testnameLen;
p = testname;
for (i = 0; i < testnameLen; i++) {
*idx++ = *p++;
}
indexlen = idx - index;
ret = start_ping( ss, index, indexlen, argv[ arg ] );
if ( ret != 0 ) {
return ret;
}
signal(SIGINT, sigint);
wait_for_completion( ss, index, indexlen );
overall_stats( ss, index, indexlen );
cleanup_ctlTable( ss, index, indexlen );
snmp_close(ss);
SOCK_CLEANUP;
return 0;
}