net-snmp/apps/snmppcap.c

244 lines
7.5 KiB
C

/*
* Copyright (c) 2015, 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:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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
* HOLDER 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 <net-snmp/net-snmp-includes.h>
#include <net-snmp/library/large_fd_set.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pcap/pcap.h>
#define FAKE_FD 3
/*
* This is a funny little program, hooking together callbacks
* to get packets from a pcap format file to go through
* net-snmp's main loop. Both net-snmp and libpcap have
* callback-based processing, so:
*
* We create a fake snmp transport that calls snmppcap_recv()
* to receive a packet, and use snmp_add() to add the session
* and the transport directly.
*
* We create a session callback on the session that just
* prints out the varbind list that we got.
*
* We use the libpcap pcap_dispatch() to loop through all
* the packets in the pcap file, and then pretend to snmp_read2()
* that a fake file descriptor is ready. Luckily, there is
* only one session active, and it happens to also claim to
* be using that file descriptor, so snmplib calls the transport
* receive function, snmppcap_recv(). If the packet parses,
* we get a callback at snmppcap_callback(). If it doesn't,
* you may need to use the -D options.
*/
typedef struct mystuff {
int pktnum;
} mystuff_t;
/*
* These two globals are used to communicate between handle_pcap()
* and snmppcap_recv(). Don't try to multi-thread! :-)
*/
const void *recv_data;
int recv_datalen;
int
snmppcap_recv(netsnmp_transport *t, void *buf, int bufsiz, void **opaque, int *opaque_len)
{
if (bufsiz > recv_datalen) {
memcpy(buf, recv_data, recv_datalen);
return recv_datalen;
} else {
return -1;
}
}
/*
* snmplib calls us back with the received packet.
*/
static int
snmppcap_callback(int op, netsnmp_session *sess, int reqid, netsnmp_pdu *pdu,
void *magic)
{
mystuff_t *mystuff = (mystuff_t *)magic;
netsnmp_variable_list *vars;
/*
* We ignore op, since we know there is only way we can be
* called back, since this is not a "real" transport.
*/
printf( "Packet %d PDU contents:\n", mystuff->pktnum );
/*
* TODO: print PDU type and other info?
*/
for (vars = pdu->variables; vars; vars = vars->next_variable) {
printf( " " );
print_variable(vars->name, vars->name_length, vars );
}
return 0;
}
void
handle_pcap(u_char *user, const struct pcap_pkthdr *h,
const u_char *bytes)
{
size_t len;
const u_char *buf;
int skip;
mystuff_t *mystuff = (mystuff_t *)user;
netsnmp_large_fd_set lfdset;
mystuff->pktnum++;
/*
* If it's not a full packet, then we can't parse it.
*/
if ( h->caplen < h->len ) {
printf( "Skipping packet #%d; we only have %d of %d bytes\n", mystuff->pktnum, h->caplen, h->len );
return;
}
/*
* For now, no error checking and almost no parsing.
* Assume that we have all Ethernet/IPv4/UDP/SNMP.
*/
skip = 14 /* Ethernet */ + 20 /* IPv4 */ + 8 /* UDP */;
buf = bytes + skip;
len = h->len - skip;
printf( "Packet #%d:\n", mystuff->pktnum );
/*
* Store the data in the globals that we use to communicate
*/
recv_data = buf;
recv_datalen = len;
/*
* We call snmp_read2() pretending that our
* fake file descriptor is ready to read.
* This is a funny API to fake up - we need to
* set our fake file descriptor so that our fake
* receive function gets called.
*/
netsnmp_large_fd_set_init(&lfdset, FD_SETSIZE);
netsnmp_large_fd_setfd(FAKE_FD, &lfdset);
snmp_read2(&lfdset);
netsnmp_large_fd_set_cleanup(&lfdset);
}
void
usage(void)
{
fprintf(stderr, "USAGE: snmppcap [OPTIONS] FILE\n\n");
/* can't use snmp_parse_args_usage because it assumes an agent */
snmp_parse_args_descriptions(stderr);
}
int main(int argc, char **argv)
{
netsnmp_session *ss;
netsnmp_transport *transport;
int arg;
char errbuf[PCAP_ERRBUF_SIZE];
char *fname;
pcap_t *p;
mystuff_t mystuff;
ss = SNMP_MALLOC_TYPEDEF(netsnmp_session);
/*
* snmp_parse_args usage here is totally overkill, but trying to
* parse -D
*/
switch (arg = snmp_parse_args(argc, argv, ss, "", NULL)) {
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, "Specify exactly one file name\n");
usage();
exit(1);
}
fname = argv[ arg-1 ];
p = pcap_open_offline( fname, errbuf );
if ( p == NULL ) {
fprintf(stderr, "%s: %s\n", fname, errbuf );
return 1;
}
if ( pcap_datalink( p ) != DLT_EN10MB) {
fprintf(stderr, "Only Ethernet pcaps currently supported\n");
return 2;
}
transport = SNMP_MALLOC_TYPEDEF(netsnmp_transport);
if ( transport == NULL ) {
fprintf(stderr, "Could not malloc transport\n" );
return 3;
}
/*
* We set up just enough of the transport to fake the main
* loop into calling us back.
*/
transport->sock = FAKE_FD; /* nobody actually uses this as a file descriptor */
transport->f_recv = snmppcap_recv;
ss->callback = snmppcap_callback;
ss->callback_magic = (void *)&mystuff;
/* todo: add the option of a filter here */
mystuff.pktnum = 0;
/* todo: user, etc. parsing. */
ss->securityModel = SNMP_SEC_MODEL_USM;
printf("flags %lx securityModel %d version %ld securityNameLen %" NETSNMP_PRIz "d securityEngineIDLen %" NETSNMP_PRIz "d\n",
ss->flags, ss->securityModel, ss->version,
ss->securityNameLen, ss->securityEngineIDLen);
create_user_from_session(ss);
/*
* We use snmp_add() to specify the transport
* explicitly.
*/
snmp_add(ss, transport, NULL, NULL);
pcap_loop(p, -1, handle_pcap, (void *)&mystuff);
return 0;
}