diff --git a/libnetutil/netutil.cc b/libnetutil/netutil.cc index 47c66ecba..c872f4868 100644 --- a/libnetutil/netutil.cc +++ b/libnetutil/netutil.cc @@ -4156,13 +4156,83 @@ void set_pcap_filter(const char *device, pcap_t *pd, const char *bpf, ...) { datalink types DLT_EN10MB and DLT_LINUX_SLL. Returns -1 on error. */ int datalink_offset(int datalink) { - if (datalink == DLT_EN10MB) - return ETH_HDR_LEN; - else if (datalink == DLT_LINUX_SLL) - /* The datalink type is Linux "cooked" sockets. See pcap-linktype(7). */ - return 16; - else - return -1; + int offset = -1; + /* 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 = ETH_HDR_LEN; + 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; +#else + offset = 24; /* Anyone use this??? */ +#endif + break; + 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; +#else +#ifdef SOLARIS + offset = 8; +#else + offset = 24; /* Anyone use this? */ +#endif /* ifdef solaris */ +#endif /* if freebsd || openbsd || netbsd || bsdi */ + break; + case DLT_RAW: + offset = 0; + break; + 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 + default: + offset = -1; + break; + } + return offset; } /* Common subroutine for reading ARP and NS responses. Input parameters are pd, @@ -4170,7 +4240,7 @@ int datalink_offset(int datalink) then the output parameters p, head, rcvdtime, datalink, and offset are filled in, and the function returns 1. If no frame passes before the timeout, then the function returns 0 and the output parameters are undefined. */ -static int read_reply_pcap(pcap_t *pd, long to_usec, +int read_reply_pcap(pcap_t *pd, long to_usec, bool (*accept_callback)(const unsigned char *, const struct pcap_pkthdr *, int, size_t), const unsigned char **p, struct pcap_pkthdr **head, struct timeval *rcvdtime, int *datalink, size_t *offset) diff --git a/libnetutil/netutil.h b/libnetutil/netutil.h index edba55814..51e17f81a 100644 --- a/libnetutil/netutil.h +++ b/libnetutil/netutil.h @@ -583,6 +583,16 @@ int read_ns_reply_pcap(pcap_t *pd, u8 *sendermac, struct timeval *rcvdtime, void (*traceArp_callback)(int, const u8 *, u32 , struct timeval *)); +/* Attempts to read one IP packet from the pcap descriptor pd. Input parameters are pd, + to_usec, and accept_callback. If a received frame passes accept_callback, + then the output parameters p, head, rcvdtime, datalink, and offset are filled + in, and the function returns 1. If no frame passes before the timeout, then + the function returns 0 and the output parameters are undefined. */ +int read_reply_pcap(pcap_t *pd, long to_usec, + bool (*accept_callback)(const unsigned char *, const struct pcap_pkthdr *, int, size_t), + const unsigned char **p, struct pcap_pkthdr **head, struct timeval *rcvdtime, + int *datalink, size_t *offset); + /* Read a single host specification from a file, as for -iL and --excludefile. It returns the length of the string read; an overflow is indicated when the return value is >= n. Returns 0 if there was no specification to be read. The diff --git a/tcpip.cc b/tcpip.cc index 3653b20b8..78d5b91c7 100644 --- a/tcpip.cc +++ b/tcpip.cc @@ -1346,7 +1346,7 @@ int readudppacket(const u8 *packet, int readdata) { /* Used by validatepkt() to validate the TCP header (including option lengths). The options checked are MSS, WScale, SackOK, Sack, and Timestamp. */ -static bool validateTCPhdr(u8 *tcpc, unsigned len) { +static bool validateTCPhdr(const u8 *tcpc, unsigned len) { struct tcp_hdr *tcp = (struct tcp_hdr *) tcpc; unsigned hdrlen, optlen; @@ -1431,7 +1431,7 @@ static bool validateTCPhdr(u8 *tcpc, unsigned len) { * read more than the IP header says we should have so as to not pass garbage * data to the caller. */ -static bool validatepkt(u8 *ipc, unsigned *len) { +static bool validatepkt(const u8 *ipc, unsigned *len) { struct ip *ip = (struct ip *) ipc; const void *data; unsigned int datalen, iplen; @@ -1531,14 +1531,14 @@ static bool validatepkt(u8 *ipc, unsigned *len) { linknfo->header will be filled with the appropriate values. */ /* Specifying true for validate will enable validity checks against the received IP packet. See validatepkt() for a list of checks. */ -char *readipv4_pcap(pcap_t *pd, unsigned int *len, long to_usec, +const u8 *readipv4_pcap(pcap_t *pd, unsigned int *len, long to_usec, struct timeval *rcvdtime, struct link_header *linknfo, bool validate) { - char *buf; + const u8 *buf; buf = readip_pcap(pd, len, to_usec, rcvdtime, linknfo, validate); if (buf != NULL) { - struct ip *ip; + const struct ip *ip; ip = (struct ip *) buf; if (*len < 1 || ip->ip_v != 4) @@ -1548,240 +1548,76 @@ char *readipv4_pcap(pcap_t *pd, unsigned int *len, long to_usec, return buf; } -char *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, +static bool accept_any (const unsigned char *p, const struct pcap_pkthdr *h, int datalink, size_t offset) { + return true; +} + +static bool accept_ip (const unsigned char *p, const struct pcap_pkthdr *h, int datalink, size_t offset) { + struct ip *ip = NULL; + + if (h->caplen < offset + sizeof(struct ip)) { + return false; + } + ip = (struct ip *) (p + offset); + switch (ip->ip_v) { + case 4: + case 6: + break; + default: + return false; + break; + } + + return true; +} + +const u8 *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, struct timeval *rcvdtime, struct link_header *linknfo, bool validate) { - unsigned int offset = 0; - struct pcap_pkthdr *head; - const unsigned char *p; - int pcap_status = 0; int datalink; - int timedout = 0; - struct timeval tv_start, tv_end; - static char *alignedbuf = NULL; - static unsigned int alignedbufsz = 0; - static int warning = 0; + size_t offset = 0; + struct pcap_pkthdr *head; + const u8 *p; + int got_one = 0; if (linknfo) { memset(linknfo, 0, sizeof(*linknfo)); } - if (!pd) - fatal("NULL packet device passed to %s", __func__); - - if (to_usec < 0) { - if (!warning) { - warning = 1; - error("WARNING: Negative timeout value (%lu) passed to %s() -- using 0", to_usec, __func__); - } - to_usec = 0; + if (validate) { + got_one = read_reply_pcap(pd, to_usec, accept_ip, &p, &head, rcvdtime, &datalink, &offset); + } + else { + got_one = read_reply_pcap(pd, to_usec, accept_any, &p, &head, rcvdtime, &datalink, &offset); } - /* New packet capture device, need to recompute offset */ - if ((datalink = pcap_datalink(pd)) < 0) - fatal("Cannot obtain datalink information: %s", pcap_geterr(pd)); - - /* 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; -#else - offset = 24; /* Anyone use this??? */ -#endif - break; - 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; -#else -#ifdef SOLARIS - offset = 8; -#else - offset = 24; /* Anyone use this? */ -#endif /* ifdef solaris */ -#endif /* if freebsd || openbsd || netbsd || bsdi */ - break; - case DLT_RAW: - offset = 0; - break; - 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 - default: - pcap_status = pcap_next_ex(pd, &head, &p); - if (pcap_status == 0) { - /* Lets sleep a brief time and try again to increase the chance of seeing - a real packet ... */ - usleep(500000); - pcap_status = pcap_next_ex(pd, &head, &p); - } - if (pcap_status != 1 || !head) { - // No packet captured - fatal("FATAL: Unknown datalink type (%d).", datalink); - } - if (head->caplen > 100000 || !p) { - fatal("FATAL: %s: bogus caplen from libpcap (%d) on interface type %d", __func__, head->caplen, datalink); - } - error("FATAL: Unknown datalink type (%d). Caplen: %d; Packet:", datalink, head->caplen); - nmap_hexdump(p, head->caplen); - exit(1); - } - - if (to_usec > 0) { - gettimeofday(&tv_start, NULL); - } - - do { - - p = NULL; - pcap_status = 0; - /* It may be that protecting this with !pcap_selectable_fd_one_to_one is not - necessary, that it is always safe to do a nonblocking read in this way on - all platforms. But I have only tested it on Solaris. */ - if (!pcap_selectable_fd_one_to_one()) { - int rc, nonblock; - - nonblock = pcap_getnonblock(pd, NULL); - assert(nonblock == 0); - rc = pcap_setnonblock(pd, 1, NULL); - assert(rc == 0); - pcap_status = pcap_next_ex(pd, &head, &p); - rc = pcap_setnonblock(pd, nonblock, NULL); - assert(rc == 0); - } - - if (pcap_status == PCAP_ERROR) { - // TODO: Gracefully end the scan. - // For now, consider a pcap read error to be fatal. - fatal("Error from pcap_next_ex: %s\n", pcap_geterr(pd)); - } - - if (pcap_status == 0 || p == NULL) { - /* Nonblocking pcap_next_ex didn't get anything. */ - if (pcap_select(pd, to_usec) == 0) - timedout = 1; - else - pcap_status = pcap_next_ex(pd, &head, &p); - } - - if (pcap_status == PCAP_ERROR) { - // TODO: Gracefully end the scan. - fatal("Error from pcap_next_ex: %s\n", pcap_geterr(pd)); - } - - if (pcap_status == 1 && p) { - if (head->caplen <= offset) { - *len = 0; - return NULL; - } - if (offset && linknfo) { - linknfo->datalinktype = datalink; - linknfo->headerlen = offset; - assert(offset <= MAX_LINK_HEADERSZ); - memcpy(linknfo->header, p, MIN(sizeof(linknfo->header), offset)); - } - p += offset; - } - else { - /* Should we timeout? */ - if (to_usec == 0) { - timedout = 1; - } else if (to_usec > 0) { - gettimeofday(&tv_end, NULL); - if (TIMEVAL_SUBTRACT(tv_end, tv_start) >= to_usec) { - timedout = 1; - } - } - } - } while (!timedout && (!p)); - - if (timedout) { + if (!got_one) { *len = 0; return NULL; } + *len = head->caplen - offset; - if (*len > alignedbufsz) { - alignedbuf = (char *) safe_realloc(alignedbuf, *len); - alignedbufsz = *len; - } - memcpy(alignedbuf, p, *len); + p += offset; if (validate) { - /* Let's see if this packet passes inspection.. */ - if (!validatepkt((u8 *) alignedbuf, len)) { + if (!validatepkt(p, len)) { *len = 0; return NULL; } } - // printf("Just got a packet at %li,%li\n", head.ts.tv_sec, head.ts.tv_usec); - if (rcvdtime) { - // FIXME: I eventually need to figure out why Windows head.ts time is sometimes BEFORE the time I - // sent the packet (which is according to gettimeofday() in nbase). For now, I will sadly have to - // use gettimeofday() for Windows in this case - // Actually I now allow .05 discrepancy. So maybe this isn't needed. I'll comment out for now. - // Nope: it is still needed at least for Windows. Sometimes the time from he pcap header is a - // COUPLE SECONDS before the gettimeofday() results :(. -#if defined(WIN32) || defined(__amigaos__) - gettimeofday(&tv_end, NULL); - *rcvdtime = tv_end; -#else - rcvdtime->tv_sec = head->ts.tv_sec; - rcvdtime->tv_usec = head->ts.tv_usec; - assert(head->ts.tv_sec); -#endif + if (offset && linknfo) { + linknfo->datalinktype = datalink; + linknfo->headerlen = offset; + assert(offset <= MAX_LINK_HEADERSZ); + memcpy(linknfo->header, p, MIN(sizeof(linknfo->header), offset)); } - if (rcvdtime) - PacketTrace::trace(PacketTrace::RCVD, (u8 *) alignedbuf, *len, - rcvdtime); + PacketTrace::trace(PacketTrace::RCVD, (u8 *) p, *len, + rcvdtime); else - PacketTrace::trace(PacketTrace::RCVD, (u8 *) alignedbuf, *len); + PacketTrace::trace(PacketTrace::RCVD, (u8 *) p, *len); - return alignedbuf; + *len = head->caplen - offset; + return p; } /* Attempts to read one IPv6 Neighbor Solicitation reply packet from the pcap @@ -1795,7 +1631,7 @@ char *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, by Nmap only. Any other calling this should pass NULL instead. */ int read_na_pcap(pcap_t *pd, u8 *sendermac, struct sockaddr_in6 *senderIP, long to_usec, struct timeval *rcvdtime, bool *has_mac) { - struct ip *ip_tmp; + const struct ip *ip_tmp; struct icmpv6_hdr *icmp6_header; struct icmpv6_msg_nd *na; struct timeval tv_start, tv_end; diff --git a/tcpip.h b/tcpip.h index 089e114ed..afc105144 100644 --- a/tcpip.h +++ b/tcpip.h @@ -436,25 +436,15 @@ int get_link_offset(char *device); filled with the time that packet was captured from the wire by pcap. If linknfo is not NULL, lnkinfo->headerlen and lnkinfo->header will be filled with the appropriate values. */ -char *readipv4_pcap(pcap_t *pd, unsigned int *len, long to_usec, +const u8 *readipv4_pcap(pcap_t *pd, unsigned int *len, long to_usec, struct timeval *rcvdtime, struct link_header *linknfo, bool validate); -char *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, +const u8 *readip_pcap(pcap_t *pd, unsigned int *len, long to_usec, struct timeval *rcvdtime, struct link_header *linknfo, bool validate); int read_na_pcap(pcap_t *pd, u8 *sendermac, struct sockaddr_in6 *senderIP, long to_usec, struct timeval *rcvdtime, bool *has_mac); -/* Attempts to read one IPv4/Ethernet ARP reply packet from the pcap - descriptor pd. If it receives one, fills in sendermac (must pass - in 6 bytes), senderIP, and rcvdtime (can be NULL if you don't care) - and returns 1. If it times out and reads no arp requests, returns - 0. to_usec is the timeout period in microseconds. Use 0 to avoid - blocking to the extent possible, and -1 to block forever. Returns - -1 or exits if there is an error. */ -int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac, struct in_addr *senderIP, - long to_usec, struct timeval *rcvdtime); - /* Examines the given tcp packet and obtains the TCP timestamp option information if available. Note that the CALLER must ensure that "tcp" contains a valid header (in particular the th_off must be the