/* * This program is is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2 of the * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ /** * $Id$ * @file pcap.c * @brief Wrappers around libpcap functions * * @author Arran Cudbard-Bell * @copyright 2013 Arran Cudbard-Bell */ #ifdef HAVE_LIBPCAP #include #include const FR_NAME_NUMBER pcap_types[] = { { "interface", PCAP_INTERFACE_IN }, { "file", PCAP_FILE_IN }, { "stdio", PCAP_STDIO_IN }, { "interface", PCAP_INTERFACE_OUT }, { "file", PCAP_FILE_OUT }, { "stdio", PCAP_STDIO_OUT }, { NULL, 0} }; /** Talloc destructor to free pcap resources associated with a handle. * * @param pcap to free. * @return 0 */ static int _free_pcap(fr_pcap_t *pcap) { switch (pcap->type) { case PCAP_INTERFACE_IN: case PCAP_INTERFACE_OUT: case PCAP_FILE_IN: case PCAP_STDIO_IN: if (pcap->handle) { pcap_close(pcap->handle); if (pcap->fd > 0) { close(pcap->fd); } } break; case PCAP_FILE_OUT: case PCAP_STDIO_OUT: if (pcap->dumper) { pcap_dump_flush(pcap->dumper); pcap_dump_close(pcap->dumper); } break; case PCAP_INVALID: break; } return 0; } /** Get data link from pcap_if_t * * libpcap requires an open pcap handle to get data_link type * unfortunately when we're trying to find useful interfaces * this is too late. * * @param errbuff Error message. * @param dev to get link layer for. * @return datalink layer or -1 on failure. */ int fr_pcap_if_link_layer(char *errbuff, pcap_if_t *dev) { pcap_t *pcap; int data_link; pcap = pcap_open_live(dev->name, 0, 0, 0, errbuff); if (!pcap) return -1; data_link = pcap_datalink(pcap); pcap_close(pcap); return data_link; } /** Initialise a pcap handle abstraction * * @param ctx talloc TALLOC_CTX to allocate handle in. * @param name of interface or file to open. * @param type of handle to initialise. * @return new handle or NULL on error. */ fr_pcap_t *fr_pcap_init(TALLOC_CTX *ctx, char const *name, fr_pcap_type_t type) { fr_pcap_t *this = talloc_zero(ctx, fr_pcap_t); if (!this) { return NULL; } talloc_set_destructor(this, _free_pcap); this->name = talloc_typed_strdup(this, name); this->type = type; this->link_layer = -1; return this; } /** Open a PCAP handle abstraction * * This opens interfaces for capture or injection, or files/streams for reading/writing. * @param pcap created with fr_pcap_init. * @return 0 on success, -1 on error. */ int fr_pcap_open(fr_pcap_t *pcap) { switch (pcap->type) { case PCAP_INTERFACE_OUT: case PCAP_INTERFACE_IN: { #if defined(HAVE_PCAP_CREATE) && defined(HAVE_PCAP_ACTIVATE) pcap->handle = pcap_create(pcap->name, pcap->errbuf); if (!pcap->handle) { fr_strerror_printf("%s", pcap->errbuf); return -1; } if (pcap_set_snaplen(pcap->handle, SNAPLEN) != 0) { create_error: fr_strerror_printf("%s", pcap_geterr(pcap->handle)); pcap_close(pcap->handle); pcap->handle = NULL; return -1; } if (pcap_set_timeout(pcap->handle, PCAP_NONBLOCK_TIMEOUT) != 0) { goto create_error; } if (pcap_set_promisc(pcap->handle, pcap->promiscuous) != 0) { goto create_error; } if (pcap_set_buffer_size(pcap->handle, SNAPLEN * (pcap->buffer_pkts ? pcap->buffer_pkts : PCAP_BUFFER_DEFAULT)) != 0) { goto create_error; } if (pcap_activate(pcap->handle) != 0) { goto create_error; } #else /* * Alternative functions for libpcap < 1.0 */ pcap->handle = pcap_open_live(pcap->name, SNAPLEN, pcap->promiscuous, PCAP_NONBLOCK_TIMEOUT, pcap->errbuf); if (!pcap->handle) { fr_strerror_printf("%s", pcap->errbuf); return -1; } #endif /* * Despite accepting an errbuff, pcap_setnonblock doesn't seem to write * error message there in newer versions. */ if (pcap_setnonblock(pcap->handle, true, pcap->errbuf) != 0) { fr_strerror_printf("%s", *pcap->errbuf != '\0' ? pcap->errbuf : pcap_geterr(pcap->handle)); pcap_close(pcap->handle); pcap->handle = NULL; return -1; } pcap->fd = pcap_get_selectable_fd(pcap->handle); pcap->link_layer = pcap_datalink(pcap->handle); #ifndef __linux__ { int value = 1; if (ioctl(pcap->fd, BIOCIMMEDIATE, &value) < 0) { fr_strerror_printf("Failed setting BIOCIMMEDIATE: %s", fr_syserror(errno)); } } #endif } break; case PCAP_FILE_IN: pcap->handle = pcap_open_offline(pcap->name, pcap->errbuf); if (!pcap->handle) { fr_strerror_printf("%s", pcap->errbuf); return -1; } pcap->fd = pcap_get_selectable_fd(pcap->handle); pcap->link_layer = pcap_datalink(pcap->handle); break; case PCAP_FILE_OUT: if (pcap->link_layer < 0) { pcap->link_layer = DLT_EN10MB; } pcap->handle = pcap_open_dead(pcap->link_layer, SNAPLEN); if (!pcap->handle) { fr_strerror_printf("Unknown error occurred opening dead PCAP handle"); return -1; } pcap->dumper = pcap_dump_open(pcap->handle, pcap->name); if (!pcap->dumper) { fr_strerror_printf("%s", pcap_geterr(pcap->handle)); return -1; } break; #ifdef HAVE_PCAP_FOPEN_OFFLINE case PCAP_STDIO_IN: pcap->handle = pcap_fopen_offline(stdin, pcap->errbuf); if (!pcap->handle) { fr_strerror_printf("%s", pcap->errbuf); return -1; } pcap->fd = pcap_get_selectable_fd(pcap->handle); pcap->link_layer = pcap_datalink(pcap->handle); break; #else case PCAP_STDIO_IN: fr_strerror_printf("This version of libpcap does not support reading pcap data from streams"); return -1; #endif #ifdef HAVE_PCAP_DUMP_FOPEN case PCAP_STDIO_OUT: pcap->handle = pcap_open_dead(DLT_EN10MB, SNAPLEN); pcap->dumper = pcap_dump_fopen(pcap->handle, stdout); if (!pcap->dumper) { fr_strerror_printf("%s", pcap_geterr(pcap->handle)); return -1; } break; #else case PCAP_STDIO_OUT: fr_strerror_printf("This version of libpcap does not support writing pcap data to streams"); return -1; #endif case PCAP_INVALID: default: fr_assert(0); fr_strerror_printf("Bad handle type (%i)", pcap->type); return -1; } return 0; } /** Apply capture filter to an interface * * @param pcap handle to apply filter to. * @param expression PCAP expression to use as a filter. * @return 0 on success, 1 can't apply to interface, -1 on error. */ int fr_pcap_apply_filter(fr_pcap_t *pcap, char const *expression) { bpf_u_int32 mask = 0; /* Our netmask */ bpf_u_int32 net = 0; /* Our IP */ struct bpf_program fp; /* * nflog devices are in the set of devices selected by default. * Unfortunately there's a bug in all released version of libpcap (as of 2/1/2014) * which triggers an abort if pcap_setfilter is called on an nflog interface. * * See here: * https://github.com/the-tcpdump-group/libpcap/commit/676cf8a61ed240d0a86d471ef419f45ba35dba80 */ #ifdef DLT_NFLOG if (pcap->link_layer == DLT_NFLOG) { fr_strerror_printf("NFLOG link-layer type filtering not implemented"); return 1; } #endif if (pcap->type == PCAP_INTERFACE_IN) { if (pcap_lookupnet(pcap->name, &net, &mask, pcap->errbuf) < 0) { fr_strerror_printf("Failed getting IP for interface \"%s\", using defaults: %s", pcap->name, pcap->errbuf); } } if (pcap_compile(pcap->handle, &fp, expression, 0, net) < 0) { fr_strerror_printf("%s", pcap_geterr(pcap->handle)); return -1; } if (pcap_setfilter(pcap->handle, &fp) < 0) { fr_strerror_printf("%s", pcap_geterr(pcap->handle)); return -1; } return 0; } char *fr_pcap_device_names(TALLOC_CTX *ctx, fr_pcap_t *pcap, char c) { fr_pcap_t *pcap_p; char *buff, *p; size_t len = 0, left = 0, wrote; if (!pcap) { goto null; } for (pcap_p = pcap; pcap_p; pcap_p = pcap_p->next) { len += talloc_array_length(pcap_p->name); // Talloc array length includes the \0 } if (!len) { null: return talloc_zero_array(ctx, char, 1); } left = len + 1; buff = p = talloc_zero_array(ctx, char, left); for (pcap_p = pcap; pcap_p; pcap_p = pcap_p->next) { wrote = snprintf(p, left, "%s%c", pcap_p->name, c); left -= wrote; p += wrote; } buff[len - 1] = '\0'; return buff; } /** Check whether fr_pcap_link_layer_offset can process a link_layer * * @param link_layer to check. * @return true if supported, else false. */ bool fr_pcap_link_layer_supported(int link_layer) { switch (link_layer) { case DLT_EN10MB: case DLT_RAW: case DLT_NULL: case DLT_LOOP: #ifdef DLT_LINUX_SLL case DLT_LINUX_SLL: #endif case DLT_PFLOG: return true; default: return false; } } /** Returns the length of the link layer header * * Libpcap does not include a decoding function to skip the L2 header, but it does * at least inform us of the type. * * Unfortunately some headers are of variable length (like ethernet), so additional * decoding logic is required. * * @note No header data is returned, this is only meant to be used to determine how * data to consume before attempting to parse the IP header. * * @param data start of packet data. * @param len caplen. * @param link_layer value returned from pcap_linktype. * @return the length of the header, or -1 on error. */ ssize_t fr_pcap_link_layer_offset(uint8_t const *data, size_t len, int link_layer) { uint8_t const *p = data; switch (link_layer) { case DLT_RAW: break; case DLT_NULL: case DLT_LOOP: p += 4; if (((size_t)(p - data)) > len) { ood: fr_strerror_printf("Out of data, needed %zu bytes, have %zu bytes", (size_t)(p - data), len); return -1; } break; case DLT_EN10MB: { uint16_t ether_type; /* Ethernet type */ int i; p += 12; /* SRC/DST Mac-Addresses */ if (((size_t)(p - data)) > len) { goto ood; } for (i = 0; i < 3; i++) { ether_type = ntohs(*((uint16_t const *) p)); switch (ether_type) { /* * There are a number of devices out there which * double tag with 0x8100 *sigh* */ case 0x8100: /* CVLAN */ case 0x9100: /* SVLAN */ case 0x9200: /* SVLAN */ case 0x9300: /* SVLAN */ p += 4; if (((size_t)(p - data)) > len) { goto ood; } break; default: p += 2; if (((size_t)(p - data)) > len) { goto ood; } goto done; } } fr_strerror_printf("Exceeded maximum level of VLAN tag nesting (2)"); return -1; } #ifdef DLT_LINUX_SLL case DLT_LINUX_SLL: p += 16; if (((size_t)(p - data)) > len) { goto ood; } break; #endif case DLT_PFLOG: p += 28; if (((size_t)(p - data)) > len) { goto ood; } break; default: fr_strerror_printf("Unsupported link layer type %i", link_layer); } done: return p - data; } #endif /* HAVE_LIBPCAP */