/* * 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 #include "common/hash.h" #include "jails_tree.h" #include "jails_oid.h" #define SNAP_LEN 48 /* our module handle */ static struct lmodule *module; /* OIDs */ static const struct asn_oid oid_jails = OIDX_jails; /* the Object Resource registration index */ static u_int reg_index = 0; struct monitor { TAILQ_ENTRY(monitor) link; int refs; char *device; pcap_t *handle; void *watch; struct bpf_program filter; int filter_valid; }; TAILQ_HEAD(monitor_list, monitor); /* list of monitor structures */ static struct monitor_list monitors = TAILQ_HEAD_INITIALIZER (monitors); struct jaildat { uint32_t index; TAILQ_ENTRY(jaildat) link; int mark; /* Configuration */ char *host; char *path; struct sockaddr_in addr; /* Network monitor */ struct monitor *monitor; /* Stats gathered */ uint64_t in_octets; uint64_t in_packets; uint64_t out_octets; uint64_t out_packets; }; TAILQ_HEAD(jaildat_list, jaildat); /* list of jail structures */ static struct jaildat_list jaildats = TAILQ_HEAD_INITIALIZER (jaildats); /* number of if jail structures */ static u_int jaildat_count = 0; /* Hash of jail structures by id */ static hsh_t *jaildat_by_host = NULL; /* Hash of jail structures by address */ static hsh_t *jaildat_by_address = NULL; /* Timer for refreshing the jaildat info */ static void *timer_refresh = NULL; /* The monitor network filter */ static u_char *network_filter = NULL; /* ----------------------------------------------------------------------------- * HELPERS */ static void emsg(const char *format, ...) { va_list va; va_start (va, format); vsyslog (LOG_ERR, format, va); va_end (va); } typedef void* (*if_enumerator) (struct ifreq *ifr, void* data); static void* enumerate_ifs (if_enumerator func, void* data) { int sockfd = -1; struct ifconf ifc; struct ifreq *ifr = NULL; unsigned char *ptr; unsigned char *buf = NULL; void *result = NULL; ASSERT (func); ifc.ifc_len = 32768; buf = malloc (ifc.ifc_len); ifc.ifc_req = (struct ifreq*)buf; if (!buf) { emsg ("couldn't allocate buffer to list interfaces: out of memory"); goto cleanup; } sockfd = socket (AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { emsg ("couldn't create socket to list interfaces: %s", strerror (errno)); goto cleanup; } if (ioctl (sockfd, SIOCGIFCONF, &ifc) < 0) { emsg ("couldn't list interfaces: %s", strerror (errno)); goto cleanup; } #define IFR_SIZE(ifr) \ (max ((ifr)->ifr_addr.sa_len, sizeof((ifr)->ifr_addr)) + sizeof((ifr)->ifr_name)) for (ptr = buf; ptr < buf + ifc.ifc_len; ) { ifr = (struct ifreq*)ptr; ptr += IFR_SIZE (ifr); result = (func) (ifr, data); if (result) break; } cleanup: if (sockfd >= 0) close (sockfd); if (buf) free (buf); return result; } /* ----------------------------------------------------------------------------- * 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 process_ip4 (const struct ip4hdr *hdr, uint32_t octets) { struct sockaddr_in addr; struct jaildat *jail; ASSERT (hdr); /* Try incoming */ memset (&addr, 0, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_len = sizeof (addr); addr.sin_port = 0; memcpy (&addr.sin_addr, &hdr->dst, sizeof (addr.sin_addr)); jail = hsh_get (jaildat_by_address, &addr, addr.sin_len); if (jail) { jail->in_octets += octets; ++jail->in_packets; return; } /* Try outgoing */ memcpy (&addr.sin_addr, &hdr->src, sizeof (addr.sin_addr)); jail = hsh_get (jaildat_by_address, &addr, addr.sin_len); if (jail) { jail->out_octets += octets; ++jail->out_packets; return; } } static void process_ip6 (const struct ip6hdr *hdr, uint32_t octets) { struct sockaddr_in6 addr; struct jaildat *jail; ASSERT (hdr); /* Try incoming */ memset (&addr, 0, sizeof (addr)); addr.sin6_family = AF_INET6; addr.sin6_len = sizeof (addr); addr.sin6_port = 0; memcpy (&addr.sin6_addr, &hdr->dst, sizeof (addr.sin6_addr)); jail = hsh_get (jaildat_by_address, &addr, addr.sin6_len); if (jail) { jail->in_octets += octets; ++jail->in_packets; return; } /* Try outgoing */ memcpy (&addr.sin6_addr, &hdr->src, sizeof (addr.sin6_addr)); jail = hsh_get (jaildat_by_address, &addr, addr.sin6_len); if (jail) { jail->out_octets += octets; ++jail->out_packets; return; } } static void monitor_packet (u_char *data, const struct pcap_pkthdr *hdr, const u_char *bytes) { struct ethhdr *eth; int minlen, octets, type; /* Short packet, don't care */ if (hdr->len < sizeof (struct ethhdr)) return; eth = (struct ethhdr*)bytes; bytes += sizeof (struct ethhdr); octets = hdr->len - sizeof (struct ethhdr); type = ntohs (eth->type); /* IPv4 packet? */ if (type == 0x0800) { minlen = (sizeof (struct ethhdr) + sizeof (struct ip4hdr)); if (hdr->len >= minlen) { ASSERT (hdr->caplen >= minlen); process_ip4 ((const struct ip4hdr*)bytes, octets); } /* IPv6 packet? */ } else if (type == 0x86DD) { minlen = (sizeof (struct ethhdr) + sizeof (struct ip6hdr)); if (hdr->len >= minlen) { ASSERT (hdr->caplen >= minlen); process_ip6 ((const struct ip6hdr*)bytes, octets); } } } 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->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); TAILQ_REMOVE (&monitors, mon, link); free (mon); } static struct monitor* monitor_create (const char *device) { char errbuf[PCAP_ERRBUF_SIZE]; struct monitor* mon = NULL; int success = 0; int fd; ASSERT (device); ASSERT (network_filter); mon = calloc (1, sizeof (struct monitor)); if (!mon) { emsg ("couldn't allocate monitor: out of memory"); goto cleanup; } TAILQ_INSERT_TAIL(&monitors, mon, link); mon->device = strdup (device); mon->handle = pcap_open_live (mon->device, SNAP_LEN, 0, 100, errbuf); if (!mon->handle) { emsg ("couldn't open monitor on %s: %s", mon->device, errbuf); goto cleanup; } if (pcap_compile (mon->handle, &mon->filter, network_filter, 1, 0) < 0) { emsg ("couldn't compile monitor expression: %s", pcap_geterr (mon->handle)); goto cleanup; } mon->filter_valid = 1; if (pcap_setfilter (mon->handle, &mon->filter) < 0) { emsg ("couldn't setup monitor expression: %s", pcap_geterr (mon->handle)); goto cleanup; } if (pcap_setnonblock (mon->handle, 1, errbuf) < 0) { emsg ("couldn't set monitor in non-block mode: %s", errbuf); goto cleanup; } fd = pcap_get_selectable_fd (mon->handle); if (fd < 0) { emsg ("couldn't get selectable monitor: %s", pcap_geterr (mon->handle)); goto cleanup; } mon->watch = fd_select (fd, monitor_io, mon, module); if (!mon->watch) { emsg ("couldn't listen to monitor: %s", strerror (errno)); goto cleanup; } success = 1; cleanup: if (!success && mon) { monitor_free (mon); mon = NULL; } return mon; } static void monitor_ref (struct monitor *mon) { ASSERT (mon); ASSERT (mon->refs >= 0); ++mon->refs; } static void monitor_unref (struct monitor *mon) { ASSERT (mon); --mon->refs; ASSERT (mon->refs >= 0); if (mon->refs == 0) monitor_free (mon); } static struct monitor* monitor_for_device (const char *device) { struct monitor *mon; ASSERT (device); /* Mark and prepare for sweep below */ TAILQ_FOREACH (mon, &monitors, link) { ASSERT (mon->device); if (strcmp (mon->device, device) == 0) return mon; } return monitor_create (device); } static void* monitor_addr_enumerator (struct ifreq *ifr, void *data) { struct sockaddr *addr = (struct sockaddr*)data; int match = 0; ASSERT (ifr); ASSERT (addr); switch (ifr->ifr_addr.sa_family) { case AF_INET: if (addr->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in*)addr; if (memcmp (&(sin->sin_addr), &(((struct sockaddr_in*)&ifr->ifr_addr)->sin_addr), sizeof (sin->sin_addr)) == 0) match = 1; } break; case AF_INET6: if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)addr; if (memcmp (&sin6->sin6_addr, &(((struct sockaddr_in6*)&ifr->ifr_addr)->sin6_addr), sizeof (sin6->sin6_addr)) == 0) match = 1; } break; default: break; }; if (!match) return NULL; return monitor_for_device (ifr->ifr_name); } static struct monitor* monitor_for_address (struct sockaddr *addr) { return (struct monitor*)enumerate_ifs (monitor_addr_enumerator, addr); } static int monitor_test_filter (const char *filter) { struct bpf_program bpf; int ret; ret = pcap_compile_nopcap (SNAP_LEN, DLT_EN10MB, &bpf, (char*)filter, 1, 0); if (ret >= 0) pcap_freecode (&bpf); return ret; } /* ----------------------------------------------------------------------------- * JAIL LOOKUPS */ static void jail_free (struct jaildat *jail) { ASSERT (jail); if (jail->host) free (jail->host); jail->host = NULL; if (jail->path) free (jail->path); jail->path = NULL; if (jail->monitor) monitor_unref (jail->monitor); jail->monitor = NULL; if (jail->index) TAILQ_REMOVE (&jaildats, jail, link); free (jail); } static int jail_update (struct jaildat *jail, const char *host, const char *path, struct in_addr *addr) { struct monitor *mon; char *dup; ASSERT (jail); if (!host) host = ""; if (!jail->host || strcmp (jail->host, host) != 0) { dup = strdup (host); if (!dup) return -1; if (jail->host) { hsh_rem (jaildat_by_host, jail->host, HSH_KEY_STRING); free (jail->host); } jail->host = dup; hsh_set (jaildat_by_host, jail->host, HSH_KEY_STRING, jail); } if (!path) path = ""; if (!jail->path || strcmp (jail->path, path) != 0) { dup = strdup (path); if (!dup) return -1; if (jail->path) free (jail->path); jail->path = dup; } if (memcmp (&jail->addr.sin_addr, addr, sizeof (jail->addr.sin_addr)) != 0) { if (jail->addr.sin_len) hsh_rem (jaildat_by_address, &(jail->addr), jail->addr.sin_len); jail->addr.sin_family = AF_INET; jail->addr.sin_len = sizeof (jail->addr); jail->addr.sin_port = 0; memcpy (&jail->addr.sin_addr, addr, sizeof (jail->addr.sin_addr)); hsh_set (jaildat_by_address, &(jail->addr), jail->addr.sin_len, jail); mon = monitor_for_address ((struct sockaddr*)&jail->addr); if (mon && mon != jail->monitor) { monitor_ref (mon); if (jail->monitor) monitor_unref (jail->monitor); jail->monitor = mon; } } return 0; } static struct jaildat* jail_alloc (const char *host, const char *path, struct in_addr *addr) { struct jaildat *jail; jail = (struct jaildat*)calloc (1, sizeof (struct jaildat)); if (!jail) return NULL; if (jail_update (jail, host, path, addr) < 0) { jail_free (jail); return NULL; } jaildat_count++; jail->index = jaildat_count; INSERT_OBJECT_INT (jail, &jaildats); return jail; } static void jail_refresh_all (void* unused) { struct xprison *sxp, *xp; struct in_addr in; struct jaildat *jail, *tmp; size_t i, len; /* Get the length of the list */ if (sysctlbyname ("security.jail.list", NULL, &len, NULL, 0) == -1) { emsg ("couldn't lookup jail list: %s", strerror (errno)); return; } /* Retrieve actual data */ for (i = 0; i < 4; i++) { if (len <= 0) return; sxp = xp = malloc (len); if (sxp == NULL) { emsg ("out of memory"); return; } if (sysctlbyname ("security.jail.list", xp, &len, NULL, 0) == -1) { free (sxp); sxp = NULL; if (errno == ENOMEM) continue; } if (sxp == NULL) { emsg ("couldn't retrieve jail list: %s", strerror (errno)); return; } break; } /* Make sure its kosher */ if (len < sizeof (*xp) || len % sizeof (*xp) || xp->pr_version != XPRISON_VERSION) { emsg ("kernel and userland out of sync"); free (sxp); return; } /* Mark and prepare for sweep below */ TAILQ_FOREACH (jail, &jaildats, link) jail->mark = 1; /* Allocate new jails, and update old ones */ for (i = 0; i < len / sizeof (*xp); ++i) { in.s_addr = ntohl (xp->pr_ip); jail = hsh_get (jaildat_by_host, xp->pr_host, HSH_KEY_STRING); if (!jail) jail = jail_alloc (xp->pr_host, xp->pr_path, &in); else jail_update (jail, xp->pr_host, xp->pr_path, &in); jail->mark = 0; xp++; } /* Sweep any jails that are no longer */ TAILQ_FOREACH_SAFE (jail, &jaildats, link, tmp) { if (jail->mark) jail_free (jail); } free (sxp); } /* ----------------------------------------------------------------------------- * CALLBACKS/CONFIG */ int op_jailconfig (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 r = SNMP_ERR_NOERROR; switch (which) { case LEAF_jailNetworkFilter: if (op == SNMP_OP_GET) return string_get (value, network_filter, -1); /* Remainder only at initialization */ if (community != COMM_INITIALIZE) return SNMP_ERR_NOT_WRITEABLE; switch (op) { case SNMP_OP_SET: if ((r = string_save (value, ctx, -1, &network_filter)) == SNMP_ERR_NOERROR) { if (monitor_test_filter (network_filter) < 0) r = SNMP_ERR_GENERR; } if (r != SNMP_ERR_NOERROR) string_rollback (ctx, &network_filter); break; case SNMP_OP_COMMIT: string_commit (ctx); break; case SNMP_OP_ROLLBACK: string_rollback (ctx, &network_filter); break; default: ASSERT(0); break; }; return r; default: break; }; ASSERT(0); return -1; } int op_jailentry (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 jaildat *jail = NULL; switch (op) { case SNMP_OP_GETNEXT: jail = NEXT_OBJECT_INT (&jaildats, &value->var, sub); if (jail == NULL) return SNMP_ERR_NOSUCHNAME; value->var.len = sub + 1; value->var.subs[sub] = jail->index; break; case SNMP_OP_GET: jail = FIND_OBJECT_INT (&jaildats, &value->var, sub); if (jail == NULL) return SNMP_ERR_NOSUCHNAME; break; default: jail = FIND_OBJECT_INT (&jaildats, &value->var, sub); break; }; if (op != SNMP_OP_GET && op != SNMP_OP_GETNEXT) return SNMP_ERR_NOT_WRITEABLE; switch (which) { case LEAF_jailIndex: value->v.integer = jail->index; return SNMP_ERR_NOERROR; case LEAF_jailHost: return string_get (value, jail->host, -1); case LEAF_jailInOctets: value->v.uint32 = jail->in_octets; return SNMP_ERR_NOERROR; case LEAF_jailInPackets: value->v.uint32 = jail->in_packets; return SNMP_ERR_NOERROR; case LEAF_jailOutOctets: value->v.uint32 = jail->out_octets; return SNMP_ERR_NOERROR; case LEAF_jailOutPackets: value->v.uint32 = jail->out_packets; return SNMP_ERR_NOERROR; default: ASSERT (0); return SNMP_ERR_NOSUCHNAME; }; } int op_jail (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_jailCount: value->v.integer = jaildat_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 jaildat *jail; struct monitor *mon; if (reg_index) or_unregister (reg_index); if (network_filter) free (network_filter); network_filter = NULL; if (jaildat_by_address) hsh_free (jaildat_by_address); jaildat_by_address = NULL; if (jaildat_by_host) hsh_free (jaildat_by_host); jaildat_by_host = NULL; while ((jail = TAILQ_FIRST(&jaildats)) != NULL) jail_free (jail); while ((mon = TAILQ_FIRST(&monitors)) != NULL) monitor_free (mon); if (timer_refresh) timer_stop (timer_refresh); timer_refresh = NULL; return 0; } /* the initialisation function */ static int module_init (struct lmodule *mod, int argc, char *argv[]) { int success = 0; module = mod; if (argc != 0) { syslog (LOG_ERR, "bad number of arguments for %s", __func__); return EINVAL; } network_filter = strdup ("ip or ip6"); if (!network_filter) goto cleanup; jaildat_by_host = hsh_create (); if (!jaildat_by_host) goto cleanup; jaildat_by_address = hsh_create (); if (!jaildat_by_address) goto cleanup; success = 1; cleanup: if (!success) { emsg ("error initializing: out of memory"); module_fini (); return ENOMEM; } return 0; } /* Module is started */ static void module_start (void) { reg_index = or_register (&oid_jails, "The MIB for jail interface data.", module); jail_refresh_all (NULL); timer_refresh = timer_start_repeat (500, 500, jail_refresh_all, NULL, module); if (!timer_refresh) emsg ("couldn't setup timer to refresh jails"); } const struct snmp_module config = { .comment = "This module implements SNMP monitoring of jails", .init = module_init, .start = module_start, .fini = module_fini, .tree = jails_ctree, .tree_size = jails_CTREE_SIZE, };