/*************************************************************************** * 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-2024 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 #if HAVE_SYS_IOCTL_H #include #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 #define _AIX #else #include #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 ; 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)); } pcap_freecode(&fcode); return rc; } 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 */