1
0
mirror of https://github.com/nmap/nmap.git synced 2026-02-08 06:26:33 +00:00

Use libnetutil's read_reply_pcap to deduplicate pcap reading logic.

This commit is contained in:
dmiller
2020-01-13 21:48:04 +00:00
parent 00cb4d22d9
commit 4ffeb09ad3
4 changed files with 143 additions and 237 deletions

View File

@@ -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)

View File

@@ -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

270
tcpip.cc
View File

@@ -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;

14
tcpip.h
View File

@@ -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