1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-26 17:39:03 +00:00

Factor out common code from read_arp_reply_pcap and read_ns_reply_pcap.

This commit is contained in:
david
2012-03-21 23:03:42 +00:00
parent 29a4522c33
commit 560e663867

View File

@@ -3922,8 +3922,8 @@ void set_pcap_filter(const char *device, pcap_t *pd, const char *bpf, ...) {
/* Returns true if the captured frame is ARP. This function understands the
datalink types DLT_EN10MB and DLT_LINUX_SLL. */
static bool frame_is_arp(const u8 *frame, size_t len, int datalink) {
if (len < 16)
static bool frame_is_arp(const u8 *frame, size_t len, int datalink, size_t offset) {
if (len < offset + 28)
return false;
if (datalink == DLT_EN10MB) {
@@ -3937,27 +3937,29 @@ static bool frame_is_arp(const u8 *frame, size_t len, int datalink) {
}
}
/* 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. Returns -1 or exits if there is
an error. The last parameter is a pointer to a callback function
that can be used for packet tracing. This is intended to be used
by Nmap only. Any other calling this should pass NULL instead. */
int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac,
struct in_addr *senderIP, long to_usec,
struct timeval *rcvdtime,
void (*traceArp_callback)(int, const u8 *, u32 , struct timeval *)) {
/* Return the data offset for the given datalink. This function understands the
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;
}
static int read_reply_pcap(pcap_t *pd, long to_usec, unsigned char **p,
struct pcap_pkthdr *head, struct timeval *rcvdtime,
int *datalink, size_t *offset,
bool (*accept_callback)(const unsigned char *, const struct pcap_pkthdr *, int, size_t))
{
static int warning = 0;
int datalink;
struct pcap_pkthdr head;
u8 *p;
int timedout = 0;
int badcounter = 0;
unsigned int offset=0;
struct timeval tv_start, tv_end;
int ioffset;
if (!pd)
netutil_fatal("NULL packet device passed to %s", __func__);
@@ -3971,17 +3973,12 @@ int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac,
}
/* New packet capture device, need to recompute offset */
if ((datalink = pcap_datalink(pd)) < 0)
if ((*datalink = pcap_datalink(pd)) < 0)
netutil_fatal("Cannot obtain datalink information: %s", pcap_geterr(pd));
if (datalink == DLT_EN10MB) {
offset = ETH_HDR_LEN;
} else if (datalink == DLT_LINUX_SLL) {
/* The datalink type is Linux "cooked" sockets. See pcap-linktype(7). */
offset = 16;
} else {
netutil_fatal("%s called on interface that is datatype %d rather than DLT_EN10MB (%d) or DLT_LINUX_SLL (%d)", __func__, datalink, DLT_EN10MB, DLT_LINUX_SLL);
}
ioffset = datalink_offset(*datalink);
if (ioffset < 0)
netutil_fatal("datalink_offset failed for type %d (DLT_EN10MB = %d, DLT_LINUX_SLL = %d)", *datalink, DLT_EN10MB, DLT_LINUX_SLL);
*offset = (unsigned int) ioffset;
if (to_usec > 0) {
gettimeofday(&tv_start, NULL);
@@ -4000,26 +3997,16 @@ int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac,
}
#endif
p = NULL;
*p = NULL;
if (pcap_select(pd, to_usec) == 0)
timedout = 1;
else
p = (u8 *) pcap_next(pd, &head);
*p = (u8 *) pcap_next(pd, head);
if (p && head.caplen >= offset + 28) {
/* hw type eth (0x0001), prot ip (0x0800),
hw size (0x06), prot size (0x04) */
if (frame_is_arp(p, head.caplen, datalink) &&
memcmp(p + offset, "\x00\x01\x08\x00\x06\x04\x00\x02", 8) == 0) {
memcpy(sendermac, p + offset + 8, 6);
/* I think alignment should allow this ... */
memcpy(&senderIP->s_addr, p + offset + 14, 4);
break;
}
}
if (!p) {
if (*p != NULL && accept_callback(*p, head, *datalink, *offset)) {
break;
} else if (*p == NULL) {
/* Should we timeout? */
if (to_usec == 0) {
timedout = 1;
@@ -4051,19 +4038,79 @@ int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac,
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);
rcvdtime->tv_sec = head->ts.tv_sec;
rcvdtime->tv_usec = head->ts.tv_usec;
assert(head->ts.tv_sec);
#endif
}
if(traceArp_callback!=NULL){
/* TODO: First parameter "2" is a hardcoded value for Nmap's PacketTrace::RECV*/
traceArp_callback(2, (u8 *) p + offset, ARP_HDR_LEN + ARP_ETHIP_LEN, rcvdtime);
}
return 1;
}
static bool accept_arp(const unsigned char *p, const struct pcap_pkthdr *head,
int datalink, size_t offset)
{
if (head->caplen < offset + 8)
return false;
/* hw type eth (0x0001), prot ip (0x0800),
hw size (0x06), prot size (0x04) */
return frame_is_arp(p, head->caplen, datalink, offset) &&
memcmp(p + offset, "\x00\x01\x08\x00\x06\x04\x00\x02", 8) == 0;
}
/* 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. Returns -1 or exits if there is
an error. The last parameter is a pointer to a callback function
that can be used for packet tracing. This is intended to be used
by Nmap only. Any other calling this should pass NULL instead. */
int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac,
struct in_addr *senderIP, long to_usec,
struct timeval *rcvdtime,
void (*trace_callback)(int, const u8 *, u32, struct timeval *)) {
unsigned char *p;
struct pcap_pkthdr head;
int datalink;
size_t offset;
int rc;
rc = read_reply_pcap(pd, to_usec, &p, &head, rcvdtime, &datalink, &offset, accept_arp);
if (rc == 0)
return 0;
memcpy(sendermac, p + offset + 8, 6);
/* I think alignment should allow this ... */
memcpy(&senderIP->s_addr, p + offset + 14, 4);
if (trace_callback != NULL) {
/* TODO: First parameter "2" is a hardcoded value for Nmap's PacketTrace::RECV. */
trace_callback(2, (u8 *) p + offset, ARP_HDR_LEN + ARP_ETHIP_LEN, rcvdtime);
}
return 1;
}
static bool accept_ns(const unsigned char *p, const struct pcap_pkthdr *head,
int datalink, size_t offset)
{
struct icmpv6_hdr *icmp6_header;
struct icmpv6_msg_nd *na;
if (head->caplen < offset + IP6_HDR_LEN + 32)
return false;
icmp6_header = (struct icmpv6_hdr *)(p + offset + IP6_HDR_LEN);
na = (struct icmpv6_msg_nd *)(p + offset + IP6_HDR_LEN + ICMPV6_HDR_LEN);
return icmp6_header->icmpv6_type == ICMPV6_NEIGHBOR_ADVERTISEMENT &&
icmp6_header->icmpv6_code == 0 &&
na->icmpv6_option_type == 2 &&
na->icmpv6_option_length == 1;
}
/* Attempts to read one IPv6/Ethernet Neighbor Solicitation 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)
@@ -4076,120 +4123,25 @@ int read_arp_reply_pcap(pcap_t *pd, u8 *sendermac,
int read_ns_reply_pcap(pcap_t *pd, u8 *sendermac,
struct sockaddr_in6 *senderIP, long to_usec,
struct timeval *rcvdtime,
void (*traceND_callback)(int, const u8 *, u32 , struct timeval *)) {
static int warning = 0;
int datalink;
void (*trace_callback)(int, const u8 *, u32, struct timeval *)) {
unsigned char *p;
struct pcap_pkthdr head;
u8 *p;
int timedout = 0;
int badcounter = 0;
unsigned int offset=0;
struct timeval tv_start, tv_end;
struct icmpv6_hdr *icmp6_header = NULL;
struct icmpv6_msg_nd *na = NULL;
int datalink;
size_t offset;
int rc;
struct icmpv6_msg_nd *na;
if (!pd)
netutil_fatal("NULL packet device passed to %s", __func__);
if (to_usec < 0) {
if (!warning) {
warning = 1;
netutil_error("WARNING: Negative timeout value (%lu) passed to %s() -- using 0", to_usec, __func__);
}
to_usec = 0;
}
/* New packet capture device, need to recompute offset */
if ((datalink = pcap_datalink(pd)) < 0)
netutil_fatal("Cannot obtain datalink information: %s", pcap_geterr(pd));
if (datalink == DLT_EN10MB) {
offset = ETH_HDR_LEN;
} else if (datalink == DLT_LINUX_SLL) {
/* The datalink type is Linux "cooked" sockets. See pcap-linktype(7). */
offset = 16;
} else {
netutil_fatal("%s called on interface that is datatype %d rather than DLT_EN10MB (%d) or DLT_LINUX_SLL (%d)", __func__, datalink, DLT_EN10MB, DLT_LINUX_SLL);
}
if (to_usec > 0) {
gettimeofday(&tv_start, NULL);
}
do {
#ifdef WIN32
if (to_usec == 0) {
PacketSetReadTimeout(pd->adapter, 1);
} else {
gettimeofday(&tv_end, NULL);
long to_left =
MAX(1, (to_usec - TIMEVAL_SUBTRACT(tv_end, tv_start)) / 1000);
// Set the timeout (BUGBUG: this is cheating)
PacketSetReadTimeout(pd->adapter, to_left);
}
#endif
p = NULL;
if (pcap_select(pd, to_usec) == 0)
timedout = 1;
else
p = (u8 *) pcap_next(pd, &head);
if (p && head.caplen >= offset + IP6_HDR_LEN + 32) {
/* hw type eth (0x0001), prot ip (0x0800),
hw size (0x06), prot size (0x04) */
icmp6_header = (struct icmpv6_hdr *)(p + offset + IP6_HDR_LEN);
na = (struct icmpv6_msg_nd *)(p + offset + IP6_HDR_LEN + ICMPV6_HDR_LEN);
if (icmp6_header->icmpv6_type == ICMPV6_NEIGHBOR_ADVERTISEMENT &&
icmp6_header->icmpv6_code == 0 &&
na->icmpv6_option_type == 2 && na->icmpv6_option_length == 1){
memcpy(sendermac, &na->icmpv6_mac, 6);
memcpy(&senderIP->sin6_addr.s6_addr, &na->icmpv6_target, 16);
break;
}
}
if (!p) {
/* 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;
}
}
} else {
/* We'll be a bit patient if we're getting actual packets back, but
not indefinitely so */
if (badcounter++ > 50)
timedout = 1;
}
} while (!timedout);
if (timedout)
rc = read_reply_pcap(pd, to_usec, &p, &head, rcvdtime, &datalink, &offset, accept_ns);
if (rc == 0)
return 0;
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(traceND_callback!=NULL){
/* TODO: First parameter "2" is a hardcoded value for Nmap's PacketTrace::RECV*/
traceND_callback(2, (u8 *) p + offset, IP6_HDR_LEN + ICMPV6_HDR_LEN + 4 + 16 + 8, rcvdtime);
na = (struct icmpv6_msg_nd *)(p + offset + IP6_HDR_LEN + ICMPV6_HDR_LEN);
memcpy(sendermac, &na->icmpv6_mac, 6);
memcpy(&senderIP->sin6_addr.s6_addr, &na->icmpv6_target, 16);
if (trace_callback != NULL) {
/* TODO: First parameter "2" is a hardcoded value for Nmap's PacketTrace::RECV. */
trace_callback(2, (u8 *) p + offset, IP6_HDR_LEN + ICMPV6_HDR_LEN + 4 + 16 + 8, rcvdtime);
}
return 1;