(1) ICMP error messages (fe. Destination Unreachable) are often rate-limited and/or generated by some middlebox (packet filter). In most type of scans (but not in "no response means open" types) getting ICMP error or getting no response doesn't make a difference (both end up as "filtered"). There is no reason to adjust packet timing in order to get ICMP error response for every probe... (2) Solaris 9 rate-limits RST,ACK packets (response to SYN probe against closed port) so scanning it becomes very slow (due to scanner adjusting timing). This patch implements --defeat_rst_ratelimit option which affects SYN scan in a way that it does not distinguish between closed and filtered ports (reports closed|filtered for both) and so it finds opened ports only. With this option, scanning a host with rate-limited RST is (approx.) same fast as against a host with a DROP policy. If you don't have Solaris 9 you can simulate it (not exactly, but very close) on Linux with something like: iptables -A INPUT -i eth0 -p tcp -m multiport --destination-ports 22,25,53,80,443 -j ACCEPT iptables -A INPUT -i eth0 -p tcp -m limit --limit 40/s -j REJECT --reject-with tcp-reset iptables -A INPUT -i eth0 -p tcp -j DROP --- nmap-3.98BETA1/nmap.cc.ratelimit 2006-01-22 21:30:19.000000000 +0100 +++ nmap-3.98BETA1/nmap.cc 2006-01-23 08:53:47.000000000 +0100 @@ -266,6 +266,8 @@ {"min_hostgroup", required_argument, 0, 0}, {"min-hostgroup", required_argument, 0, 0}, {"scanflags", required_argument, 0, 0}, + {"defeat_rst_ratelimit", no_argument, 0, 0}, + {"defeat-rst-ratelimit", no_argument, 0, 0}, {"host_timeout", required_argument, 0, 0}, {"host-timeout", required_argument, 0, 0}, {"scan_delay", required_argument, 0, 0}, @@ -438,6 +440,8 @@ if (o.scan_delay > o.maxTCPScanDelay()) o.setMaxTCPScanDelay(o.scan_delay); if (o.scan_delay > o.maxUDPScanDelay()) o.setMaxUDPScanDelay(o.scan_delay); o.max_parallelism = 1; + } else if (optcmp(long_options[option_index].name, "defeat-rst-ratelimit") == 0) { + o.defeat_rst_ratelimit = 1; } else if (optcmp(long_options[option_index].name, "max-scan-delay") == 0) { l = tval2msecs(optarg); if (l < 0) fatal("--max-scan-delay cannot be negative."); diff -urp nmap-3.95.orig/NmapOps.cc nmap-3.95/NmapOps.cc --- nmap-3.95.orig/NmapOps.cc 2005-12-06 23:26:05.000000000 +0100 +++ nmap-3.95/NmapOps.cc 2005-12-09 12:47:16.000000000 +0100 @@ -217,6 +217,7 @@ void NmapOps::Initialize() { extra_payload = NULL; scan_delay = 0; scanflags = -1; + defeat_rst_ratelimit = 0; resume_ip.s_addr = 0; osscan_limit = 0; osscan_guess = 0; @@ -412,6 +413,10 @@ void NmapOps::ValidateOptions() { if (osscan && pingscan) { fatal("WARNING: OS Scan is unreliable with a ping scan. You need to use a scan type along with it, such as -sS, -sT, -sF, etc instead of -sP"); } + + if (defeat_rst_ratelimit && !synscan) { + fatal("Option --defeat_rst_ratelimit works only with SYN scan (-sS)"); + } if (resume_ip.s_addr && generate_random_ips) resume_ip.s_addr = 0; diff -urp nmap-3.95.orig/NmapOps.h nmap-3.95/NmapOps.h --- nmap-3.95.orig/NmapOps.h 2005-09-07 10:26:57.000000000 +0200 +++ nmap-3.95/NmapOps.h 2005-12-09 12:47:16.000000000 +0100 @@ -238,6 +238,10 @@ class NmapOps { FIN scan into a PSH scan. Sort of a hack, but can be very useful sometimes. */ + int defeat_rst_ratelimit; /* Solaris 9 rate-limits RSTs so scanning is very + slow against it. If we don't distinguish between closed and filtered ports, + we can get the list of open ports very fast */ + struct in_addr resume_ip; /* The last IP in the log file if user requested --restore . Otherwise restore_ip.s_addr == 0. Also diff -urp nmap-3.95.orig/scan_engine.cc nmap-3.95/scan_engine.cc --- nmap-3.95.orig/scan_engine.cc 2005-12-08 09:13:34.000000000 +0100 +++ nmap-3.95/scan_engine.cc 2005-12-09 13:00:26.000000000 +0100 @@ -538,6 +538,7 @@ public: bool prot_scan; bool ping_scan; /* Includes trad. ping scan & arp scan */ bool ping_scan_arp; /* ONLY includes arp ping scan */ + bool noresp_open_scan; /* Whether no response means a port is open */ struct timeval now; /* Updated after potentially meaningful delays. This can be used to save a call to gettimeofday() */ GroupScanStats *gstats; @@ -1170,23 +1171,26 @@ void UltraScanInfo::Init(vectorscantype == FIN_SCAN || - USI->scantype == XMAS_SCAN || USI->scantype == MAIMON_SCAN || - USI->scantype == NULL_SCAN || USI->scantype == UDP_SCAN || - USI->scantype == IPPROT_SCAN; + bool noresp_open_scan = USI->noresp_open_scan; if (USI->prot_scan) { proto = IPPROTO_IP; @@ -1906,9 +1907,9 @@ bool ultrascan_port_pspec_update(UltraSc /* This function is called when a new status is determined for a port. the port in the probeI of host hss is now in newstate. This function needs to update timing information, other stats, and the - Nmap port state table as appropriate. If rcvdtime is NULL, packet - stats are not updated. If you don't have an UltraProbe list - iterator, you may need to call ultrascan_port_psec_update() + Nmap port state table as appropriate. If rcvdtime is NULL or we got + unimportant packet, packet stats are not updated. If you don't have an + UltraProbe list iterator, you may need to call ultrascan_port_psec_update() instead */ static void ultrascan_port_probe_update(UltraScanInfo *USI, HostScanStats *hss, list::iterator probeI, @@ -1917,24 +1918,32 @@ static void ultrascan_port_probe_update( const probespec *pspec = probe->pspec(); bool changed = false; - if (rcvdtime) ultrascan_adjust_times(USI, hss, probe, rcvdtime); - changed = ultrascan_port_pspec_update(USI, hss, pspec, newstate); /* The rcvdtime check is because this func is called that way when we give up on a probe because of too many retransmissions. */ - if (changed && probe->tryno > hss->max_successful_tryno - && rcvdtime) { - hss->max_successful_tryno = probe->tryno; - if (o.debugging) - log_write(LOG_STDOUT, "Increased max_successful_tryno for %s to %d (packet drop)\n", hss->target->targetipstr(), hss->max_successful_tryno); - if (hss->max_successful_tryno > ((o.timing_level >= 4)? 4 : 3)) { - unsigned int olddelay = hss->sdn.delayms; - hss->boostScanDelay(); - if (o.verbose && hss->sdn.delayms != olddelay) - log_write(LOG_STDOUT, "Increasing send delay for %s from %d to %d due to max_successful_tryno increase to %d\n", - hss->target->targetipstr(), olddelay, hss->sdn.delayms, - hss->max_successful_tryno); + if (rcvdtime && + /* If we are not in "noresp_open_scan" and got something back and the + * newstate is PORT_FILTERED then we got ICMP error response. + * ICMP errors are often rate-limited (RFC1812) and/or generated by + * middle-box. No reason to slow down the scan */ + ((changed && newstate != PORT_FILTERED) || USI->noresp_open_scan) && + /* If we are in --defeat_rst_ratelimit mode, we do not care whether we got RST back or not + * because RST and "no response" both mean PORT_CLOSEDFILTERED. Do not slow down */ + !(o.defeat_rst_ratelimit && newstate == PORT_CLOSEDFILTERED)) { /* rcvdtime is interesting */ + ultrascan_adjust_times(USI, hss, probe, rcvdtime); + if (probe->tryno > hss->max_successful_tryno) { + hss->max_successful_tryno = probe->tryno; + if (o.debugging) + log_write(LOG_STDOUT, "Increased max_successful_tryno for %s to %d (packet drop)\n", hss->target->targetipstr(), hss->max_successful_tryno); + if (hss->max_successful_tryno > ((o.timing_level >= 4)? 4 : 3)) { + unsigned int olddelay = hss->sdn.delayms; + hss->boostScanDelay(); + if (o.verbose && hss->sdn.delayms != olddelay) + log_write(LOG_STDOUT, "Increasing send delay for %s from %d to %d due to max_successful_tryno increase to %d\n", + hss->target->targetipstr(), olddelay, hss->sdn.delayms, + hss->max_successful_tryno); + } } } @@ -2913,7 +2922,10 @@ static bool get_pcap_result(UltraScanInf newstate = (tcp->th_win)? PORT_OPEN : PORT_CLOSED; } else if (USI->scantype == ACK_SCAN) { newstate = PORT_UNFILTERED; - } else newstate = PORT_CLOSED; + } else if (o.defeat_rst_ratelimit) { + newstate = PORT_CLOSEDFILTERED; + } else + newstate = PORT_CLOSED; } else { if (o.debugging) error("Received scan response with unexpected TCP flags: %d\n", tcp->th_flags); @@ -3038,6 +3050,8 @@ static bool get_pcap_result(UltraScanInf sizeof(struct ip)); break; } + if (o.defeat_rst_ratelimit && newstate == PORT_FILTERED) + newstate = PORT_CLOSEDFILTERED; if (newstate == PORT_UNKNOWN) break; goodone = true; }