diff options
Diffstat (limited to 'nsock/src/nsock_pcap.c')
-rw-r--r-- | nsock/src/nsock_pcap.c | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/nsock/src/nsock_pcap.c b/nsock/src/nsock_pcap.c new file mode 100644 index 0000000..c9e43d4 --- /dev/null +++ b/nsock/src/nsock_pcap.c @@ -0,0 +1,516 @@ +/*************************************************************************** + * nsock_pcap.c -- This contains pcap operations functions from * + * the nsock parallel socket event library * + * * + ***********************IMPORTANT NSOCK LICENSE TERMS*********************** + * + * The nsock parallel socket event library is (C) 1999-2023 Nmap Software LLC + * This library is free software; you may redistribute and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; Version 2. This guarantees your right to use, modify, and + * redistribute this software under certain conditions. If this license is + * unacceptable to you, Nmap Software LLC may be willing to sell alternative + * licenses (contact sales@nmap.com ). + * + * As a special exception to the GPL terms, Nmap Software LLC grants permission + * to link the code of this program with any version of the OpenSSL library + * which is distributed under a license identical to that listed in the included + * docs/licenses/OpenSSL.txt file, and distribute linked combinations including + * the two. You must obey the GNU GPL in all respects for all of the code used + * other than OpenSSL. If you modify this file, you may extend this exception to + * your version of the file, but you are not obligated to do so. + * + * If you received these files with a written license agreement stating terms + * other than the (GPL) terms above, then that alternative license agreement + * takes precedence over this comment. + * + * Source is provided to this software because we believe users have a right to + * know exactly what a program is going to do before they run it. This also + * allows you to audit the software for security holes. + * + * Source code also allows you to port Nmap to new platforms, fix bugs, and add + * new features. You are highly encouraged to send your changes to the + * dev@nmap.org mailing list for possible incorporation into the main + * distribution. By sending these changes to Fyodor or one of the Insecure.Org + * development mailing lists, or checking them into the Nmap source code + * repository, it is understood (unless you specify otherwise) that you are + * offering the Nmap Project (Nmap Software LLC) the unlimited, non-exclusive + * right to reuse, modify, and relicense the code. Nmap will always be available + * Open Source, but this is important because the inability to relicense code + * has caused devastating problems for other Free Software projects (such as KDE + * and NASM). We also occasionally relicense the code to third parties as + * discussed above. If you wish to specify special license conditions of your + * contributions, just say so when you send them. + * + * 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 v2.0 for more + * details (http://www.gnu.org/licenses/gpl-2.0.html). + * + ***************************************************************************/ + +/* $Id$ */ + +#include "nsock.h" +#include "nsock_internal.h" +#include "nsock_log.h" + +#include <limits.h> +#if HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#if HAVE_NET_BPF_H +#ifdef _AIX +/* Prevent bpf.h from redefining the DLT_ values to their IFT_ values. (See + * similar comment in libpcap/pcap-bpf.c.) */ +#undef _AIX +#include <net/bpf.h> +#define _AIX +#else +#include <net/bpf.h> +#endif +#endif + +#include "nsock_pcap.h" + +extern struct timeval nsock_tod; + +#if HAVE_PCAP + +#ifndef PCAP_NETMASK_UNKNOWN +/* libpcap before 1.1.1 (e.g. WinPcap) doesn't handle this specially, so just use 0 netmask */ +#define PCAP_NETMASK_UNKNOWN 0 +#endif + +#define PCAP_OPEN_MAX_RETRIES 3 + +#define PCAP_FAILURE_EXPL_MESSAGE \ + "There are several possible reasons for this, " \ + "depending on your operating system:\n" \ + "LINUX: If you are getting Socket type not supported, " \ + "try modprobe af_packet or recompile your kernel with PACKET enabled.\n" \ + "*BSD: If you are getting device not configured, you need to recompile " \ + "your kernel with Berkeley Packet Filter support." \ + "If you are getting No such file or directory, try creating the device " \ + "(eg cd /dev; MAKEDEV <device>; or use mknod).\n" \ + "*WINDOWS: Nmap only supports ethernet interfaces on Windows for most " \ + "operations because Microsoft disabled raw sockets as of Windows XP SP2. " \ + "Depending on the reason for this error, it is possible that the " \ + "--unprivileged command-line argument will help.\n" \ + "SOLARIS: If you are trying to scan localhost and getting "\ + "'/dev/lo0: No such file or directory', complain to Sun. "\ + "I don't think Solaris can support advanced localhost scans. "\ + "You can probably use \"-PN -sT localhost\" though.\n\n" + + +static int nsock_pcap_set_filter(struct npool *nsp, pcap_t *pt, const char *device, + const char *bpf) { + struct bpf_program fcode; + int rc; + + rc = pcap_compile(pt, &fcode, (char *)bpf, 1, PCAP_NETMASK_UNKNOWN); + if (rc) { + nsock_log_error("Error compiling pcap filter: %s", pcap_geterr(pt)); + return rc; + } + + rc = pcap_setfilter(pt, &fcode); + if (rc) { + nsock_log_error("Failed to set the pcap filter: %s", pcap_geterr(pt)); + return rc; + } + + pcap_freecode(&fcode); + return 0; +} + +static int nsock_pcap_get_l3_offset(pcap_t *pt, int *dl) { + int datalink; + unsigned int offset = 0; + + /* New packet capture device, need to recompute offset */ + if ((datalink = pcap_datalink(pt)) < 0) + fatal("Cannot obtain datalink information: %s", pcap_geterr(pt)); + + /* XXX NOTE: + * if a new offset ever exceeds the current max (24), + * adjust MAX_LINK_HEADERSZ in libnetutil/netutil.h + */ + switch (datalink) { + case DLT_EN10MB: offset = 14; break; + case DLT_IEEE802: offset = 22; break; + #ifdef __amigaos__ + case DLT_MIAMI: offset = 16; break; + #endif + #ifdef DLT_LOOP + case DLT_LOOP: + #endif + case DLT_NULL: offset = 4; break; + + case DLT_SLIP: + #ifdef DLT_SLIP_BSDOS + case DLT_SLIP_BSDOS: + #endif + #if (FREEBSD || OPENBSD || NETBSD || BSDI || MACOSX) + offset = 16;break; + #else + offset = 24;break; /* Anyone use this??? */ + #endif + + case DLT_PPP: + #ifdef DLT_PPP_BSDOS + case DLT_PPP_BSDOS: + #endif + #ifdef DLT_PPP_SERIAL + case DLT_PPP_SERIAL: + #endif + #ifdef DLT_PPP_ETHER + case DLT_PPP_ETHER: + #endif + #if (FREEBSD || OPENBSD || NETBSD || BSDI || MACOSX) + offset = 4;break; + #else + #ifdef SOLARIS + offset = 8;break; + #else + offset = 24;break; /* Anyone use this? */ + #endif /* ifdef solaris */ + #endif /* if freebsd || openbsd || netbsd || bsdi */ + #ifdef DLT_RAW + case DLT_RAW: offset = 0; break; + #endif /* DLT_RAW */ + case DLT_FDDI: offset = 21; break; + #ifdef DLT_ENC + case DLT_ENC: offset = 12; break; + #endif /* DLT_ENC */ + #ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: offset = 16; break; + #endif + #ifdef DLT_IPNET + case DLT_IPNET: offset = 24; break; + #endif /* DLT_IPNET */ + + default: /* Sorry, link type is unknown. */ + fatal("Unknown datalink type %d.\n", datalink); + } + if (dl) + *dl = datalink; + return (offset); +} + +/* Convert new nsiod to pcap descriptor. Other parameters have + * the same meaning as for pcap_open_live in pcap(3). + * device : pcap-style device name + * snaplen : size of packet to be copied to handler + * promisc : whether to open device in promiscuous mode + * bpf_fmt : berkeley filter + * return value: NULL if everything was okay, or error string + * if error occurred. */ +int nsock_pcap_open(nsock_pool nsp, nsock_iod nsiod, const char *pcap_device, + int snaplen, int promisc, const char *bpf_fmt, ...) { + struct niod *nsi = (struct niod *)nsiod; + struct npool *ms = (struct npool *)nsp; + mspcap *mp = (mspcap *)nsi->pcap; + char errbuf[PCAP_ERRBUF_SIZE]; + char bpf[4096]; + va_list ap; + int failed, datalink; + int rc; + +#ifdef PCAP_CAN_DO_SELECT +#if PCAP_BSD_SELECT_HACK + /* MacOsX reports error if to_ms is too big (like INT_MAX) with error + * FAILED. Reported error: BIOCSRTIMEOUT: Invalid argument + * INT_MAX/6 (=357913941) seems to be working... */ + int to_ms = 357913941; +#else + int to_ms = 200; +#endif /* PCAP_BSD_SELECT_HACK */ + +#else + int to_ms = 1; +#endif + + gettimeofday(&nsock_tod, NULL); + + if (mp) { + nsock_log_error("This nsi already has pcap device opened"); + return -1; + } + + mp = (mspcap *)safe_zalloc(sizeof(mspcap)); + nsi->pcap = (void *)mp; + + va_start(ap, bpf_fmt); + rc = Vsnprintf(bpf, sizeof(bpf), bpf_fmt, ap); + va_end(ap); + + if (rc >= (int)sizeof(bpf)) { + nsock_log_error("Too-large bpf filter argument"); + return -1; + } + + nsock_log_info("PCAP requested on device '%s' with berkeley filter '%s' " + "(promisc=%i snaplen=%i to_ms=%i) (IOD #%li)", + pcap_device,bpf, promisc, snaplen, to_ms, nsi->id); + +#ifdef __amigaos__ + // Amiga doesn't have pcap_create + // TODO: Does Nmap still work on Amiga? + mp->pt = pcap_open_live(pcap_device, snaplen, promisc, to_ms, errbuf); + if (!mp->pt) { + nsock_log_error("pcap_open_live(%s, %d, %d, %d) failed with error: %s", + pcap_device, snaplen, promisc, to_ms, errbuf); + nsock_log_error(PCAP_FAILURE_EXPL_MESSAGE); + nsock_log_error("Can't open pcap! Are you root?"); + return -1; + } +#else + mp->pt = pcap_create(pcap_device, errbuf); + if (!mp->pt) { + nsock_log_error("pcap_create(%s) failed with error: %s", pcap_device, errbuf); + nsock_log_error(PCAP_FAILURE_EXPL_MESSAGE); + nsock_log_error("Can't open pcap! Are you root?"); + return -1; + } + +#define MY_PCAP_SET(func, p_t, val) do {\ + failed = func(p_t, val);\ + if (failed) {\ + nsock_log_error(#func "(%d) FAILED: %d.", val, failed);\ + pcap_close(p_t);\ + return -1;\ + }\ +} while(0); + + MY_PCAP_SET(pcap_set_snaplen, mp->pt, snaplen); + MY_PCAP_SET(pcap_set_promisc, mp->pt, promisc); + MY_PCAP_SET(pcap_set_timeout, mp->pt, to_ms); +#ifdef HAVE_PCAP_SET_IMMEDIATE_MODE + MY_PCAP_SET(pcap_set_immediate_mode, mp->pt, 1); +#endif + + failed = pcap_activate(mp->pt); + if (failed < 0) { + // PCAP error + nsock_log_error("pcap_activate(%s) FAILED: %s.", pcap_device, pcap_geterr(mp->pt)); + pcap_close(mp->pt); + mp->pt = NULL; + return -1; + } + else if (failed > 0) { + // PCAP warning, report but assume it'll still work + nsock_log_error("pcap_activate(%s) WARNING: %s.", pcap_device, pcap_geterr(mp->pt)); + } +#endif /* not __amigaos__ */ + + rc = nsock_pcap_set_filter(ms, mp->pt, pcap_device, bpf); + if (rc) + return rc; + + mp->l3_offset = nsock_pcap_get_l3_offset(mp->pt, &datalink); + mp->snaplen = snaplen; + mp->datalink = datalink; + mp->pcap_device = strdup(pcap_device); +#ifdef PCAP_CAN_DO_SELECT + mp->pcap_desc = pcap_get_selectable_fd(mp->pt); +#else + mp->pcap_desc = -1; +#endif + mp->readsd_count = 0; + +#ifndef HAVE_PCAP_SET_IMMEDIATE_MODE + /* This is already handled by pcap_set_immediate_mode if available */ +#ifdef BIOCIMMEDIATE + /* Without setting this ioctl, some systems (BSDs, though it depends on the + * release) will buffer packets in non-blocking mode and only return them in a + * bunch when the buffer is full. Setting the ioctl makes each one be + * delivered immediately. This is how Linux works by default. See the comments + * surrounding the setting of BIOCIMMEDIATE in libpcap/pcap-bpf.c. */ + if (mp->pcap_desc != -1) { + int immediate = 1; + + if (ioctl(mp->pcap_desc, BIOCIMMEDIATE, &immediate) < 0) + fatal("Cannot set BIOCIMMEDIATE on pcap descriptor"); + } +#elif defined WIN32 + /* We want any responses back ASAP */ + pcap_setmintocopy(mp->pt, 0); +#endif +#endif + + /* Set device non-blocking */ + rc = pcap_setnonblock(mp->pt, 1, errbuf); + if (rc) { + + /* I can't do select() on pcap! + * blocking + no_select is fatal */ +#ifndef PCAP_BSD_SELECT_HACK + if (mp->pcap_desc < 0) +#endif + { + nsock_log_error("Failed to set pcap descriptor on device %s " + "to nonblocking mode: %s", pcap_device, errbuf); + return -1; + } + /* in other case, we can accept blocking pcap */ + nsock_log_info("Failed to set pcap descriptor on device %s " + "to nonblocking state: %s", pcap_device, errbuf); + } + + if (NsockLogLevel <= NSOCK_LOG_INFO) { + #if PCAP_BSD_SELECT_HACK + int bsd_select_hack = 1; + #else + int bsd_select_hack = 0; + #endif + + #if PCAP_RECV_TIMEVAL_VALID + int recv_timeval_valid = 1; + #else + int recv_timeval_valid = 0; + #endif + + nsock_log_info("PCAP created successfully on device '%s' " + "(pcap_desc=%i bsd_hack=%i to_valid=%i l3_offset=%i) (IOD #%li)", + pcap_device, mp->pcap_desc, bsd_select_hack, + recv_timeval_valid, mp->l3_offset, nsi->id); + } + return 0; +} + +/* Requests exactly one packet to be captured. */ +nsock_event_id nsock_pcap_read_packet(nsock_pool nsp, nsock_iod nsiod, + nsock_ev_handler handler, + int timeout_msecs, void *userdata) { + struct niod *nsi = (struct niod *)nsiod; + struct npool *ms = (struct npool *)nsp; + struct nevent *nse; + + nse = event_new(ms, NSE_TYPE_PCAP_READ, nsi, timeout_msecs, handler, userdata); + assert(nse); + + nsock_log_info("Pcap read request from IOD #%li EID %li", nsi->id, nse->id); + + nsock_pool_add_event(ms, nse); + + return nse->id; +} + +/* Remember that pcap descriptor is in nonblocking state. */ +int do_actual_pcap_read(struct nevent *nse) { + mspcap *mp = (mspcap *)nse->iod->pcap; + nsock_pcap npp; + nsock_pcap *n; + struct pcap_pkthdr *pkt_header; + const unsigned char *pkt_data = NULL; + int rc; + + memset(&npp, 0, sizeof(nsock_pcap)); + + nsock_log_debug_all("PCAP %s TEST (IOD #%li) (EID #%li)", + __func__, nse->iod->id, nse->id); + + assert(fs_length(&(nse->iobuf)) == 0); + + rc = pcap_next_ex(mp->pt, &pkt_header, &pkt_data); + switch (rc) { + case 1: /* read good packet */ +#ifdef PCAP_RECV_TIMEVAL_VALID + npp.ts = pkt_header->ts; +#else + /* On these platforms time received from pcap is invalid. + * It's better to set current time */ + memcpy(&npp.ts, nsock_gettimeofday(), sizeof(struct timeval)); +#endif + npp.len = pkt_header->len; + npp.caplen = pkt_header->caplen; + npp.packet = pkt_data; + + fs_cat(&(nse->iobuf), (char *)&npp, sizeof(npp)); + fs_cat(&(nse->iobuf), (char *)pkt_data, npp.caplen); + n = (nsock_pcap *)fs_str(&(nse->iobuf)); + n->packet = (unsigned char *)fs_str(&(nse->iobuf)) + sizeof(npp); + + nsock_log_debug_all("PCAP %s READ (IOD #%li) (EID #%li) size=%i", + __func__, nse->iod->id, nse->id, pkt_header->caplen); + rc = 1; + break; + + case 0: /* timeout */ + rc = 0; + break; + + case -1: /* error */ + fatal("pcap_next_ex() fatal error while reading from pcap: %s\n", + pcap_geterr(mp->pt)); + break; + + case -2: /* no more packets in savefile (if reading from one) */ + default: + fatal("Unexpected return code from pcap_next_ex! (%d)\n", rc); + } + + return rc; +} + +void nse_readpcap(nsock_event nsev, const unsigned char **l2_data, size_t *l2_len, + const unsigned char **l3_data, size_t *l3_len, + size_t *packet_len, struct timeval *ts) { + struct nevent *nse = (struct nevent *)nsev; + struct niod *iod = nse->iod; + mspcap *mp = (mspcap *)iod->pcap; + nsock_pcap *n; + size_t l2l; + size_t l3l; + + n = (nsock_pcap *)fs_str(&(nse->iobuf)); + if (fs_length(&(nse->iobuf)) < sizeof(nsock_pcap)) { + if (l2_data) + *l2_data = NULL; + if (l2_len) + *l2_len = 0; + if (l3_data) + *l3_data = NULL; + if (l3_len) + *l3_len = 0; + if (packet_len) + *packet_len = 0; + return; + } + + l2l = MIN(mp->l3_offset, n->caplen); + l3l = MAX(0, n->caplen-mp->l3_offset); + + if (l2_data) + *l2_data = n->packet; + if (l2_len) + *l2_len = l2l; + if (l3_data) + *l3_data = (l3l > 0) ? n->packet+l2l : NULL; + if (l3_len) + *l3_len = l3l; + if (packet_len) + *packet_len = n->len; + if (ts) + *ts = n->ts; + return; +} + +int nsock_iod_linktype(nsock_iod iod) { + struct niod *nsi = (struct niod *)iod; + mspcap *mp = (mspcap *)nsi->pcap; + + assert(mp); + return (mp->datalink); +} + +int nsock_iod_is_pcap(nsock_iod iod) { + struct niod *nsi = (struct niod *)iod; + mspcap *mp = (mspcap *)nsi->pcap; + + return (mp != NULL); +} + +#endif /* HAVE_PCAP */ + |