summaryrefslogtreecommitdiffstats
path: root/FPEngine.cc
diff options
context:
space:
mode:
Diffstat (limited to 'FPEngine.cc')
-rw-r--r--FPEngine.cc2741
1 files changed, 2741 insertions, 0 deletions
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 <math.h>
+
+
+/******************************************************************************
+ * 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<Target *> &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<std::string, FPPacket>& 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<std::string, FPPacket>::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<std::string, FPPacket> 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<Target *> &Targets) {
+ bool osscan_done = false;
+ const char *bpf_filter = NULL;
+ std::vector<FPHost6 *> curr_hosts; /* Hosts currently doing OS detection */
+ std::vector<FPHost6 *> done_hosts; /* Hosts for which we already did OSdetect */
+ std::vector<FPHost6 *> 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;
+}