From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- FPEngine.cc | 2741 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2741 insertions(+) create mode 100644 FPEngine.cc (limited to 'FPEngine.cc') diff --git a/FPEngine.cc b/FPEngine.cc new file mode 100644 index 0000000..261a6f1 --- /dev/null +++ b/FPEngine.cc @@ -0,0 +1,2741 @@ + +/*************************************************************************** + * FPEngine.cc -- Routines used for IPv6 OS detection via TCP/IP * + * fingerprinting. * For more information on how this works in Nmap, see * + * https://nmap.org/osdetect/ * + * * + ***********************IMPORTANT NMAP LICENSE TERMS************************ + * + * The Nmap Security Scanner is (C) 1996-2023 Nmap Software LLC ("The Nmap + * Project"). Nmap is also a registered trademark of the Nmap Project. + * + * This program is distributed under the terms of the Nmap Public Source + * License (NPSL). The exact license text applying to a particular Nmap + * release or source code control revision is contained in the LICENSE + * file distributed with that version of Nmap or source code control + * revision. More Nmap copyright/legal information is available from + * https://nmap.org/book/man-legal.html, and further information on the + * NPSL license itself can be found at https://nmap.org/npsl/ . This + * header summarizes some key points from the Nmap license, but is no + * substitute for the actual license text. + * + * Nmap is generally free for end users to download and use themselves, + * including commercial use. It is available from https://nmap.org. + * + * The Nmap license generally prohibits companies from using and + * redistributing Nmap in commercial products, but we sell a special Nmap + * OEM Edition with a more permissive license and special features for + * this purpose. See https://nmap.org/oem/ + * + * If you have received a written Nmap license agreement or contract + * stating terms other than these (such as an Nmap OEM license), you may + * choose to use and redistribute Nmap under those terms instead. + * + * The official Nmap Windows builds include the Npcap software + * (https://npcap.com) for packet capture and transmission. It is under + * separate license terms which forbid redistribution without special + * permission. So the official Nmap Windows builds may not be redistributed + * without special permission (such as an Nmap OEM license). + * + * 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 submit your changes as a Github PR + * or by email to the dev@nmap.org mailing list for possible incorporation into + * the main distribution. Unless you specify otherwise, it is understood that + * you are offering us very broad rights to use your submissions as described in + * the Nmap Public Source License Contributor Agreement. This is important + * because we fund the project by selling licenses with various terms, and also + * because the inability to relicense code has caused devastating problems for + * other Free Software projects (such as KDE and NASM). + * + * The free version of Nmap 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. Warranties, + * indemnification and commercial support are all available through the + * Npcap OEM program--see https://nmap.org/oem/ + * + ***************************************************************************/ + +/* $Id$ */ + +#include "FPEngine.h" +#include "Target.h" +#include "FingerPrintResults.h" +#include "NmapOps.h" +#include "nmap_error.h" +#include "osscan.h" +#include "linear.h" +#include "FPModel.h" +#include "tcpip.h" +#include "string_pool.h" +extern NmapOps o; +#ifdef WIN32 +/* Need DnetName2PcapName */ +#include "libnetutil/netutil.h" +/* from libdnet's intf-win32.c */ +extern "C" int g_has_npcap_loopback; +#endif + +#include + + +/****************************************************************************** + * Globals. * + ******************************************************************************/ + +/* This is the global network controller. FPHost classes use it to request + * network resources and schedule packet transmissions. */ +FPNetworkControl global_netctl; + + +/****************************************************************************** + * Implementation of class FPNetworkControl. * + ******************************************************************************/ +FPNetworkControl::FPNetworkControl() { + memset(&this->nsp, 0, sizeof(nsock_pool)); + memset(&this->pcap_nsi, 0, sizeof(pcap_nsi)); + memset(&this->pcap_ev_id, 0, sizeof(nsock_event_id)); + this->nsock_init = false; + this->rawsd = -1; + this->probes_sent = 0; + this->responses_recv = 0; + this->probes_timedout = 0; + this->cc_cwnd = 0; + this->cc_ssthresh = 0; +} + + +FPNetworkControl::~FPNetworkControl() { + if (this->nsock_init) { + nsock_event_cancel(this->nsp, this->pcap_ev_id, 0); + nsock_pool_delete(this->nsp); + this->nsock_init = false; + } +} + + +/* (Re)-Initialize object's state (default parameter setup and nsock + * initialization). */ +void FPNetworkControl::init(const char *ifname, devtype iftype) { + + /* Init congestion control parameters */ + this->cc_init(); + + /* If there was a previous nsock pool, delete it */ + if (this->pcap_nsi) { + nsock_iod_delete(this->pcap_nsi, NSOCK_PENDING_SILENT); + } + if (this->nsock_init) { + nsock_event_cancel(this->nsp, this->pcap_ev_id, 0); + nsock_pool_delete(this->nsp); + } + + /* Create a new nsock pool */ + if ((this->nsp = nsock_pool_new(NULL)) == NULL) + fatal("Unable to obtain an Nsock pool"); + + nmap_set_nsock_logger(); + nmap_adjust_loglevel(o.packetTrace()); + + nsock_pool_set_device(nsp, o.device); + + if (o.proxy_chain) + nsock_pool_set_proxychain(this->nsp, o.proxy_chain); + + /* Allow broadcast addresses */ + nsock_pool_set_broadcast(this->nsp, 1); + + /* Allocate an NSI for packet capture */ + this->pcap_nsi = nsock_iod_new(this->nsp, NULL); + this->first_pcap_scheduled = false; + + /* Flag it as already initialized so we free this nsp next time */ + this->nsock_init = true; + + /* Obtain raw socket or check that we can obtain an eth descriptor. */ + if ((o.sendpref & PACKET_SEND_ETH) && (iftype == devt_ethernet +#ifdef WIN32 + || (g_has_npcap_loopback && iftype == devt_loopback) +#endif + ) && ifname != NULL) { + /* We don't need to store the eth handler because FPProbes come with a + * suitable one (FPProbes::getEthernet()), we just attempt to obtain one + * to see if it fails. */ + if (eth_open_cached(ifname) == NULL) + fatal("dnet: failed to open device %s", ifname); + this->rawsd = -1; + } else { +#ifdef WIN32 + win32_fatal_raw_sockets(ifname); +#endif + if (this->rawsd >= 0) + close(this->rawsd); + rawsd = nmap_raw_socket(); + if (rawsd < 0) + pfatal("Couldn't obtain raw socket in %s", __func__); + } + + /* De-register existing callers */ + while (this->callers.size() > 0) { + this->callers.pop_back(); + } + return; +} + + +/* This function initializes the controller's congestion control parameters. + * The network controller uses TCP's Slow Start and Congestion Avoidance + * algorithms from RFC 5681 (slightly modified for convenience). + * + * As the OS detection process does not open full TCP connections, we can't just + * use ACKs (or the lack of ACKs) to increase or decrease the congestion window + * so we use probe responses. Every time we get a response to an OS detection + * probe, we treat it as if it was a TCP ACK in TCP's congestion control. + * + * Note that the initial Congestion Window is set to the number of timed + * probes that we send to each target. This is necessary since we need to + * know for sure that we can send that many packets in order to transmit them. + * Otherwise, we could fail to deliver the probes 100ms apart. */ +int FPNetworkControl::cc_init() { + this->probes_sent = 0; + this->responses_recv = 0; + this->probes_timedout = 0; + this->cc_cwnd = OSSCAN_INITIAL_CWND; + this->cc_ssthresh = OSSCAN_INITIAL_SSTHRESH; + return OP_SUCCESS; +} + + +/* This method is used to indicate that we have scheduled the transmission of + * one or more packets. This is used in congestion control to determine the + * number of outstanding probes (number of probes sent but not answered yet) + * and therefore, the effective transmission window. @param pkts indicates the + * number of packets that were scheduled. Returns OP_SUCCESS on success and + * OP_FAILURE in case of error. */ +int FPNetworkControl::cc_update_sent(int pkts = 1) { + if (pkts <= 0) + return OP_FAILURE; + this->probes_sent+=pkts; + return OP_SUCCESS; +} + + +/* This method is used to indicate that a drop has occurred. In TCP, drops are + * detected by the absence of an ACK. However, we can't use that, since it is + * very likely that our targets do not respond to some of our OS detection + * probes intentionally. For this reason, we consider that a drop has occurred + * when we receive a response for a probe that has already suffered one + * retransmission (first transmission got dropped in transit, some later + * transmission made it to the host and it responded). So when we detect a drop + * we do the same as TCP, adjust the congestion window and the slow start + * threshold. */ +int FPNetworkControl::cc_report_drop() { +/* FROM RFC 5681 + + When a TCP sender detects segment loss using the retransmission timer + and the given segment has not yet been resent by way of the + retransmission timer, the value of ssthresh MUST be set to no more + than the value given in equation (4): + + ssthresh = max (FlightSize / 2, 2*SMSS) (4) + + where, as discussed above, FlightSize is the amount of outstanding + data in the network. + + On the other hand, when a TCP sender detects segment loss using the + retransmission timer and the given segment has already been + retransmitted by way of the retransmission timer at least once, the + value of ssthresh is held constant. + */ + int probes_outstanding = this->probes_sent - this->responses_recv - this->probes_timedout; + this->cc_ssthresh = (float)MAX(probes_outstanding, OSSCAN_INITIAL_CWND); + this->cc_cwnd = OSSCAN_INITIAL_CWND; + return OP_SUCCESS; +} + + +/* This method is used to indicate that a response to a previous probe was + * received. For us this is like getting and ACK in TCP congestion control, so + * we update the congestion window (increase by one packet if we are in slow + * start or increase it by a small percentage of a packet if we are in + * congestion avoidance). */ +int FPNetworkControl::cc_update_received() { + this->responses_recv++; + /* If we are in Slow Start, increment congestion window by one packet. + * (Note that we treat probe responses the same way TCP CC treats ACKs). */ + if (this->cc_cwnd < this->cc_ssthresh) { + this->cc_cwnd += 1; + /* Otherwise we are in Congestion Avoidance and CWND is incremented slowly, + * approximately one packet per RTT */ + } else { + this->cc_cwnd = this->cc_cwnd + 1/this->cc_cwnd; + } + if (o.debugging > 3) { + log_write(LOG_PLAIN, "[FPNetworkControl] Congestion Control Parameters: cwnd=%f ssthresh=%f sent=%d recv=%d tout=%d outstanding=%d\n", + this->cc_cwnd, this->cc_ssthresh, this->probes_sent, this->responses_recv, this->probes_timedout, + this->probes_sent - this->responses_recv - this->probes_timedout); + } + return OP_SUCCESS; +} + + +/* This method is public and can be called by FPHosts to inform the controller + * that a probe has experienced a final timeout. In other words, that no + * response was received for the probe after doing the necessary retransmissions + * and waiting for the RTO. This is used to decrease the number of outstanding + * probes. Otherwise, if no host responded to the probes, the effective + * transmission window could reach zero and prevent new probes from being sent, + * clogging the engine. */ +int FPNetworkControl::cc_report_final_timeout() { + this->probes_timedout++; + return OP_SUCCESS; +} + + +/* This method is used by FPHosts to request permission to transmit a number of + * probes. Permission is granted if the current congestion window allows the + * transmission of new probes. It returns true if permission is granted and + * false if it is denied. */ +bool FPNetworkControl::request_slots(size_t num_packets) { + int probes_outstanding = this->probes_sent - this->responses_recv - this->probes_timedout; + if (o.debugging > 3) + log_write(LOG_PLAIN, "[FPNetworkControl] Slot request for %u packets. ProbesOutstanding=%d cwnd=%f ssthresh=%f\n", + (unsigned int)num_packets, probes_outstanding, this->cc_cwnd, this->cc_ssthresh); + /* If we still have room for more outstanding probes, let the caller + * schedule transmissions. */ + if ((probes_outstanding + num_packets) <= this->cc_cwnd) { + this->cc_update_sent(num_packets); + return true; + } + return false; +} + + +/* This method lets FPHosts register themselves in the network controller so + * the controller can call them back every time a packet they are interested + * in is captured.*/ +int FPNetworkControl::register_caller(FPHost *newcaller) { + this->callers.push_back(newcaller); + return OP_SUCCESS; +} + + +/* This method lets FPHosts unregister themselves in the network controller so + * the controller does not call them back again. This is called by hosts that + * have already finished their OS detection. */ +int FPNetworkControl::unregister_caller(FPHost *oldcaller) { + for (size_t i = 0; i < this->callers.size(); i++) { + if (this->callers[i] == oldcaller) { + this->callers.erase(this->callers.begin() + i); + return OP_SUCCESS; + } + } + return OP_FAILURE; +} + + +/* This method gets the controller ready for packet capture. Basically it + * obtains a pcap descriptor from nsock and sets an appropriate BPF filter. */ +int FPNetworkControl::setup_sniffer(const char *iface, const char *bpf_filter) { + char pcapdev[128]; + int rc; + +#ifdef WIN32 + /* Nmap normally uses device names obtained through dnet for interfaces, but + Pcap has its own naming system. So the conversion is done here */ + if (!DnetName2PcapName(iface, pcapdev, sizeof(pcapdev))) { + /* Oh crap -- couldn't find the corresponding dev apparently. Let's just go + with what we have then ... */ + Strncpy(pcapdev, iface, sizeof(pcapdev)); + } +#else + Strncpy(pcapdev, iface, sizeof(pcapdev)); +#endif + + /* Obtain a pcap descriptor */ + rc = nsock_pcap_open(this->nsp, this->pcap_nsi, pcapdev, 8192, 0, bpf_filter); + if (rc) + fatal("Error opening capture device %s\n", pcapdev); + + /* Store the pcap NSI inside the pool so we can retrieve it inside a callback */ + nsock_pool_set_udata(this->nsp, (void *)&(this->pcap_nsi)); + + return OP_SUCCESS; +} + + +/* This method makes the controller process pending events (like packet + * transmissions or packet captures). */ +void FPNetworkControl::handle_events() { + nmap_adjust_loglevel(o.packetTrace()); + nsock_loop(nsp, 50); +} + + +/* This method lets FPHosts to schedule the transmission of an OS detection + * probe. It takes an FPProbe pointer and the amount of milliseconds the + * controller should wait before injecting the probe into the wire. */ +int FPNetworkControl::scheduleProbe(FPProbe *pkt, int in_msecs_time) { + nsock_timer_create(this->nsp, probe_transmission_handler_wrapper, in_msecs_time, (void*)pkt); + return OP_SUCCESS; +} + + +/* This is the handler for packet transmission. It is called by nsock whenever a timer expires, + * which means that a new packet needs to be transmitted. Note that this method is not + * called directly by Nsock but by the wrapper function probe_transmission_handler_wrapper(). + * The reason for that is because C++ does not allow to use class methods as callback + * functions, so this is a small hack to make that happen. */ +void FPNetworkControl::probe_transmission_handler(nsock_pool nsp, nsock_event nse, void *arg) { + assert(nsock_pool_get_udata(nsp) != NULL); + nsock_iod nsi_pcap = *((nsock_iod *)nsock_pool_get_udata(nsp)); + enum nse_status status = nse_status(nse); + enum nse_type type = nse_type(nse); + FPProbe *myprobe = (FPProbe *)arg; + u8 *buf; + size_t len; + int result; + + if (status == NSE_STATUS_SUCCESS) { + switch(type) { + /* Timer events mean that we need to send a packet. */ + case NSE_TYPE_TIMER: + + /* The first time a packet is sent, we schedule a pcap event. After that + * we don't have to worry since the response reception handler schedules + * a new capture event for each captured packet. */ + if (!this->first_pcap_scheduled) { + this->pcap_ev_id = nsock_pcap_read_packet(nsp, nsi_pcap, response_reception_handler_wrapper, -1, NULL); + this->first_pcap_scheduled = true; + } + + /* Send the packet*/ + for (int decoy = 0; decoy < o.numdecoys; decoy++) { + result = myprobe->changeSourceAddress(&((struct sockaddr_in6 *)&o.decoys[decoy])->sin6_addr); + assert(result == OP_SUCCESS); + assert(myprobe->host != NULL); + buf = myprobe->getPacketBuffer(&len); + if (send_ip_packet(this->rawsd, myprobe->getEthernet(), myprobe->host->getTargetAddress(), buf, len) == -1) { + if (decoy == o.decoyturn) { + myprobe->setFailed(); + this->cc_report_final_timeout(); + myprobe->host->fail_one_probe(); + gh_perror("Unable to send packet in %s", __func__); + } + } + if (decoy == o.decoyturn) { + myprobe->setTimeSent(); + } + free(buf); + } + /* Reset the address to the original one if decoys were present and original Address wasn't last one */ + if ( o.numdecoys != o.decoyturn+1 ) { + result = myprobe->changeSourceAddress(&((struct sockaddr_in6 *)&o.decoys[o.decoyturn])->sin6_addr); + assert(result == OP_SUCCESS); + } + + break; + + default: + fatal("Unexpected Nsock event in probe_transmission_handler()"); + break; + } /* switch(type) */ + } else if (status == NSE_STATUS_EOF) { + if (o.debugging) + log_write(LOG_PLAIN, "probe_transmission_handler(): EOF\n"); + } else if (status == NSE_STATUS_ERROR || status == NSE_STATUS_PROXYERROR) { + if (o.debugging) + log_write(LOG_PLAIN, "probe_transmission_handler(): %s failed: %s\n", nse_type2str(type), strerror(socket_errno())); + } else if (status == NSE_STATUS_TIMEOUT) { + if (o.debugging) + log_write(LOG_PLAIN, "probe_transmission_handler(): %s timeout: %s\n", nse_type2str(type), strerror(socket_errno())); + } else if (status == NSE_STATUS_CANCELLED) { + if (o.debugging) + log_write(LOG_PLAIN, "probe_transmission_handler(): %s canceled: %s\n", nse_type2str(type), strerror(socket_errno())); + } else if (status == NSE_STATUS_KILL) { + if (o.debugging) + log_write(LOG_PLAIN, "probe_transmission_handler(): %s killed: %s\n", nse_type2str(type), strerror(socket_errno())); + } else { + if (o.debugging) + log_write(LOG_PLAIN, "probe_transmission_handler(): Unknown status code %d\n", status); + } + return; +} + + +/* This is the handler for packet capture. It is called by nsock whenever libpcap + * captures a packet from the network interface. This method basically captures + * the packet, extracts its source IP address and tries to find an FPHost that + * is targeting such address. If it does, it passes the packet to that FPHost + * via callback() so the FPHost can determine if the packet is actually the + * response to a FPProbe that it sent before. Note that this method is not + * called directly by Nsock but by the wrapper function + * response_reception_handler_wrapper(). See doc in probe_transmission_handler() + * for details. */ +void FPNetworkControl::response_reception_handler(nsock_pool nsp, nsock_event nse, void *arg) { + nsock_iod nsi = nse_iod(nse); + enum nse_status status = nse_status(nse); + enum nse_type type = nse_type(nse); + const u8 *rcvd_pkt = NULL; /* Points to the captured packet */ + size_t rcvd_pkt_len = 0; /* Length of the captured packet */ + struct timeval pcaptime; /* Time the packet was captured */ + struct sockaddr_storage sent_ss; + struct sockaddr_storage rcvd_ss; + struct sockaddr_in *rcvd_ss4 = (struct sockaddr_in *)&rcvd_ss; + struct sockaddr_in6 *rcvd_ss6 = (struct sockaddr_in6 *)&rcvd_ss; + memset(&rcvd_ss, 0, sizeof(struct sockaddr_storage)); + IPv4Header ip4; + IPv6Header ip6; + int res = -1; + + struct timeval tv; + gettimeofday(&tv, NULL); + + if (status == NSE_STATUS_SUCCESS) { + switch(type) { + + case NSE_TYPE_PCAP_READ: + + /* Schedule a new pcap read operation */ + this->pcap_ev_id = nsock_pcap_read_packet(nsp, nsi, response_reception_handler_wrapper, -1, NULL); + + /* Get captured packet */ + nse_readpcap(nse, NULL, NULL, &rcvd_pkt, &rcvd_pkt_len, NULL, &pcaptime); + + /* Extract the packet's source address */ + ip4.storeRecvData(rcvd_pkt, rcvd_pkt_len); + if (ip4.validate() != OP_FAILURE && ip4.getVersion() == 4) { + ip4.getSourceAddress(&(rcvd_ss4->sin_addr)); + rcvd_ss4->sin_family = AF_INET; + } else { + ip6.storeRecvData(rcvd_pkt, rcvd_pkt_len); + if (ip6.validate() != OP_FAILURE && ip6.getVersion() == 6) { + ip6.getSourceAddress(&(rcvd_ss6->sin6_addr)); + rcvd_ss6->sin6_family = AF_INET6; + } else { + /* If we get here it means that the received packet is not + * IPv4 or IPv6 so we just discard it returning. */ + return; + } + } + + /* Check if we have a caller that expects packets from this sender */ + for (size_t i = 0; i < this->callers.size(); i++) { + + /* Obtain the target address */ + sent_ss = *this->callers[i]->getTargetAddress(); + + /* Check that the received packet is of the same address family */ + if (sent_ss.ss_family != rcvd_ss.ss_family) + continue; + + /* Check that the captured packet's source address matches the + * target address. If it matches, pass the received packet + * to the appropriate FPHost object through callback(). */ + if (sockaddr_storage_equal(&rcvd_ss, &sent_ss)) { + if ((res = this->callers[i]->callback(rcvd_pkt, rcvd_pkt_len, &tv)) >= 0) { + + /* If callback() returns >=0 it means that the packet we've just + * passed was successfully matched with a previous probe. Now + * update the count of received packets (so we can determine how + * many outstanding packets are out there). Note that we only do + * that if callback() returned >0 because 0 is a special case: a + * reply to a retransmitted timed probe that was already replied + * to in the past. We don't want to count replies to the same probe + * more than once, so that's why we only update when res > 0. */ + if (res > 0) + this->cc_update_received(); + + /* When the callback returns more than 1 it means that the packet + * was sent more than once before being answered. This means that + * we experienced congestion (first transmission got dropped), so + * we update our CC parameters to deal with the congestion. */ + if (res > 1) { + this->cc_report_drop(); + } + } + return; + } + } + break; + + default: + fatal("Unexpected Nsock event in response_reception_handler()"); + break; + + } /* switch(type) */ + + } else if (status == NSE_STATUS_EOF) { + if (o.debugging) + log_write(LOG_PLAIN, "response_reception_handler(): EOF\n"); + } else if (status == NSE_STATUS_ERROR || status == NSE_STATUS_PROXYERROR) { + if (o.debugging) + log_write(LOG_PLAIN, "response_reception_handler(): %s failed: %s\n", nse_type2str(type), strerror(socket_errno())); + } else if (status == NSE_STATUS_TIMEOUT) { + if (o.debugging) + log_write(LOG_PLAIN, "response_reception_handler(): %s timeout: %s\n", nse_type2str(type), strerror(socket_errno())); + } else if (status == NSE_STATUS_CANCELLED) { + if (o.debugging) + log_write(LOG_PLAIN, "response_reception_handler(): %s canceled: %s\n", nse_type2str(type), strerror(socket_errno())); + } else if (status == NSE_STATUS_KILL) { + if (o.debugging) + log_write(LOG_PLAIN, "response_reception_handler(): %s killed: %s\n", nse_type2str(type), strerror(socket_errno())); + } else { + if (o.debugging) + log_write(LOG_PLAIN, "response_reception_handler(): Unknown status code %d\n", status); + } + return; +} + + +/****************************************************************************** + * Implementation of class FPEngine. * + ******************************************************************************/ +FPEngine::FPEngine() { + this->osgroup_size = OSSCAN_GROUP_SIZE; +} + + +FPEngine::~FPEngine() { + +} + + +/* Returns a suitable BPF filter for the OS detection. If less than 20 targets + * are passed, the filter contains an explicit list of target addresses. It + * looks similar to this: + * + * dst host fe80::250:56ff:fec0:1 and (src host fe80::20c:29ff:feb0:2316 or src host fe80::20c:29ff:fe9f:5bc2) + * + * When more than 20 targets are passed, a generic filter based on the source + * address is used. The returned filter looks something like: + * + * dst host fe80::250:56ff:fec0:1 + */ +const char *FPEngine::bpf_filter(std::vector &Targets) { + static char pcap_filter[2048]; + /* 20 IPv6 addresses is max (46 byte addy + 14 (" or src host ")) * 20 == 1200 */ + char dst_hosts[1220]; + int filterlen = 0; + int len = 0; + unsigned int targetno; + memset(pcap_filter, 0, sizeof(pcap_filter)); + + /* If we have 20 or less targets, build a list of addresses so we can set + * an explicit BPF filter */ + if (Targets.size() <= 20) { + for (targetno = 0; targetno < Targets.size(); targetno++) { + len = Snprintf(dst_hosts + filterlen, + sizeof(dst_hosts) - filterlen, + "%ssrc host %s", (targetno == 0)? "" : " or ", + Targets[targetno]->targetipstr()); + + if (len < 0 || len + filterlen >= (int) sizeof(dst_hosts)) + fatal("ran out of space in dst_hosts"); + filterlen += len; + } + + len = Snprintf(pcap_filter, sizeof(pcap_filter), "dst host %s and (%s)", + Targets[0]->sourceipstr(), dst_hosts); + } else { + len = Snprintf(pcap_filter, sizeof(pcap_filter), "dst host %s", Targets[0]->sourceipstr()); + } + + if (len < 0 || len >= (int) sizeof(pcap_filter)) + fatal("ran out of space in pcap filter"); + + return pcap_filter; +} + + +/****************************************************************************** + * Implementation of class FPEngine6. * + ******************************************************************************/ +FPEngine6::FPEngine6() { + +} + + +FPEngine6::~FPEngine6() { + +} + +/* Not all operating systems allow setting the flow label in outgoing packets; + notably all Unixes other than Linux when using raw sockets. This function + finds out whether the flow labels we set are likely really being sent. + Otherwise, the operating system is probably filling in 0. Compare to the + logic in send_ipv6_packet_eth_or_sd. */ +static bool can_set_flow_label(const struct eth_nfo *eth) { + if (eth != NULL) + return true; +#if HAVE_IPV6_IPPROTO_RAW + return true; +#else + return false; +#endif +} + +void FPHost6::fill_FPR(FingerPrintResultsIPv6 *FPR) { + unsigned int i; + + FPR->begin_time = this->begin_time; + + for (i = 0; i < sizeof(this->fp_responses) / sizeof(this->fp_responses[0]); i++) { + const FPResponse *resp; + + resp = this->fp_responses[i]; + if (resp != NULL) { + FPR->fp_responses[i] = new FPResponse(resp->probe_id, resp->buf, resp->len, + resp->senttime, resp->rcvdtime); + } + } + + /* Were we actually able to set the flow label? */ + FPR->flow_label = 0; + for (i = 0; i < sizeof(this->fp_probes) / sizeof(this->fp_probes[0]); i++) { + const FPProbe& probe = fp_probes[0]; + if (probe.is_set()) { + if (can_set_flow_label(probe.getEthernet())) + FPR->flow_label = OSDETECT_FLOW_LABEL; + break; + } + } + + /* Did we fail to send some probe? */ + FPR->incomplete = this->incomplete_fp; +} + +static IPv6Header *find_ipv6(const PacketElement *pe) { + while (pe != NULL && pe->protocol_id() != HEADER_TYPE_IPv6) + pe = pe->getNextElement(); + + return (IPv6Header *) pe; +} + +static const TCPHeader *find_tcp(const PacketElement *pe) { + while (pe != NULL && pe->protocol_id() != HEADER_TYPE_TCP) + pe = pe->getNextElement(); + + return (TCPHeader *) pe; +} + +static const ICMPv6Header *find_icmpv6(const PacketElement *pe) { + while (pe != NULL && pe->protocol_id() != HEADER_TYPE_ICMPv6) + pe = pe->getNextElement(); + + return (ICMPv6Header *) pe; +} + +static double vectorize_plen(const PacketElement *pe) { + const IPv6Header *ipv6; + + ipv6 = find_ipv6(pe); + if (ipv6 == NULL) + return -1; + else + return ipv6->getPayloadLength(); +} + +static double vectorize_tc(const PacketElement *pe) { + const IPv6Header *ipv6; + + ipv6 = find_ipv6(pe); + if (ipv6 == NULL) + return -1; + else + return ipv6->getTrafficClass(); +} + +/* For reference, the dev@nmap.org email thread which contains the explanations for the + * design decisions of this vectorization method: + * http://seclists.org/nmap-dev/2015/q1/218 + */ +static int vectorize_hlim(const PacketElement *pe, int target_distance, enum dist_calc_method method) { + const IPv6Header *ipv6; + int hlim; + int er_lim; + + ipv6 = find_ipv6(pe); + if (ipv6 == NULL) + return -1; + hlim = ipv6->getHopLimit(); + + if (method != DIST_METHOD_NONE) { + if (method == DIST_METHOD_TRACEROUTE || method == DIST_METHOD_ICMP) { + if (target_distance > 0) + hlim += target_distance - 1; + } + er_lim = 5; + } else + er_lim = 20; + + if (32 - er_lim <= hlim && hlim <= 32+ 5 ) + hlim = 32; + else if (64 - er_lim <= hlim && hlim <= 64+ 5 ) + hlim = 64; + else if (128 - er_lim <= hlim && hlim <= 128+ 5 ) + hlim = 128; + else if (255 - er_lim <= hlim && hlim <= 255+ 5 ) + hlim = 255; + else + hlim = -1; + + return hlim; +} + +static double vectorize_isr(std::map& resps) { + const char * const SEQ_PROBE_NAMES[] = {"S1", "S2", "S3", "S4", "S5", "S6"}; + u32 seqs[NELEMS(SEQ_PROBE_NAMES)]; + struct timeval times[NELEMS(SEQ_PROBE_NAMES)]; + unsigned int i, j; + double sum, t; + + j = 0; + for (i = 0; i < NELEMS(SEQ_PROBE_NAMES); i++) { + const char *probe_name; + const FPPacket *fp; + const TCPHeader *tcp; + std::map::const_iterator it; + + probe_name = SEQ_PROBE_NAMES[i]; + it = resps.find(probe_name); + if (it == resps.end()) + continue; + + fp = &it->second; + tcp = find_tcp(fp->getPacket()); + if (tcp == NULL) + continue; + + seqs[j] = tcp->getSeq(); + times[j] = fp->getTime(); + j++; + } + + if (j < 2) + return -1; + + sum = 0.0; + for (i = 0; i < j - 1; i++) + sum += seqs[i + 1] - seqs[i]; + t = TIMEVAL_FSEC_SUBTRACT(times[j - 1], times[0]); + + return sum / t; +} + +static int vectorize_icmpv6_type(const PacketElement *pe) { + const ICMPv6Header *icmpv6; + + icmpv6 = find_icmpv6(pe); + if (icmpv6 == NULL) + return -1; + + return icmpv6->getType(); +} + +static int vectorize_icmpv6_code(const PacketElement *pe) { + const ICMPv6Header *icmpv6; + + icmpv6 = find_icmpv6(pe); + if (icmpv6 == NULL) + return -1; + + return icmpv6->getCode(); +} + +static struct feature_node *vectorize(const FingerPrintResultsIPv6 *FPR) { + const char * const IPV6_PROBE_NAMES[] = {"S1", "S2", "S3", "S4", "S5", "S6", "IE1", "IE2", "NS", "U1", "TECN", "T2", "T3", "T4", "T5", "T6", "T7"}; + const char * const TCP_PROBE_NAMES[] = {"S1", "S2", "S3", "S4", "S5", "S6", "TECN", "T2", "T3", "T4", "T5", "T6", "T7"}; + const char * const ICMPV6_PROBE_NAMES[] = {"IE1", "IE2", "NS"}; + + unsigned int nr_feature, i, idx; + struct feature_node *features; + std::map resps; + + for (i = 0; i < NUM_FP_PROBES_IPv6; i++) { + PacketElement *pe; + + if (FPR->fp_responses[i] == NULL) + continue; + pe = PacketParser::split(FPR->fp_responses[i]->buf, FPR->fp_responses[i]->len); + assert(pe != NULL); + resps[FPR->fp_responses[i]->probe_id].setPacket(pe); + resps[FPR->fp_responses[i]->probe_id].setTime(&FPR->fp_responses[i]->senttime); + } + + nr_feature = get_nr_feature(&FPModel); + features = new feature_node[nr_feature + 1]; + for (i = 0; i < nr_feature; i++) { + features[i].index = i + 1; + features[i].value = -1; + } + features[i].index = -1; + + idx = 0; + for (i = 0; i < NELEMS(IPV6_PROBE_NAMES); i++) { + const char *probe_name; + + probe_name = IPV6_PROBE_NAMES[i]; + features[idx++].value = vectorize_plen(resps[probe_name].getPacket()); + features[idx++].value = vectorize_tc(resps[probe_name].getPacket()); + features[idx++].value = vectorize_hlim(resps[probe_name].getPacket(), FPR->distance, FPR->distance_calculation_method); + } + /* TCP features */ + features[idx++].value = vectorize_isr(resps); + for (i = 0; i < NELEMS(TCP_PROBE_NAMES); i++) { + const char *probe_name; + const TCPHeader *tcp; + u16 flags; + u16 mask; + unsigned int j; + int mss; + int sackok; + int wscale; + + probe_name = TCP_PROBE_NAMES[i]; + + mss = -1; + sackok = -1; + wscale = -1; + + tcp = find_tcp(resps[probe_name].getPacket()); + if (tcp == NULL) { + /* 49 TCP features. */ + idx += 49; + continue; + } + features[idx++].value = tcp->getWindow(); + flags = tcp->getFlags16(); + for (mask = 0x001; mask <= 0x800; mask <<= 1) + features[idx++].value = (flags & mask) != 0; + + for (j = 0; j < 16; j++) { + nping_tcp_opt_t opt; + opt = tcp->getOption(j); + if (opt.value == NULL) + break; + features[idx++].value = opt.type; + /* opt.len includes the two (type, len) bytes. */ + if (opt.type == TCPOPT_MSS && opt.len == 4 && mss == -1) + mss = ntohs(*(u16 *) opt.value); + else if (opt.type == TCPOPT_SACKOK && opt.len == 2 && sackok == -1) + sackok = 1; + else if (opt.type == TCPOPT_WSCALE && opt.len == 3 && wscale == -1) + wscale = *(u8 *) opt.value; + } + for (; j < 16; j++) + idx++; + + for (j = 0; j < 16; j++) { + nping_tcp_opt_t opt; + opt = tcp->getOption(j); + if (opt.value == NULL) + break; + features[idx++].value = opt.len; + } + for (; j < 16; j++) + idx++; + + features[idx++].value = mss; + features[idx++].value = sackok; + features[idx++].value = wscale; + if (mss != 0 && mss != -1) + features[idx++].value = (float)tcp->getWindow() / mss; + else + features[idx++].value = -1; + } + /* ICMPv6 features */ + for (i = 0; i < NELEMS(ICMPV6_PROBE_NAMES); i++) { + const char *probe_name; + + probe_name = ICMPV6_PROBE_NAMES[i]; + features[idx++].value = vectorize_icmpv6_type(resps[probe_name].getPacket()); + features[idx++].value = vectorize_icmpv6_code(resps[probe_name].getPacket()); + } + + assert(idx == nr_feature); + + if (o.debugging > 2) { + log_write(LOG_PLAIN, "v = {"); + for (i = 0; i < nr_feature; i++) + log_write(LOG_PLAIN, "%.16g, ", features[i].value); + log_write(LOG_PLAIN, "};\n"); + } + + return features; +} + +static void apply_scale(struct feature_node *features, unsigned int num_features, + const double (*scale)[2]) { + unsigned int i; + + for (i = 0; i < num_features; i++) { + double val = features[i].value; + if (val < 0) + continue; + val = (val + scale[i][0]) * scale[i][1]; + features[i].value = val; + } +} + +/* (label, prob) pairs for purpose of sorting. */ +struct label_prob { + int label; + double prob; +}; + +int label_prob_cmp(const void *a, const void *b) { + const struct label_prob *la, *lb; + + la = (struct label_prob *) a; + lb = (struct label_prob *) b; + + /* Sort descending. */ + if (la->prob > lb->prob) + return -1; + else if (la->prob < lb->prob) + return 1; + else + return 0; +} + +/* Return a measure of how much the given feature vector differs from the other + members of the class given by label. + + This can be thought of as the distance from the given feature vector to the + mean of the class in multidimensional space, after scaling. Each dimension is + further scaled by the inverse of the sample variance of that feature. This is + an approximation of the Mahalanobis distance + (https://en.wikipedia.org/wiki/Mahalanobis_distance), which normally uses a + full covariance matrix of the features. If we take the features to be + pairwise independent (which they are not), then the covariance matrix is just + the diagonal matrix containing per-feature variances, leading to the same + calculation as is done below. Using only the per-feature variances rather + than covariance matrices is to save space; it requires only n entries per + class rather than n^2, where n is the length of a feature vector. + + It happens often that a feature's variance is undefined (because there is + only one example in the class) or zero (because there are two identical + values for that feature). Both these cases are mapped to zero by train.py, + and we handle them the same way: by using a small default variance. This will + tend to make small differences count a lot (because we probably want this + fingerprint in order to expand the class), while still allowing near-perfect + matches to match. */ +static double novelty_of(const struct feature_node *features, int label) { + const double *means, *variances; + int i, nr_feature; + double sum; + + nr_feature = get_nr_feature(&FPModel); + assert(0 <= label); + assert(label < nr_feature); + + means = FPmean[label]; + variances = FPvariance[label]; + + sum = 0.0; + for (i = 0; i < nr_feature; i++) { + double d, v; + + assert(i + 1 == features[i].index); + d = features[i].value - means[i]; + v = variances[i]; + if (v == 0.0) { + /* No variance? It means that samples were identical. Substitute a default + variance. This will tend to make novelty large in these cases, which + will hopefully encourage for submissions for this class. */ + v = 0.01; + } + sum += d * d / v; + } + + return sqrt(sum); +} + +static void classify(FingerPrintResultsIPv6 *FPR) { + int nr_class, i; + struct feature_node *features; + double *values; + struct label_prob *labels; + + nr_class = get_nr_class(&FPModel); + + features = vectorize(FPR); + values = new double[nr_class]; + labels = new struct label_prob[nr_class]; + + apply_scale(features, get_nr_feature(&FPModel), FPscale); + + predict_values(&FPModel, features, values); + for (i = 0; i < nr_class; i++) { + labels[i].label = i; + labels[i].prob = 1.0 / (1.0 + exp(-values[i])); + } + qsort(labels, nr_class, sizeof(labels[0]), label_prob_cmp); + for (i = 0; i < nr_class && i < MAX_FP_RESULTS; i++) { + FPR->matches[i] = &o.os_labels_ipv6[labels[i].label]; + FPR->accuracy[i] = labels[i].prob; + FPR->num_matches = i + 1; + if (labels[i].prob >= 0.90 * labels[0].prob) + FPR->num_perfect_matches = i + 1; + if (o.debugging > 2) { + printf("%7.4f %7.4f %3u %s\n", FPR->accuracy[i] * 100, + novelty_of(features, labels[i].label), labels[i].label, FPR->matches[i]->OS_name); + } + } + if (FPR->num_perfect_matches == 0) { + FPR->overall_results = OSSCAN_NOMATCHES; + } else if (FPR->num_perfect_matches == 1) { + double novelty; + + novelty = novelty_of(features, labels[0].label); + if (o.debugging > 1) + log_write(LOG_PLAIN, "Novelty of closest match is %.3f.\n", novelty); + + if (novelty < FP_NOVELTY_THRESHOLD) { + FPR->overall_results = OSSCAN_SUCCESS; + } else { + if (o.debugging > 0) { + log_write(LOG_PLAIN, "Novelty of closest match is %.3f > %.3f; ignoring.\n", + novelty, FP_NOVELTY_THRESHOLD); + } + FPR->overall_results = OSSCAN_NOMATCHES; + FPR->num_perfect_matches = 0; + } + } else { + FPR->overall_results = OSSCAN_NOMATCHES; + FPR->num_perfect_matches = 0; + } + + delete[] features; + delete[] values; + delete[] labels; +} + + +/* This method is the core of the FPEngine class. It takes a list of IPv6 + * targets that need to be fingerprinted. The method handles the whole + * fingerprinting process, sending probes, collecting responses, analyzing + * results and matching fingerprints. If everything goes well, the internal + * state of the supplied target objects will be modified to reflect the results + * of the */ +int FPEngine6::os_scan(std::vector &Targets) { + bool osscan_done = false; + const char *bpf_filter = NULL; + std::vector curr_hosts; /* Hosts currently doing OS detection */ + std::vector done_hosts; /* Hosts for which we already did OSdetect */ + std::vector left_hosts; /* Hosts we have not yet started with */ + struct timeval begin_time; + + if (o.debugging) + log_write(LOG_PLAIN, "Starting IPv6 OS Scan...\n"); + + /* Initialize variables, timers, etc. */ + gettimeofday(&begin_time, NULL); + global_netctl.init(Targets[0]->deviceName(), Targets[0]->ifType()); + for (size_t i = 0; i < Targets.size(); i++) { + if (o.debugging > 3) { + log_write(LOG_PLAIN, "[FPEngine] Allocating FPHost6 for %s %s\n", + Targets[i]->targetipstr(), Targets[i]->sourceipstr()); + } + FPHost6 *newhost = new FPHost6(Targets[i], &global_netctl); + newhost->begin_time = begin_time; + fphosts.push_back(newhost); + } + + /* Build the BPF filter */ + bpf_filter = this->bpf_filter(Targets); + if (o.debugging) + log_write(LOG_PLAIN, "[FPEngine] Interface=%s BPF:%s\n", Targets[0]->deviceName(), bpf_filter); + + /* Set up the sniffer */ + global_netctl.setup_sniffer(Targets[0]->deviceName(), bpf_filter); + + /* Divide the targets into two groups, the ones we are going to start + * processing, and the ones we leave for later. */ + for (size_t i = 0; i < Targets.size() && i < this->osgroup_size; i++) { + curr_hosts.push_back(fphosts[i]); + } + for (size_t i = curr_hosts.size(); i < Targets.size(); i++) { + left_hosts.push_back(fphosts[i]); + } + + /* Do the OS detection rounds */ + while (!osscan_done) { + osscan_done = true; /* It will remain true only when all hosts are .done() */ + if (o.debugging > 3) { + log_write(LOG_PLAIN, "[FPEngine] CurrHosts=%d, LeftHosts=%d, DoneHosts=%d\n", + (int) curr_hosts.size(), (int) left_hosts.size(), (int) done_hosts.size()); + } + +#ifdef WIN32 + // Reset system idle timer to avoid going to sleep + SetThreadExecutionState(ES_SYSTEM_REQUIRED); +#endif + /* Go through the list of hosts and ask them to schedule their probes */ + for (unsigned int i = 0; i < curr_hosts.size(); i++) { + + /* If the host is not done yet, call schedule() to let it schedule + * new probes, retransmissions, etc. */ + if (!curr_hosts[i]->done()) { + osscan_done = false; + curr_hosts[i]->schedule(); + if (o.debugging > 3) + log_write(LOG_PLAIN, "[FPEngine] CurrHost #%u not done\n", i); + + /* If the host is done, take it out of the curr_hosts group and add it + * to the done_hosts group. If we still have hosts left in the left_hosts + * group, take the first one and insert it into curr_hosts. This way we + * always have a full working group of hosts (unless we ran out of hosts, + * of course). */ + } else { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[FPEngine] CurrHost #%u done\n", i); + if (o.debugging > 3) + log_write(LOG_PLAIN, "[FPEngine] Moving done host %u to the done_hosts list\n", i); + done_hosts.push_back(curr_hosts[i]); + curr_hosts.erase(curr_hosts.begin() + i); + + /* If we still have hosts left, add one to the current group */ + if (left_hosts.size() > 0) { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[FPEngine] Inserting one new hosts in the curr_hosts list.\n"); + curr_hosts.push_back(left_hosts[0]); + left_hosts.erase(left_hosts.begin()); + osscan_done = false; + } + + i--; /* Decrement i so we don't miss the host that is now in the + * position of the host we've just removed from the list */ + } + } + + /* Handle scheduled events */ + global_netctl.handle_events(); + + } + + /* Once we've finished with all fphosts, check which ones were correctly + * fingerprinted, and update the Target objects. */ + for (size_t i = 0; i < this->fphosts.size(); i++) { + fphosts[i]->finish(); + + fphosts[i]->fill_FPR((FingerPrintResultsIPv6 *) Targets[i]->FPR); + classify((FingerPrintResultsIPv6 *) Targets[i]->FPR); + } + + /* Cleanup and return */ + while (this->fphosts.size() > 0) { + FPHost6 *tmp = fphosts.back(); + delete tmp; + fphosts.pop_back(); + } + + if (o.debugging) + log_write(LOG_PLAIN, "IPv6 OS Scan completed.\n"); + return OP_SUCCESS; +} + + +/****************************************************************************** + * Implementation of class FPHost. * + ******************************************************************************/ +FPHost::FPHost() { + this->__reset(); +} + + +FPHost::~FPHost() { + +} + + +void FPHost::__reset() { + this->total_probes = 0; + this->timed_probes = 0; + this->probes_sent = 0; + this->probes_answered = 0; + this->probes_unanswered = 0; + this->incomplete_fp = false; + this->detection_done = false; + this->timedprobes_sent = false; + this->target_host = NULL; + this->netctl = NULL; + this->netctl_registered = false; + this->tcpSeqBase = 0; + this->open_port_tcp = -1; + this->closed_port_tcp = -1; + this->closed_port_udp = -1; + this->tcp_port_base = -1; + this->udp_port_base = -1; + /* Retransmission time-out parameters. + * + * From RFC 2988: + * Until a round-trip time (RTT) measurement has been made for a segment + * sent between the sender and receiver, the sender SHOULD set + * RTO <- 3 seconds */ + this->rto = OSSCAN_INITIAL_RTO; + this->rttvar = -1; + this->srtt = -1; + + this->begin_time.tv_sec = 0; + this->begin_time.tv_usec = 0; +} + + +/* Returns the IP address of the target associated with the FPHost in + * struct sockaddr_storage format. */ +const struct sockaddr_storage *FPHost::getTargetAddress() { + return this->target_host->TargetSockAddr(); +} + +/* Marks one probe as unanswerable, making the fingerprint incomplete and + * ineligible for submission */ +void FPHost::fail_one_probe() { + this->probes_unanswered++; + this->incomplete_fp = true; +} + +/* Accesses the Target object associated with the FPHost to extract the port + * numbers to be used in OS detection. In particular it extracts: + * + * - An open TCP port. + * - A closed TCP port. + * - A closed UDP port. + * + * When not enough information is found in the Target, the necessary port + * numbers are generated randomly. */ +int FPHost::choose_osscan_ports() { + Port *tport = NULL; + Port port; + /* Choose an open TCP port: First, check if the host already has a + * FingerPrintResults object that defines an open port. */ + if (this->target_host->FPR != NULL && this->target_host->FPR->osscan_opentcpport > 0) { + this->open_port_tcp = this->target_host->FPR->osscan_opentcpport; + + /* Otherwise, get the first open port that we've found open */ + } else if ((tport = this->target_host->ports.nextPort(NULL, &port, IPPROTO_TCP, PORT_OPEN))) { + this->open_port_tcp = tport->portno; + /* If it is zero, let's try another one if there is one */ + if (tport->portno == 0) { + if ((tport = this->target_host->ports.nextPort(tport, &port, IPPROTO_TCP, PORT_OPEN))) + this->open_port_tcp = tport->portno; + } + if (this->target_host->FPR != NULL) { + this->target_host->FPR->osscan_opentcpport = this->open_port_tcp; + } + } else { + /* If we don't have an open port, set it to -1 so we don't send probes that + * target TCP open ports */ + this->open_port_tcp = -1; + } + + /* Choose a closed TCP port. */ + if (this->target_host->FPR != NULL && this->target_host->FPR->osscan_closedtcpport > 0) { + this->closed_port_tcp = this->target_host->FPR->osscan_closedtcpport; + } else if ((tport = this->target_host->ports.nextPort(NULL, &port, IPPROTO_TCP, PORT_CLOSED))) { + this->closed_port_tcp = tport->portno; + /* If it is zero, let's try another one if there is one */ + if (tport->portno == 0) + if ((tport = this->target_host->ports.nextPort(tport, &port, IPPROTO_TCP, PORT_CLOSED))) + this->closed_port_tcp = tport->portno; + if (this->target_host->FPR != NULL) { + this->target_host->FPR->osscan_closedtcpport = this->closed_port_tcp; + } + } else if ((tport = this->target_host->ports.nextPort(NULL, &port, IPPROTO_TCP, PORT_UNFILTERED))) { + /* Well, we will settle for unfiltered */ + this->closed_port_tcp = tport->portno; + /* But again we'd prefer not to have zero */ + if (tport->portno == 0) + if ((tport = this->target_host->ports.nextPort(tport, &port, IPPROTO_TCP, PORT_UNFILTERED))) + this->closed_port_tcp = tport->portno; + } else { + /* If we don't have a closed port, set it to -1 so we don't send probes that + * target TCP closed ports. */ + this->closed_port_tcp = -1; + } + + /* Closed UDP port */ + if (this->target_host->FPR != NULL && this->target_host->FPR->osscan_closedudpport > 0) { + this->closed_port_udp = this->target_host->FPR->osscan_closedudpport; + } else if ((tport = this->target_host->ports.nextPort(NULL, &port, IPPROTO_UDP, PORT_CLOSED))) { + this->closed_port_udp = tport->portno; + /* Not zero, if possible */ + if (tport->portno == 0) + if ((tport = this->target_host->ports.nextPort(tport, &port, IPPROTO_UDP, PORT_CLOSED))) + this->closed_port_udp = tport->portno; + if (this->target_host->FPR != NULL) { + this->target_host->FPR->osscan_closedudpport = this->closed_port_udp; + } + } else if ((tport = this->target_host->ports.nextPort(NULL, &port, IPPROTO_UDP, PORT_UNFILTERED))) { + /* Well, we will settle for unfiltered */ + this->closed_port_udp = tport->portno; + /* But not zero, please */ + if (tport->portno == 0) + if ((tport = this->target_host->ports.nextPort(NULL, &port, IPPROTO_UDP, PORT_UNFILTERED))) + this->closed_port_udp = tport->portno; + } else { + /* Pick one at random. Shrug. */ + this->closed_port_udp = (get_random_uint() % 14781) + 30000; + } + + this->tcpSeqBase = get_random_u32(); + this->tcp_port_base = o.magic_port_set ? o.magic_port : o.magic_port + get_random_u8(); + this->udp_port_base = o.magic_port_set ? o.magic_port : o.magic_port + get_random_u8(); + this->icmp_seq_counter = 0; + + return OP_SUCCESS; +} + + +/* This method is called whenever we receive a response to a probe. It + * recomputes the host's retransmission timer based on the new RTT measure. + * @param measured_rtt_usecs is the new RTT observation in MICROseconds. + * @param retransmission indicates whether the observed RTT correspond to + * a packet that was transmitted more than once or not. It is used to + * avoid using RTT samples obtained from retransmissions (Karn's algorithm) */ +int FPHost::update_RTO(int measured_rtt_usecs, bool retransmission) { +/* RFC 2988: TCP MUST use Karn's algorithm [KP87] for taking RTT samples. That + * is, RTT samples MUST NOT be made using segments that were + * retransmitted (and thus for which it is ambiguous whether the reply + * was for the first instance of the packet or a later instance).*/ + if (retransmission == true) + return OP_SUCCESS; + +/* RFC 2988: When the first RTT measurement R is made, the host MUST set + * + * SRTT <- R + * RTTVAR <- R/2 + * RTO <- SRTT + max (G, K*RTTVAR) + * + * where K = 4, and G is the clock granularity.. */ + if (this->srtt == -1 && this->rttvar == -1) { + this->srtt = measured_rtt_usecs; + this->rttvar = measured_rtt_usecs/2; + this->rto = this->srtt + MAX(500000, 4*this->rttvar); /* Assume a granularity of 1/2 sec */ + } else { + + /* RFC 2988: When a subsequent RTT measurement R' is made, a host MUST set + * + * RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R'| + * SRTT <- (1 - alpha) * SRTT + alpha * R' + * + * The above SHOULD be computed using alpha = 1/8 and beta = 1/4. + * After the computation, a host MUST update + * + * RTO <- SRTT + max (G, K*RTTVAR) + */ + this->rttvar += (ABS(this->srtt - measured_rtt_usecs) - this->rttvar) >> 2; + this->srtt += (measured_rtt_usecs - this->srtt) >> 3; + this->rto = this->srtt + MAX(500000, 4*this->rttvar); + } + +/* RFC 2988: Whenever RTO is computed, if it is less than 1 second then the RTO + * SHOULD be rounded up to 1 second. + * [NOTE: In Nmap we find this excessive, so we set a minimum of 100ms + * (100,000 usecs). It may seem aggressive but waiting too long can cause + * the engine to fail to detect drops until many probes later on extremely + * low-latency networks (such as localhost scans). */ + if (this->rto < (MIN_RTT_TIMEOUT*1000)) + this->rto = (MIN_RTT_TIMEOUT*1000); + return this->rto; +} + + +/****************************************************************************** + * Implementation of class FPHost6. * + ******************************************************************************/ + +FPHost6::FPHost6(Target *tgt, FPNetworkControl *fpnc) { + this->init(tgt, fpnc); + return; +} + + +FPHost6::~FPHost6() { + this->reset(); +} + + +void FPHost6::reset() { + this->__reset(); + for (unsigned int i = 0; i < NUM_FP_PROBES_IPv6; i++) { + this->fp_probes[i].reset(); + if (this->fp_responses[i]) { + delete this->fp_responses[i]; + this->fp_responses[i] = NULL; + } + } +} + + +void FPHost6::init(Target *tgt, FPNetworkControl *fpnc) { + this->target_host = tgt; + this->netctl = fpnc; + this->total_probes = 0; + this->timed_probes = 0; + + /* Set state in the supplied Target */ + if (this->target_host->FPR == NULL) + this->target_host->FPR = new FingerPrintResultsIPv6; + this->target_host->osscanSetFlag(OS_PERF); + + /* Choose TCP/UDP ports for the probes. */ + this->choose_osscan_ports(); + + /* Build the list of OS detection probes */ + this->build_probe_list(); + + for (unsigned int i = 0; i < NUM_FP_PROBES_IPv6; i++) + this->fp_responses[i] = NULL; + + for (unsigned int i = 0; i < NUM_FP_TIMEDPROBES_IPv6; i++) + this->aux_resp[i] = NULL; +} + +/* Get the hop limit encapsulated in an ICMPv6 error reply. Return -1 if it + * can't be found. */ +static int get_encapsulated_hoplimit(const PacketElement *pe) { + /* Check that it's IPv6. */ + if (pe == NULL || pe->protocol_id() != HEADER_TYPE_IPv6) + return -1; + /* Find the ICMPv6 payload. */ + pe = pe->getNextElement(); + for (; pe != NULL; pe = pe->getNextElement()) { + if (pe->protocol_id() == HEADER_TYPE_ICMPv6) + break; + } + if (pe == NULL) + return -1; + /* Check that encapsulated is IPv6. */ + pe = pe->getNextElement(); + if (pe == NULL || pe->protocol_id() != HEADER_TYPE_IPv6) + return -1; + + return ((IPv6Header *) pe)->getHopLimit(); +} + +void FPHost6::finish() { + /* These probes are likely to get an ICMPv6 error (allowing us to calculate + distance. */ + const char * const DISTANCE_PROBE_NAMES[] = { "IE2", "U1" }; + int distance = -1; + int hoplimit_distance = -1; + enum dist_calc_method distance_calculation_method = DIST_METHOD_NONE; + unsigned int i; + + /* Calculate distance based on hop limit difference. */ + for (i = 0; i < NELEMS(DISTANCE_PROBE_NAMES); i++) { + const FPProbe *probe; + const FPResponse *resp; + const PacketElement *probe_pe; + PacketElement *resp_pe; + int sent_ttl, rcvd_ttl; + const char *probe_name; + + probe_name = DISTANCE_PROBE_NAMES[i]; + probe = this->getProbe(probe_name); + resp = this->getResponse(probe_name); + if (probe == NULL || resp == NULL) + continue; + probe_pe = probe->getPacket(); + if (probe_pe->protocol_id() != HEADER_TYPE_IPv6) + continue; + sent_ttl = ((IPv6Header *) probe_pe)->getHopLimit(); + + resp_pe = PacketParser::split(resp->buf, resp->len); + assert(resp_pe != NULL); + rcvd_ttl = get_encapsulated_hoplimit(resp_pe); + if (rcvd_ttl != -1) { + if (o.debugging > 1) { + log_write(LOG_PLAIN, "Hop limit distance from %s probe: %d - %d + 1 == %d\n", + probe_name, sent_ttl, rcvd_ttl, sent_ttl - rcvd_ttl + 1); + } + /* Set only if not already set. */ + if (hoplimit_distance == -1) + hoplimit_distance = sent_ttl - rcvd_ttl + 1; + + /* Special case: for the U1 probe, mark that we found the port closed. */ + if (this->target_host->FPR->osscan_closedudpport == -1 && strcmp(probe_name, "U1") == 0) { + const PacketElement *udp; + u16 portno; + + udp = probe_pe->getNextElement(); + assert(udp != NULL); + assert(udp->protocol_id() == HEADER_TYPE_UDP); + portno = ((UDPHeader *) udp)->getDestinationPort(); + this->target_host->FPR->osscan_closedudpport = portno; + } + } + PacketParser::freePacketChain(resp_pe); + } + + if (islocalhost(this->target_host->TargetSockAddr())) { + /* scanning localhost */ + distance = 0; + distance_calculation_method = DIST_METHOD_LOCALHOST; + } else if (this->target_host->directlyConnected()) { + /* on the same network segment */ + distance = 1; + distance_calculation_method = DIST_METHOD_DIRECT; + } else if (hoplimit_distance != -1) { + distance = hoplimit_distance; + distance_calculation_method = DIST_METHOD_ICMP; + } + + this->target_host->distance = this->target_host->FPR->distance = distance; + this->target_host->distance_calculation_method = + this->target_host->FPR->distance_calculation_method = + distance_calculation_method; +} + +struct tcp_desc { + const char *id; + u16 win; + u8 flags; + u16 dstport; + u16 urgptr; + const char *opts; + unsigned int optslen; +}; + +static u8 get_hoplimit() { + if (o.ttl != -1) + return o.ttl; + else + return (get_random_uint() % 23) + 37; +} + +static IPv6Header *make_tcp(const struct sockaddr_in6 *src, + const struct sockaddr_in6 *dst, + u32 fl, u16 win, u32 seq, u32 ack, u8 flags, u16 srcport, u16 dstport, + u16 urgptr, const char *opts, unsigned int optslen) { + IPv6Header *ip6; + TCPHeader *tcp; + + /* Allocate an instance of the protocol headers */ + ip6 = new IPv6Header(); + tcp = new TCPHeader(); + + ip6->setSourceAddress(src->sin6_addr); + ip6->setDestinationAddress(dst->sin6_addr); + + ip6->setFlowLabel(fl); + ip6->setHopLimit(get_hoplimit()); + ip6->setNextHeader("TCP"); + ip6->setNextElement(tcp); + + tcp->setWindow(win); + tcp->setSeq(seq); + tcp->setAck(ack); + tcp->setFlags(flags); + tcp->setSourcePort(srcport); + tcp->setDestinationPort(dstport); + tcp->setUrgPointer(urgptr); + tcp->setOptions((u8 *) opts, optslen); + + ip6->setPayloadLength(tcp->getLen()); + tcp->setSum(); + + return ip6; +} + +/* This method generates the list of OS detection probes to be sent to the + * target. It also sets up the list of responses. It is defined private + * because it is called by the constructor when the class is instantiated. */ +int FPHost6::build_probe_list() { +#define OPEN 1 +#define CLSD 0 + /* TCP Options: + * S1-S6: six sequencing probes. + * TECN: ECN probe. + * T2-T7: other non-sequencing probes. + * + * option 0: WScale (10), Nop, MSS (1460), Timestamp, SackP + * option 1: MSS (1400), WScale (0), SackP, T(0xFFFFFFFF,0x0), EOL + * option 2: T(0xFFFFFFFF, 0x0), Nop, Nop, WScale (5), Nop, MSS (640) + * option 3: SackP, T(0xFFFFFFFF,0x0), WScale (10), EOL + * option 4: MSS (536), SackP, T(0xFFFFFFFF,0x0), WScale (10), EOL + * option 5: MSS (265), SackP, T(0xFFFFFFFF,0x0) + * option 6: WScale (10), Nop, MSS (1460), SackP, Nop, Nop + * option 7-11: WScale (10), Nop, MSS (265), T(0xFFFFFFFF,0x0), SackP + * option 12: WScale (15), Nop, MSS (265), T(0xFFFFFFFF,0x0), SackP */ + const struct tcp_desc TCP_DESCS[] = { + { "S1", 1, 0x02, OPEN, 0, + "\x03\x03\x0A\x01\x02\x04\x05\xb4\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + { "S2", 63, 0x02, OPEN, 0, + "\x02\x04\x05\x78\x03\x03\x00\x04\x02\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x00", 20 }, + { "S3", 4, 0x02, OPEN, 0, + "\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x01\x01\x03\x03\x05\x01\x02\x04\x02\x80", 20 }, + { "S4", 4, 0x02, OPEN, 0, + "\x04\x02\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x03\x03\x0A\x00", 16 }, + { "S5", 16, 0x02, OPEN, 0, + "\x02\x04\x02\x18\x04\x02\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x03\x03\x0A\x00", 20 }, + { "S6", 512, 0x02, OPEN, 0, + "\x02\x04\x01\x09\x04\x02\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00", 16 }, + { "TECN", 3, 0xc2, OPEN, 63477, + "\x03\x03\x0A\x01\x02\x04\x05\xb4\x04\x02\x01\x01", 12 }, + { "T2", 128, 0x00, OPEN, 0, + "\x03\x03\x0A\x01\x02\x04\x01\x09\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + { "T3", 256, 0x2b, OPEN, 0, + "\x03\x03\x0A\x01\x02\x04\x01\x09\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + { "T4", 1024, 0x10, OPEN, 0, + "\x03\x03\x0A\x01\x02\x04\x01\x09\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + { "T5", 31337, 0x02, CLSD, 0, + "\x03\x03\x0A\x01\x02\x04\x01\x09\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + { "T6", 32768, 0x10, CLSD, 0, + "\x03\x03\x0A\x01\x02\x04\x01\x09\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + { "T7", 65535, 0x29, CLSD, 0, + "\x03\x03\x0f\x01\x02\x04\x01\x09\x08\x0A\xff\xff\xff\xff\x00\x00\x00\x00\x04\x02", 20 }, + }; + + const sockaddr_in6 *ss6 = NULL; + IPv6Header *ip6; + ICMPv6Header *icmp6; + UDPHeader *udp; + DestOptsHeader *dstopts; + RoutingHeader *routing; + HopByHopHeader *hopbyhop1, *hopbyhop2; + RawData *payload; + int i; + char payloadbuf[300]; + + assert(this->target_host != NULL); + + /* Set timed TCP probes */ + for (i = 0; i < NUM_FP_PROBES_IPv6_TCP && i < NUM_FP_TIMEDPROBES_IPv6; i++) { + /* If the probe is targeted to a TCP port and we don't have + * any port number for that particular state, skip the probe. */ + if (TCP_DESCS[i].dstport == OPEN && this->open_port_tcp < 0) + continue; + if (TCP_DESCS[i].dstport == CLSD && this->closed_port_tcp < 0) + continue; + + ip6 = make_tcp((struct sockaddr_in6 *) this->target_host->SourceSockAddr(), + (struct sockaddr_in6 *) this->target_host->TargetSockAddr(), + OSDETECT_FLOW_LABEL, TCP_DESCS[i].win, this->tcpSeqBase + i, get_random_u32(), + TCP_DESCS[i].flags, this->tcp_port_base + i, + TCP_DESCS[i].dstport == OPEN ? this->open_port_tcp : this->closed_port_tcp, + TCP_DESCS[i].urgptr, TCP_DESCS[i].opts, TCP_DESCS[i].optslen); + + /* Store the probe in the list so we can send it later */ + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID(TCP_DESCS[i].id); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + /* Mark as a timed probe. */ + this->fp_probes[this->total_probes].setTimed(); + this->timed_probes++; + this->total_probes++; + } + + + /* Set ICMPv6 probes */ + + memset(payloadbuf, 0, 120); + + /* ICMP Probe #1: Echo Request with hop-by-hop options */ + /* This one immediately follows the timed seq TCP probes, to allow testing for + shared flow label sequence. */ + ip6 = new IPv6Header(); + icmp6 = new ICMPv6Header(); + hopbyhop1 = new HopByHopHeader(); + payload = new RawData(); + ss6 = (const sockaddr_in6 *) this->target_host->SourceSockAddr(); + ip6->setSourceAddress(ss6->sin6_addr); + ss6 = (const sockaddr_in6 *) this->target_host->TargetSockAddr(); + ip6->setDestinationAddress(ss6->sin6_addr); + ip6->setFlowLabel(OSDETECT_FLOW_LABEL); + ip6->setHopLimit(get_hoplimit()); + ip6->setNextHeader((u8) HEADER_TYPE_IPv6_HOPOPT); + ip6->setNextElement(hopbyhop1); + hopbyhop1->setNextHeader(HEADER_TYPE_ICMPv6); + hopbyhop1->setNextElement(icmp6); + icmp6->setNextElement(payload); + payload->store((u8 *) payloadbuf, 120); + icmp6->setType(ICMPv6_ECHO); + icmp6->setCode(9); // But is supposed to be 0. + icmp6->setIdentifier(0xabcd); + icmp6->setSequence(this->icmp_seq_counter++); + icmp6->setTargetAddress(ss6->sin6_addr); // Should still contain target's addr + ip6->setPayloadLength(); + icmp6->setSum(); + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID("IE1"); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + this->total_probes++; + + /* ICMP Probe #2: Echo Request with badly ordered extension headers */ + ip6 = new IPv6Header(); + hopbyhop1 = new HopByHopHeader(); + dstopts = new DestOptsHeader(); + routing = new RoutingHeader(); + hopbyhop2 = new HopByHopHeader(); + icmp6 = new ICMPv6Header(); + payload = new RawData(); + ss6 = (const sockaddr_in6 *) this->target_host->SourceSockAddr(); + ip6->setSourceAddress(ss6->sin6_addr); + ss6 = (const sockaddr_in6 *) this->target_host->TargetSockAddr(); + ip6->setDestinationAddress(ss6->sin6_addr); + ip6->setFlowLabel(OSDETECT_FLOW_LABEL); + ip6->setHopLimit(get_hoplimit()); + ip6->setNextHeader((u8) HEADER_TYPE_IPv6_HOPOPT); + ip6->setNextElement(hopbyhop1); + hopbyhop1->setNextHeader(HEADER_TYPE_IPv6_OPTS); + hopbyhop1->setNextElement(dstopts); + dstopts->setNextHeader(HEADER_TYPE_IPv6_ROUTE); + dstopts->setNextElement(routing); + routing->setNextHeader(HEADER_TYPE_IPv6_HOPOPT); + routing->setNextElement(hopbyhop2); + hopbyhop2->setNextHeader(HEADER_TYPE_ICMPv6); + hopbyhop2->setNextElement(icmp6); + icmp6->setType(ICMPv6_ECHO); + icmp6->setCode(0); + icmp6->setIdentifier(0xabcd); + icmp6->setSequence(this->icmp_seq_counter++); + icmp6->setTargetAddress(ss6->sin6_addr); // Should still contain target's addr + ip6->setPayloadLength(); + icmp6->setSum(); + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID("IE2"); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + this->total_probes++; + + /* ICMP Probe #3: Neighbor Solicitation. (only sent to on-link targets) */ + if (this->target_host->directlyConnected() +#ifdef WIN32 + && !(g_has_npcap_loopback && this->target_host->ifType() == devt_loopback) +#endif + ) { + ip6 = new IPv6Header(); + icmp6 = new ICMPv6Header(); + ss6 = (const sockaddr_in6 *) this->target_host->SourceSockAddr(); + ip6->setSourceAddress(ss6->sin6_addr); + ss6 = (const sockaddr_in6 *) this->target_host->TargetSockAddr(); + ip6->setDestinationAddress(ss6->sin6_addr); + ip6->setFlowLabel(OSDETECT_FLOW_LABEL); + /* RFC 2461 section 7.1.1: "A node MUST silently discard any received + Neighbor Solicitation messages that do not satisfy all of the following + validity checks: - The IP Hop Limit field has a value of 255 ... */ + ip6->setHopLimit(255); + ip6->setNextHeader("ICMPv6"); + ip6->setNextElement(icmp6); + icmp6->setType(ICMPv6_NGHBRSOLICIT); + icmp6->setCode(0); + icmp6->setTargetAddress(ss6->sin6_addr); // Should still contain target's addr + icmp6->setSum(); + ip6->setPayloadLength(); + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID("NS"); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + this->total_probes++; + } + + /* Set UDP probes */ + + memset(payloadbuf, 0x43, 300); + + ip6 = new IPv6Header(); + udp = new UDPHeader(); + payload = new RawData(); + ss6 = (const sockaddr_in6 *) this->target_host->SourceSockAddr(); + ip6->setSourceAddress(ss6->sin6_addr); + ss6 = (const sockaddr_in6 *) this->target_host->TargetSockAddr(); + ip6->setDestinationAddress(ss6->sin6_addr); + ip6->setFlowLabel(OSDETECT_FLOW_LABEL); + ip6->setHopLimit(get_hoplimit()); + ip6->setNextHeader("UDP"); + ip6->setNextElement(udp); + udp->setSourcePort(this->udp_port_base); + udp->setDestinationPort(this->closed_port_udp); + payload->store((u8 *) payloadbuf, 300); + udp->setNextElement(payload); + udp->setTotalLength(); + udp->setSum(); + ip6->setPayloadLength(udp->getLen()); + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID("U1"); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + this->total_probes++; + + /* Set TECN probe */ + if ((TCP_DESCS[i].dstport == OPEN && this->open_port_tcp >= 0) + || (TCP_DESCS[i].dstport == CLSD && this->closed_port_tcp >= 0)) { + ip6 = make_tcp((struct sockaddr_in6 *) this->target_host->SourceSockAddr(), + (struct sockaddr_in6 *) this->target_host->TargetSockAddr(), + OSDETECT_FLOW_LABEL, TCP_DESCS[i].win, this->tcpSeqBase + i, 0, + TCP_DESCS[i].flags, tcp_port_base + i, + TCP_DESCS[i].dstport == OPEN ? this->open_port_tcp : this->closed_port_tcp, + TCP_DESCS[i].urgptr, TCP_DESCS[i].opts, TCP_DESCS[i].optslen); + + /* Store the probe in the list so we can send it later */ + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID(TCP_DESCS[i].id); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + this->total_probes++; + } + i++; + + /* Set untimed TCP probes */ + for (; i < NUM_FP_PROBES_IPv6_TCP; i++) { + /* If the probe is targeted to a TCP port and we don't have + * any port number for that particular state, skip the probe. */ + if (TCP_DESCS[i].dstport == OPEN && this->open_port_tcp < 0) + continue; + if (TCP_DESCS[i].dstport == CLSD && this->closed_port_tcp < 0) + continue; + + ip6 = make_tcp((struct sockaddr_in6 *) this->target_host->SourceSockAddr(), + (struct sockaddr_in6 *) this->target_host->TargetSockAddr(), + OSDETECT_FLOW_LABEL, TCP_DESCS[i].win, this->tcpSeqBase + i, get_random_u32(), + TCP_DESCS[i].flags, tcp_port_base + i, + TCP_DESCS[i].dstport == OPEN ? this->open_port_tcp : this->closed_port_tcp, + TCP_DESCS[i].urgptr, TCP_DESCS[i].opts, TCP_DESCS[i].optslen); + + /* Store the probe in the list so we can send it later */ + this->fp_probes[this->total_probes].host = this; + this->fp_probes[this->total_probes].setPacket(ip6); + this->fp_probes[this->total_probes].setProbeID(TCP_DESCS[i].id); + this->fp_probes[this->total_probes].setEthernet(this->target_host->SrcMACAddress(), this->target_host->NextHopMACAddress(), this->target_host->deviceName()); + this->total_probes++; + } + + return OP_SUCCESS; +} + +/* Indicates whether the OS detection process has finished for this host. + * Note that when "true" is returned the caller cannot assume that the host + * has been accurately fingerprinted, only that the OS detection process + * was carried out. In other words, when true is returned it means that the + * fingerprinting engine sent all OS detection probes, performed the necessary + * retransmission and attempted to capture the target's replies. In order to + * check if the detection was successful (if we actually know what OS the target + * is running), the status() method should be used. */ +bool FPHost6::done() { + if (this->probes_sent == this->total_probes) { + if (this->probes_answered + this->probes_unanswered == this->total_probes) + return true; + } + return false; +} + + +/* Asks the host to schedule the transmission of probes (if they need to do so). + * This method is called repeatedly by the FPEngine to make the host request + * the probe transmissions that it needs. From the hosts point of view, it + * determines if new transmissions need to be scheduled based on the number + * of probes sent, the number of answers received, etc. Also, in order to + * transmit a packet, the network controller must approve it (hosts may not + * be able to send packets any time they want due to congestion control + * restrictions). */ +int FPHost6::schedule() { + struct timeval now; + unsigned int timed_probes_answered = 0; + unsigned int timed_probes_timedout = 0; + + /* The first time we are asked to schedule a packet, register ourselves in + * the network controller so it can call us back when packets that match our + * target are captured. */ + if (this->netctl_registered == false && this->netctl != NULL) { + this->netctl->register_caller(this); + this->netctl_registered = true; + } + + /* Make sure we have things to do, otherwise, just return. */ + if (this->detection_done || (this->probes_answered + this->probes_unanswered == this->total_probes)) { + /* Update our internal state to indicate we have finished */ + if (!this->detection_done) + this->set_done_and_wrap_up(); + return OP_SUCCESS; + } + + /* If we have not yet sent the timed probes (and we have timed probes to send) + * request permission from the network controller and schedule the transmission + * for all of them, 100ms apart from each other. We don't want all the hosts + * to schedule their transmission for the same exact time so we add a random + * offset (between 0 and 100ms) to the first transmission. All subsequent + * ones are sent 100ms apart from the first. Note that if we did not find + * and open port, then we just don't send the timed probes. */ + if (this->timed_probes > 0 && this->timedprobes_sent == false) { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] %u Tx slots requested\n", this->target_host->targetipstr(), this->timed_probes); + if (this->netctl->request_slots(this->timed_probes) == true) { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Slots granted!\n", this->target_host->targetipstr()); + this->timedprobes_sent = true; + int whentostart = get_random_u16()%100; + for (size_t i = 0; i < this->timed_probes; i++) { + this->netctl->scheduleProbe(&(this->fp_probes[i]), whentostart + i*100); + this->probes_sent++; + } + return OP_SUCCESS; + } + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Slots denied.\n", this->target_host->targetipstr()); + return OP_FAILURE; + } else if (this->timed_probes > 0 && this->timedprobes_sent && this->fp_probes[this->timed_probes - 1].getTimeSent().tv_sec == 0) { + /* If the sent time for the last timed probe has not been set, it means + * that we haven't sent all the timed probes yet, so we don't schedule + * any other probes, we just wait until our schedule() gets called again. + * We do this because we don't want to mess with the target's stack + * in the middle of our timed probes. Otherwise, we can screw up the + * TCP sequence generation tests, etc. We also get here when timed probes + * suffer a retransmission. In that case, we also stop sending packets + * to our target until we have sent all of them. */ + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Waiting for all timed probes to be sent...\n", this->target_host->targetipstr()); + return OP_FAILURE; + } else { + /* If we get here it means that either we have sent all the timed probes or + * we don't even have to send them (because no open port was found). + * At this point if we have other probes to transmit, schedule the next one. + * Also, check for timedout probes so we can retransmit one of them. */ + if (o.debugging > 3 && this->timed_probes > 0 && this->probes_sent == this->timed_probes) + log_write(LOG_PLAIN, "[%s] All timed probes have been sent.\n", this->target_host->targetipstr()); + + if (this->probes_sent < this->total_probes) { + if (this->netctl->request_slots(1) == true) { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Scheduling probe %s\n", this->target_host->targetipstr(), this->fp_probes[this->probes_sent].getProbeID()); + this->netctl->scheduleProbe(&(this->fp_probes[this->probes_sent]), 0); + this->probes_sent++; + } else { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Can't schedule probe %s\n", this->target_host->targetipstr(), this->fp_probes[this->probes_sent].getProbeID()); + } + } + + /************************************************************************** + * PROBE TIMEOUT HANDLING * + **************************************************************************/ + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Checking for regular probe timeouts...\n", this->target_host->targetipstr()); + + /* Determine if some regular probe (not timed probes) has timedout. In that + * case, choose some outstanding probe to retransmit. */ + gettimeofday(&now, NULL); + for (unsigned int i = this->timed_probes; i < this->probes_sent; i++) { + + /* Skip probes that have already been answered */ + if (this->fp_responses[i]) { + continue; + } + + /* Skip probes that we have scheduled but have not been yet transmitted */ + if (this->fp_probes[i].getTimeSent().tv_sec == 0) + continue; + + /* Skip probes for which we didn't get a response after all + * retransmissions. */ + if (this->fp_probes[i].probeFailed()) { + continue; + } + + /* Check if the probe timedout */ + if (TIMEVAL_SUBTRACT(now, this->fp_probes[i].getTimeSent()) >= this->rto) { + + /* If we have reached the maximum number of retransmissions, mark the + * probe as failed. Otherwise, schedule its transmission. */ + if (this->fp_probes[i].getRetransmissions() >= o.maxOSTries()) { + if (o.debugging > 3) { + log_write(LOG_PLAIN, "[%s] Probe #%d (%s) failed after %d retransmissions.\n", + this->target_host->targetipstr(), i, this->fp_probes[i].getProbeID(), + this->fp_probes[i].getRetransmissions()); + } + this->fp_probes[i].setFailed(); + /* Let the network controller know that we don't expect a response + * for the probe anymore so the number of outstanding probes is + * reduced and the effective window is incremented. */ + this->netctl->cc_report_final_timeout(); + /* Also, increase our unanswered counter so we can later decide + * if the process has finished. */ + this->probes_unanswered++; + continue; + /* Otherwise, retransmit the packet.*/ + } else { + /* Note that we do not request permission to re-transmit (we don't + * call request_slots(). In TCP one can retransmit timedout + * probes even when CWND is zero, as CWND only applies for new packets. */ + if (o.debugging > 3) { + log_write(LOG_PLAIN, "[%s] Retransmitting probe #%d (%s) (retransmitted %d times already).\n", + this->target_host->targetipstr(), i, this->fp_probes[i].getProbeID(), + this->fp_probes[i].getRetransmissions()); + } + this->fp_probes[i].incrementRetransmissions(); + this->netctl->scheduleProbe(&(this->fp_probes[i]), 0); + break; + } + } + } + + /* Now let's check the state of the timed probes. We iterate over the list + * of timed probes to count how many have been answered and how many have + * timed out. If answered + timeout == total_timed_probes, it's time to + * retransmit them. */ + + /* Make sure we are actually sending timed probes. */ + if (this->timed_probes <= 0) + return OP_SUCCESS; + + bool timed_failed = false; + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Checking for timed probe timeouts...\n", this->target_host->targetipstr()); + for (unsigned int i = 0; i < this->timed_probes; i++) { + assert(this->fp_probes[i].isTimed()); + + /* Skip probes that have already been answered, but count how many of + * them are there. */ + if (this->fp_responses[i]) { + timed_probes_answered++; + continue; + } + + /* If there is some timed probe for which we have already scheduled its + * retransmission but it hasn't been sent yet, break the loop. We don't + * have to worry about retransmitting these probes yet.*/ + if (this->fp_probes[i].getTimeSent().tv_sec == 0) + return OP_SUCCESS; + + /* If we got a total timeout for any of the timed probes, we shouldn't + * attempt more retransmissions. We set a flag to indicate that but we + * still stay in the loop because we want to mark as "failed" any other + * probes we have not yet checked. */ + if (this->fp_probes[i].probeFailed()) { + timed_failed = true; + continue; + } + + /* Now check if the timed probe has timed out. If it suffered a total + * time out (max retransmissions done and still no answer) then mark + * it as such. Otherwise, count it so we can retransmit the whole + * group of timed probes later if appropriate. */ + if (TIMEVAL_SUBTRACT(now, this->fp_probes[i].getTimeSent()) >= this->rto) { + if (o.debugging > 3) { + log_write(LOG_PLAIN, "[%s] timed probe %d (%s) timedout\n", + this->target_host->targetipstr(), i, this->fp_probes[i].getProbeID()); + } + if (this->fp_probes[i].getRetransmissions() >= o.maxOSTries()) { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Timed probe #%d (%s) failed after %d retransmissions.\n", this->target_host->targetipstr(), i, this->fp_probes[i].getProbeID(), this->fp_probes[i].getRetransmissions()); + this->fp_probes[i].setFailed(); + /* Let the network controller know that we don't expect a response + * for the probe anymore so the number of outstanding probes is + * reduced and the effective window is incremented. */ + this->netctl->cc_report_final_timeout(); + /* Also, increase our unanswered counter so we can later decide + * if the process has finished. */ + this->probes_unanswered++; + } else { + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Timed probe #%d (%s) has timed out (%d retransmissions done).\n", this->target_host->targetipstr(), i, this->fp_probes[i].getProbeID(), this->fp_probes[i].getRetransmissions()); + timed_probes_timedout++; + } + } + } + + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Timed_probes=%d, answered=%u, timedout=%u\n", this->target_host->targetipstr(), this->timed_probes, timed_probes_answered, timed_probes_timedout); + + /* If the probe that has timed out is a "timed probe" it means that + * we need to retransmit all timed probes, not only this one. For + * that, we wait until all timed probes have either timed out or + * been responded. When that happens, we do the following: + * 1) Store the responses we have received the last time we sent + * the timed probes in an aux array (this->aux_resp). + * 2) Clear the responses to the timed probes from the main + * response array (this->fp_responses). + * 3) Schedule the retransmission of all timed probes, 100ms apart. */ + if (this->timed_probes > 0 && timed_failed == false && timed_probes_timedout > 0 && (timed_probes_answered + timed_probes_timedout == this->timed_probes)) { + + /* Count the number of responses we have now and the number + * of responses we stored in the aux buffer last time. */ + unsigned int responses_stored = 0; + unsigned int responses_now = 0; + for (unsigned int j = 0; j < this->timed_probes; j++) { + if (this->aux_resp[j] != NULL) + responses_stored++; + if (this->fp_responses[j] != NULL) + responses_now++; + } + + /* If now we have more responses than before, copy our current + * set of responses to the aux array. Otherwise, just + * delete the current set of responses. */ + for (unsigned int k = 0; k < this->timed_probes; k++) { + if (responses_now > responses_stored) { + /* Free previous allocations */ + if (this->aux_resp[k] != NULL) { + delete this->aux_resp[k]; + } + /* Move the current response to the aux array */ + this->aux_resp[k] = this->fp_responses[k]; + this->fp_responses[k] = NULL; + } else { + delete this->fp_responses[k]; + this->fp_responses[k] = NULL; + } + } + + /* Update answer count because now we expect new answers to the timed probes. */ + assert(((int)this->probes_answered - (int)timed_probes_answered) >= 0); + this->probes_answered-= timed_probes_answered; + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Adjusting answer count: before=%d, after=%d\n", this->target_host->targetipstr(), this->probes_answered + timed_probes_answered, this->probes_answered); + + + /* Finally do the actual retransmission. Like the first time, + * we schedule them 100ms apart, starting at same random point + * between right now and 99ms. */ + int whentostart = get_random_u16()%100; + for (size_t l = 0; l < this->timed_probes; l++) { + this->fp_probes[l].incrementRetransmissions(); + this->netctl->scheduleProbe(&(this->fp_probes[l]), whentostart + l*100); + } + if (o.debugging > 3 && this->timed_probes > 0) + log_write(LOG_PLAIN, "[%s] Retransmitting timed probes (rcvd_before=%u, rcvd_now=%u times=%d).\n", this->target_host->targetipstr(), responses_stored, responses_now, this->fp_probes[0].getRetransmissions()); + + /* Reset our local counters. */ + timed_probes_answered = 0; + timed_probes_timedout = 0; + } + } + return OP_FAILURE; +} + + +/* This method is called when we detect that the OS detection process for this + * host is completed. It basically updates the host's internal state to + * indicate that the processed finished and unregisters the host from the + * network controller so we don't get any more callbacks. Here we also handle + * the special case of the retransmitted "timed probes". When we have to + * retransmit such probes, we usually have two sets of responses: the ones we + * got for the last retransmission, and the ones we got in the best try before + * that. So what we have to do is to decide which set is the best and discard + * the other one.*/ +int FPHost6::set_done_and_wrap_up() { + assert(this->probes_answered + this->probes_unanswered == this->total_probes); + + /* Inform the network controller that we do not wish to continue + * receiving callbacks (it could happen if the system had some other + * connections established with the target) */ + this->netctl->unregister_caller(this); + + /* Set up an internal flag to indicate we have finished */ + this->detection_done = true; + + /* Check the state of the timed probe retransmissions. In particular if we + * retransmitted timed probes, we should have two sets of responses, + * the ones we got last time we retransmitted, and the best set of responses + * we got out of all previous retransmissions but the last one. So now, we + * determine which set is the best and discard the other one. Btw, none of + * these loops run if timed_probes == 0, so it's safe in all cases. */ + + /* First count the number of responses in each set. */ + unsigned int stored = 0; + unsigned int current = 0; + for (unsigned int i = 0; i < this->timed_probes; i++) { + if (this->aux_resp[i] != NULL) + stored++; + if (this->fp_responses[i] != NULL) + current++; + } + /* If we got more responses in a previous try, use them and get rid of + * the current ones. */ + if (stored > current) { + for (unsigned int i = 0; i < this->timed_probes; i++) { + if (this->fp_responses[i] != NULL) + delete this->fp_responses[i]; + this->fp_responses[i] = this->aux_resp[i]; + this->aux_resp[i] = NULL; + } + /* Otherwise, get rid of the stored responses, use the current set */ + } else { + for (unsigned int i = 0; i < this->timed_probes; i++) { + if (this->aux_resp[i] != NULL) { + delete this->aux_resp[i]; + this->aux_resp[i] = NULL; + } + } + } + + return OP_SUCCESS; +} + + +/* This function is called by the network controller every time a packet of + * interest is captured. A "packet of interest" is a packet whose source + * address matches the IP address of the target associated with the FPHost + * instance. Inside the method, the received packet is processed in order to + * determine if it corresponds to a response to a previous FPProbe sent to + * that target. If the packet is a proper response, it will be stored for + * later processing, as it is part of the target's stack fingerprint. Returns + * a positive integer when the supplied packet could be successfully matched with + * a previously sent probe. The returned value indicates how many times the + * probe was sent before getting a reply: a return value of 1 means that we + * got a normal reply, value two means that we had to retransmit the packet + * once to get the reply, and so on. A return value of zero is a special case + * that indicates that the supplied packet is a response to a timed probed + * for which we already had received a reply in the past. This is necessary + * because we need to indicate the network controller that this is not a normal + * response to a retransmitted probe, and so, it should not be used to alter + * congestion control parameters. A negative return value indicates that the + * supplied packet is not a response to any probe sent by this host. */ +int FPHost6::callback(const u8 *pkt, size_t pkt_len, const struct timeval *tv) { + PacketElement *rcvd = NULL; + /* Dummy packet to ensure destruction of rcvd. */ + FPPacket dummy; + bool match_found = false; + int times_tx = 0; + + /* Make sure we still expect callbacks */ + if (this->detection_done) + return -1; + + if (o.debugging > 3) + log_write(LOG_PLAIN, "[%s] Captured %lu bytes\n", this->target_host->targetipstr(), (unsigned long)pkt_len); + + /* Convert the ugly raw buffer into a nice chain of PacketElement objects, so + * it's easier to parse the captured packet */ + if ((rcvd = PacketParser::split(pkt, pkt_len, false)) == NULL) + return -2; + dummy.setPacket(rcvd); + + /* Iterate over the list of sent probes and determine if the captured + * packet is a response to one of them. */ + for (unsigned int i = 0; i < this->probes_sent; i++) { + /* Skip probes for which we already got a response */ + if (this->fp_responses[i]) + continue; + + /* See if the received packet is a response to a probe */ + if (this->fp_probes[i].isResponse(rcvd)) { + struct timeval now, time_sent; + + gettimeofday(&now, NULL); + this->fp_responses[i] = new FPResponse(this->fp_probes[i].getProbeID(), + pkt, pkt_len, fp_probes[i].getTimeSent(), *tv); + this->fp_probes[i].incrementReplies(); + match_found = true; + + /* If the response that we've received is for a timed probe, we + * need to do a special handling. We don't want to report that + * we've received a response after N retransmissions because we + * may have re-sent the packet even if we got a response in the past. + * This happens when one of the timed probes times out and we + * retransmit all of them. We don't want the network controller to + * think there is congestion, so we only return the number of + * retransmissions if we didn't get a response before and we did now. */ + if (this->fp_probes[i].isTimed() && this->fp_probes[i].getRetransmissions() > 0 && this->fp_probes[i].getReplies() > 1) { + times_tx = 0; // Special case. + } else { + times_tx = this->fp_probes[i].getRetransmissions()+1; + } + this->probes_answered++; + /* Recompute the Retransmission Timeout based on this new RTT observation. */ + time_sent = this->fp_probes[i].getTimeSent(); + assert(time_sent.tv_sec > 0); + this->update_RTO(TIMEVAL_SUBTRACT(now, time_sent), this->fp_probes[i].getRetransmissions() != 0); + break; + } + } + + if (match_found) { + if (o.packetTrace()) { + log_write(LOG_PLAIN, "RCVD "); + rcvd->print(stdout, LOW_DETAIL); + log_write(LOG_PLAIN, "\n"); + } + /* Here, check if with this match we completed the OS detection */ + if (this->probes_answered + this->probes_unanswered == this->total_probes) { + /* Update our internal state to indicate we have finished */ + this->set_done_and_wrap_up(); + } + /* Return the number of times that the packet was transmitted before + * getting the reply. */ + return times_tx; + } else { + return -3; + } +} + + +const FPProbe *FPHost6::getProbe(const char *id) { + unsigned int i; + + for (i = 0; i < NUM_FP_PROBES_IPv6; i++) { + if (!this->fp_probes[i].is_set()) + continue; + if (strcmp(this->fp_probes[i].getProbeID(), id) == 0) + return &this->fp_probes[i]; + } + + return NULL; +} + +const FPResponse *FPHost6::getResponse(const char *id) { + unsigned int i; + + for (i = 0; i < NUM_FP_PROBES_IPv6; i++) { + if (this->fp_responses[i] == NULL) + continue; + if (strcmp(this->fp_responses[i]->probe_id, id) == 0) + return this->fp_responses[i]; + } + + return NULL; +} + + +/****************************************************************************** + * Implementation of class FPPacket. * + ******************************************************************************/ +FPPacket::FPPacket() { + this->pkt = NULL; + this->__reset(); +} + + +FPPacket::~FPPacket() { + this->__reset(); +} + + +/* Resets all internal state, freeing any previously stored packets */ +void FPPacket::__reset() { + this->link_eth = false; + memset(&(this->eth_hdr), 0, sizeof(struct eth_nfo)); + + PacketElement *me = this->pkt, *aux = NULL; + while (me != NULL) { + aux = me->getNextElement(); + delete me; + me = aux; + } + this->pkt = NULL; + memset(&this->pkt_time, 0, sizeof(struct timeval)); +} + + +/* Returns true if the FPPacket has been associated with a packet (through a + * call to setPacket(). This is equivalent to the following conditional: + * fppacket.getPacket() != NULL */ +bool FPPacket::is_set() const { + if (this->pkt != NULL) + return true; + else + return false; +} + + +/* Associates de FPPacket instance with the first protocol header of a networkj + * packet. Such header may be linked to others through the setNextElement() + * mechanism. Note that FPPacket does NOT make a copy of the contents of the + * supplied pointer, it just stores the memory address. Therefore, the caller + * MUST ensure that the supplied pointer remains valid during the lifetime of + * the FPPacket instance. + * + * After calling this function, the FPPacket takes ownership of pkt and will + * delete pkt in its destructor. */ +int FPPacket::setPacket(PacketElement *pkt) { + assert(pkt != NULL); + this->pkt = pkt; + return OP_SUCCESS; +} + + +/* Returns a newly allocated byte array with packet contents. The caller is + * responsible for freeing the buffer. */ +u8 *FPPacket::getPacketBuffer(size_t *pkt_len) const { + u8 *pkt_buff; + + pkt_buff = (u8 *)safe_malloc(this->pkt->getLen()); + this->pkt->dumpToBinaryBuffer(pkt_buff, this->pkt->getLen()); + + *pkt_len = (size_t)this->pkt->getLen(); + + return pkt_buff; +} + + +/* Returns a pointer to first header of the packet associated with the FPPacket + * instance. Note that this method will return NULL unless a previous call to + * setPacket() has been made. */ +const PacketElement *FPPacket::getPacket() const { + return this->pkt; +} + + +/* Returns the length of the packet associated with the FPPacket instance. Note + * that this method will return zero unless an actual packet was associated + * with the FPPacket object through a call to setPacket(). */ +size_t FPPacket::getLength() const { + if (this->pkt != NULL) + return this->pkt->getLen(); + else + return 0; +} + + +/* This method associates some link layer information with the packet. If + * sending at the ethernet level is not required, just call it passing NULL + * values, like this: instance.setEthernet(NULL, NULL, NULL); + * Otherwise, pass the source address, the next hop address and the name of + * the network interface the packet should be injected through. */ +int FPPacket::setEthernet(const u8 *src_mac, const u8 *dst_mac, const char *devname) { + if (src_mac == NULL || dst_mac == NULL) { + memset(&(this->eth_hdr), 0, sizeof(struct eth_nfo)); + this->link_eth = false; + return OP_FAILURE; + } + memcpy(this->eth_hdr.srcmac, src_mac, 6); + memcpy(this->eth_hdr.dstmac, dst_mac, 6); + this->link_eth = true; + if (devname != NULL) { + strncpy(this->eth_hdr.devname, devname, sizeof(this->eth_hdr.devname)-1); + if ((this->eth_hdr.ethsd = eth_open_cached(devname)) == NULL) + fatal("%s: Failed to open ethernet device (%s)", __func__, devname); + } else { + this->eth_hdr.devname[0] = '\0'; + this->eth_hdr.ethsd = NULL; + } + return OP_SUCCESS; +} + + +/* Returns an eth_nfo structure that contains the necessary parameters to + * allow the transmission of the packet at the Ethernet level. Note that + * such structure is only returned if a previous call to setEthernet() has + * been made. If it hasn't, this means that the packet should be sent at + * the IP layer, and only NULL will be returned. */ +const struct eth_nfo *FPPacket::getEthernet() const { + if (this->link_eth == true) + return &(this->eth_hdr); + else + return NULL; +} + + +/* Sets the internal time holder to the current time. */ +int FPPacket::setTime(const struct timeval *tv) { + if (tv != NULL) { + this->pkt_time = *tv; + return 0; + } else { + return gettimeofday(&this->pkt_time, NULL); + } +} + + +/* Returns the value of the internal time holder */ +struct timeval FPPacket::getTime() const { + return this->pkt_time; +} + + +/* Sets the internal time holder to zero. */ +int FPPacket::resetTime() { + memset(&this->pkt_time, 0, sizeof(struct timeval)); + return OP_SUCCESS; +} + + + +/****************************************************************************** + * Implementation of class FPProbe. * + ******************************************************************************/ +FPProbe::FPProbe() { + this->probe_id = NULL; + this->host = NULL; + this->reset(); +} + + +FPProbe::~FPProbe() { +} + + +void FPProbe::reset() { + this->probe_no = 0; + this->retransmissions = 0; + this->times_replied = 0; + this->failed = false; + this->timed = false; + this->probe_id = NULL; + + /* Also call FPPacket::__reset() to free any existing packet information */ + this->__reset(); +} + + +/* Returns true if the supplied packet is a response to this FPProbe. This + * method handles IPv4, IPv6, ICMPv4, ICMPv6, TCP and UDP. Basically it uses + * PacketParser::is_response(). Check there for a list of matched packets and + * some usage examples.*/ +bool FPProbe::isResponse(PacketElement *rcvd) { + /* If we don't have a record of even sending this probe, no packet can be a + response. */ + if (this->pkt_time.tv_sec == 0 && this->pkt_time.tv_usec == 0) + return false; + + bool is_response = PacketParser::is_response(this->pkt, rcvd); + if (o.debugging > 2 && is_response) + printf("Received response to probe %s\n", this->getProbeID()); + + return is_response; +} + + +/* Store this probe's textual identifier. Note that this method makes a copy + * of the supplied string, so you can safely change its contents without + * affecting the object's state. */ +int FPProbe::setProbeID(const char *id) { + this->probe_id = string_pool_insert(id); + return OP_SUCCESS; +} + + +/* Returns a pointer to probe's textual identifier. */ +const char *FPProbe::getProbeID() const { + return this->probe_id; +} + + +/* Returns the number of times the probe has been scheduled for retransmission. */ +int FPProbe::getRetransmissions() const { + return this->retransmissions; +} + + +/* Increment the number of times the probe has been scheduled for retransmission + * by one unit. It returns the current value of the retransmission counter. */ +int FPProbe::incrementRetransmissions() { + this->retransmissions++; + return this->retransmissions; +} + + +/* Returns the number of times the probe has been replied. This applies for + * timed probes, which may be retransmitted even if we got a reply (because + * another timed probe timeout and we had to retransmit all of them to keep + * the timing accurate). */ +int FPProbe::getReplies() const { + return this->times_replied; +} + + +/* Increment the number of times the probe has been replied. It returns the + * current value of the reply counter. */ +int FPProbe::incrementReplies() { + this->times_replied++; + return this->times_replied; +} + + +/* Sets the time at which the probe was sent */ +int FPProbe::setTimeSent() { + return this->setTime(); +} + + +/* Returns the time at which te packet was sent */ +struct timeval FPProbe::getTimeSent() const { + return this->getTime(); +} + + +/* Sets the time at which the probe was sent to zero. */ +int FPProbe::resetTimeSent() { + return this->resetTime(); +} + +/* Returns true if this FPProbe did not receive any response after all + * necessary retransmissions. When it returns true, callers should not + * attempt to change the state of the FPProbe. */ +bool FPProbe::probeFailed() const { + return this->failed; +} + + +/* This method should be called when the probe has been retransmitted as many + * times as we could and it still timed out without a response. Once this + * method is called, the state is irreversible (unless a call to FPProbe::reset() + * is made, in which case all internal state disappears) */ +int FPProbe::setFailed() { + this->failed = true; + return OP_SUCCESS; +} + + +/* Returns true if the probe is one of the "timed probes". */ +bool FPProbe::isTimed() const { + return this->timed; +} + + +/* Marks the probe as "timed". This is used to indicate that this probe has + * specific timing requirements (it must be sent exactly 100ms after the + * previous probe)., */ +int FPProbe::setTimed() { + this->timed = true; + return OP_SUCCESS; +} + +/* Changes source address for packet element associated with current FPProbe. */ +int FPProbe::changeSourceAddress(struct in6_addr *addr) { + if (!is_set()) + return OP_FAILURE; + else{ + IPv6Header *ip6 = find_ipv6(getPacket()); + if (ip6 != NULL) + return ip6->setSourceAddress(*addr); + } + return OP_FAILURE; +} + +/****************************************************************************** + * Implementation of class FPResponse. * + ******************************************************************************/ +FPResponse::FPResponse(const char *probe_id, const u8 *buf, size_t len, + struct timeval senttime, struct timeval rcvdtime) { + this->probe_id = string_pool_insert(probe_id); + this->buf = (u8 *) safe_malloc(len); + memcpy(this->buf, buf, len); + this->len = len; + this->senttime = senttime; + this->rcvdtime = rcvdtime; +} + + +FPResponse::~FPResponse() { + free(buf); +} + + +/****************************************************************************** + * Nsock handler wrappers. * + ******************************************************************************/ + +/* This handler is a wrapper for the FPNetworkControl::probe_transmission_handler() + * method. We need this because C++ does not allow to use class methods as + * callback functions for things like signal() or the Nsock lib. */ +void probe_transmission_handler_wrapper(nsock_pool nsp, nsock_event nse, void *arg) { + global_netctl.probe_transmission_handler(nsp, nse, arg); + return; +} + + +/* This handler is a wrapper for the FPNetworkControl:response_reception_handler() + * method. We need this because C++ does not allow to use class methods as + * callback functions for things like signal() or the Nsock lib. */ +void response_reception_handler_wrapper(nsock_pool nsp, nsock_event nse, void *arg) { + global_netctl.response_reception_handler(nsp, nse, arg); + return; +} -- cgit v1.2.3