/* * Copyright (c) 2008, Stefan Walter * 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 * Stefan Walter */ #include "usuals.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcap_tree.h" #include "pcap_oid.h" #define SNAP_LEN 48 #define DEFAULT_FILTER "ip or ip6" /* our module handle */ static struct lmodule *module; /* OIDs */ static const struct asn_oid oid_pcap = OIDX_pcap; /* the Object Resource registration index */ static u_int reg_index = 0; struct monitor { uint32_t index; TAILQ_ENTRY(monitor) link; u_char *descr; u_char *device; pcap_t *handle; void *watch; u_char *filter; struct bpf_program filter_bpf; int filter_valid; /* Stats gathered */ uint64_t seen_octets; uint64_t seen_packets; }; TAILQ_HEAD(monitor_list, monitor); /* list of monitor structures */ static struct monitor_list monitors = TAILQ_HEAD_INITIALIZER (monitors); /* Number of monitors */ static int monitor_count = 0; /* ----------------------------------------------------------------------------- * HELPERS */ static void emsg(const char *format, ...) { va_list va; va_start (va, format); vsyslog (LOG_ERR, format, va); va_end (va); } /* ----------------------------------------------------------------------------- * MONITORING */ #pragma pack(1) /* Ethernet header */ struct ethhdr { #define ETHER_ADDR_LEN 6 u_char dhost[ETHER_ADDR_LEN]; /* Destination host address */ u_char shost[ETHER_ADDR_LEN]; /* Source host address */ u_short type; /* IP? ARP? RARP? etc */ }; /* IP4 header */ struct ip4hdr { uint8_t vhl; /* version << 4 | header length >> 2 */ uint8_t tos; /* type of service */ uint16_t len; /* total length */ uint16_t id; /* identification */ uint16_t off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ uint8_t ttl; /* time to live */ uint8_t proto; /* protocol */ uint16_t sum; /* checksum */ struct in_addr src, dst; /* source and dest address */ }; /* IP6 header */ struct ip6hdr { int32_t flow; int16_t payload; int8_t next; int8_t hops; struct in6_addr src, dst; }; #pragma pack() static void monitor_packet (u_char *data, const struct pcap_pkthdr *hdr, const u_char *bytes) { struct monitor *mon = (struct monitor*)data; int octets; /* Short packet, don't care */ if (hdr->len < sizeof (struct ethhdr)) return; octets = hdr->len - sizeof (struct ethhdr); mon->seen_octets += octets; mon->seen_packets += 1; } static void monitor_io (int fd, void *data) { struct monitor* mon = (struct monitor*)data; int n_packets; n_packets = pcap_dispatch (mon->handle, -1, monitor_packet, (u_char*)mon); if (n_packets < 0) emsg ("couldn't capture packets in monitor: %s", pcap_geterr (mon->handle)); } static void monitor_free (struct monitor *mon) { ASSERT (mon); if (mon->descr) free (mon->descr); if (mon->device) free (mon->device); if (mon->watch) fd_deselect (mon->watch); if (mon->handle) pcap_close (mon->handle); if (mon->filter_valid) pcap_freecode (&mon->filter_bpf); if (mon->filter) free (mon->filter); TAILQ_REMOVE (&monitors, mon, link); monitor_count--; free (mon); } static struct monitor* monitor_alloc (int index) { char errbuf[PCAP_ERRBUF_SIZE]; struct monitor* mon; mon = calloc (1, sizeof (struct monitor)); if (!mon) { emsg ("couldn't allocate monitor: out of memory"); return NULL; } mon->index = index; INSERT_OBJECT_INT (mon, &monitors); monitor_count++; mon->device = pcap_lookupdev (errbuf); if (!mon->device) { mon->device = strdup ("any"); if (!mon->device) { monitor_free (mon); return NULL; } } mon->filter = strdup (DEFAULT_FILTER); if (!mon->filter) { monitor_free (mon); return NULL; } mon->descr = strdup (""); if (!mon->descr) { monitor_free (mon); return NULL; } return mon; } static void monitor_start (struct monitor *mon) { char errbuf[PCAP_ERRBUF_SIZE]; int fd; ASSERT (mon->device); ASSERT (mon->filter); mon->handle = pcap_open_live (mon->device, SNAP_LEN, 1, 100, errbuf); if (!mon->handle) { emsg ("couldn't open monitor on %s: %s", mon->device, errbuf); return; } if (pcap_compile (mon->handle, &mon->filter_bpf, mon->filter, 1, 0) < 0) { emsg ("couldn't compile monitor expression: %s", pcap_geterr (mon->handle)); return; } mon->filter_valid = 1; if (pcap_setfilter (mon->handle, &mon->filter_bpf) < 0) { emsg ("couldn't setup monitor expression: %s", pcap_geterr (mon->handle)); return; } if (pcap_setnonblock (mon->handle, 1, errbuf) < 0) { emsg ("couldn't set monitor in non-block mode: %s", errbuf); return; } fd = pcap_get_selectable_fd (mon->handle); if (fd < 0) { emsg ("couldn't get selectable monitor: %s", pcap_geterr (mon->handle)); return; } mon->watch = fd_select (fd, monitor_io, mon, module); if (!mon->watch) { emsg ("couldn't listen to monitor: %s", strerror (errno)); return; } } /* ----------------------------------------------------------------------------- * CALLBACKS/CONFIG */ static int op_config (struct monitor *mon, 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 (!mon) return SNMP_ERR_NOSUCHNAME; switch (which) { case LEAF_pcapIndex: value->v.integer = mon->index; return SNMP_ERR_NOERROR; case LEAF_pcapDescr: return string_get (value, mon->descr, -1); case LEAF_pcapDevice: return string_get (value, mon->device, -1); case LEAF_pcapFilter: return string_get (value, mon->filter, -1); default: ASSERT (0); return SNMP_ERR_NOSUCHNAME; } } /* Remainder only at initialization */ if (community != COMM_INITIALIZE) return mon ? SNMP_ERR_NOT_WRITEABLE : SNMP_ERR_NO_CREATION; /* No writing to pcapIndex */ if (which == LEAF_pcapIndex) return SNMP_ERR_NOT_WRITEABLE; if (index_decode (&value->var, sub, iidx, &index)) return SNMP_ERR_NO_CREATION; /* Create it if necessary */ if (!mon) { mon = monitor_alloc (index); if (!mon) { emsg ("out of memory"); return SNMP_ERR_GENERR; } } switch (which) { /* pcapDescr */ case LEAF_pcapDescr: switch (op) { case SNMP_OP_SET: return string_save (value, ctx, -1, &mon->descr); case SNMP_OP_ROLLBACK: return SNMP_ERR_NOERROR; case SNMP_OP_COMMIT: return SNMP_ERR_NOERROR; default: ASSERT (0); return SNMP_ERR_GENERR; } break; /* pcapDevice */ case LEAF_pcapDevice: switch (op) { case SNMP_OP_SET: return string_save (value, ctx, -1, &mon->device); case SNMP_OP_ROLLBACK: return SNMP_ERR_NOERROR; case SNMP_OP_COMMIT: return SNMP_ERR_NOERROR; default: ASSERT (0); return SNMP_ERR_GENERR; } break; /* pcapFilter */ case LEAF_pcapFilter: switch (op) { case SNMP_OP_SET: r = string_save (value, ctx, -1, &mon->filter); return r; case SNMP_OP_ROLLBACK: return SNMP_ERR_NOERROR; case SNMP_OP_COMMIT: return SNMP_ERR_NOERROR; default: ASSERT (0); return SNMP_ERR_GENERR; } break; /* Unknown OID */ default: ASSERT (0); return SNMP_ERR_NOSUCHNAME; } } int op_pcapentry (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 monitor *mon = NULL; switch (op) { case SNMP_OP_GETNEXT: mon = NEXT_OBJECT_INT (&monitors, &value->var, sub); if (mon == NULL) return SNMP_ERR_NOSUCHNAME; value->var.len = sub + 1; value->var.subs[sub] = mon->index; break; case SNMP_OP_GET: mon = FIND_OBJECT_INT (&monitors, &value->var, sub); if (mon == NULL) return SNMP_ERR_NOSUCHNAME; break; default: mon = FIND_OBJECT_INT (&monitors, &value->var, sub); break; }; /* Send configuration stuff off elsewhere */ switch (which) { case LEAF_pcapIndex: case LEAF_pcapDescr: case LEAF_pcapDevice: case LEAF_pcapFilter: return op_config (mon, ctx, value, sub, iidx, op); } if (op != SNMP_OP_GET && op != SNMP_OP_GETNEXT) return SNMP_ERR_NOT_WRITEABLE; switch (which) { case LEAF_pcapOctets: value->v.counter64 = mon->seen_octets; return SNMP_ERR_NOERROR; case LEAF_pcapPackets: value->v.counter64 = mon->seen_packets; return SNMP_ERR_NOERROR; default: ASSERT (0); return SNMP_ERR_NOSUCHNAME; }; } int op_pcap (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_pcapCount: value->v.integer = monitor_count; break; default: ASSERT(0); break; }; return SNMP_ERR_NOERROR; } /* ----------------------------------------------------------------------------- * MODULE */ /* Called, when the module is to be unloaded after it was successfully loaded */ static int module_fini (void) { struct monitor *mon; fprintf(stderr, "fini\n"); if (reg_index) or_unregister (reg_index); while ((mon = TAILQ_FIRST(&monitors)) != NULL) monitor_free (mon); return 0; } /* 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 0; } /* Module is started */ static void module_start (void) { struct monitor *mon; fprintf(stderr, "start\n"); reg_index = or_register (&oid_pcap, "The MIB for pcap data.", module); /* Start each monitor */ TAILQ_FOREACH (mon, &monitors, link) { monitor_start (mon); } } const struct snmp_module config = { .comment = "This module implements SNMP pcap monitoring of traffic", .init = module_init, .start = module_start, .fini = module_fini, .tree = pcap_ctree, .tree_size = pcap_CTREE_SIZE, };