diff --git a/CHANGELOG b/CHANGELOG
index 2b60f27d3..0201daa52 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,16 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added the path-mtu script to perform Path MTU Discovery to the
+ target host using TCP or UDP. The script tries to conserve bandwidth and
+ time by starting with the outgoing interface's MTU and properly handling
+ the Next-Hop MTU field in ICMP responses generated by RFC-compliant
+ intermediate routers. [Kris]
+
+o [NSE] Scripts can now access the MTU of the host.interface device using
+ host.interface_mtu. [Kris]
+
+o Nmap now prints the MTU for interfaces when using --iflist. [Kris]
+
o [NSE] Removed references to MD2, as OpenSSL 1.x.x doesn't support it anymore
[alexandru]
diff --git a/Target.cc b/Target.cc
index be4f0ac52..99679c162 100644
--- a/Target.cc
+++ b/Target.cc
@@ -133,8 +133,9 @@ void Target::Initialize() {
htn.toclock_running = false;
htn.host_start = htn.host_end = 0;
interface_type = devt_other;
- devname[0] = '\0';
- devfullname[0] = '\0';
+ devname[0] = '\0';
+ devfullname[0] = '\0';
+ mtu = 0;
state_reason_init(&reason);
memset(&pingprobe, 0, sizeof(pingprobe));
pingprobe_state = PORT_UNKNOWN;
@@ -378,6 +379,15 @@ void Target::setNextHop(struct sockaddr_storage *next_hop, size_t next_hop_len)
nexthopsocklen = next_hop_len;
}
+/* Set MTU (to correspond with devname) */
+void Target::setMTU(int devmtu) {
+ mtu = devmtu;
+}
+
+/* Get MTU (to correspond with devname) */
+int Target::MTU(void) {
+ return mtu;
+}
/* Starts the timeout clock for the host running (e.g. you are
beginning a scan). If you do not have the current time handy,
diff --git a/Target.h b/Target.h
index 89758949d..6ad22c566 100644
--- a/Target.h
+++ b/Target.h
@@ -225,6 +225,9 @@ class Target {
next_hop has never been set */
bool nextHop(struct sockaddr_storage *next_hop, size_t *next_hop_len);
+ void setMTU(int devmtu);
+ int MTU(void);
+
/* Sets the interface type to one of:
devt_ethernet, devt_loopback, devt_p2p, devt_other
*/
@@ -318,7 +321,8 @@ class Target {
struct host_timeout_nfo htn;
devtype interface_type;
char devname[32];
- char devfullname[32];
+ char devfullname[32];
+ int mtu;
/* 0 (OS_NOTPERF) if os detection not performed
* 1 (OS_PERF) if os detection performed
* 2 (OS_PERF_UNREL) if an unreliable os detection has been performed */
diff --git a/docs/scripting.xml b/docs/scripting.xml
index ae7daced2..8201afc26 100644
--- a/docs/scripting.xml
+++ b/docs/scripting.xml
@@ -1513,6 +1513,15 @@ LUALIB_API int luaopen_openssl(lua_State *L) {
+
+
+
+
+ The MTU (maximum transmission unit) for host.interface,
+ or 0 if not known.
+
+
+
diff --git a/libnetutil/netutil.cc b/libnetutil/netutil.cc
index 910de92ed..e375060a9 100644
--- a/libnetutil/netutil.cc
+++ b/libnetutil/netutil.cc
@@ -995,6 +995,8 @@ static int collect_dnet_interfaces(const struct intf_entry *entry, void *arg) {
dcrn->ifaces[dcrn->numifaces].device_type = devt_other;
}
+ dcrn->ifaces[dcrn->numifaces].mtu = entry->intf_mtu;
+
/* Is the interface up and running? */
dcrn->ifaces[dcrn->numifaces].device_up = (entry->intf_flags & INTF_FLAG_UP) ? true : false;
@@ -1197,6 +1199,27 @@ static struct interface_info *getinterfaces_siocgifconf(int *howmany, char *errs
else
devs[count].device_up = 0;
+#ifdef SIOCGIFMTU
+ memcpy(&tmpifr.ifr_addr, sin, MIN(sizeof(tmpifr.ifr_addr), sizeof(*sin)));
+ rc = ioctl(sd, SIOCGIFMTU, &tmpifr);
+ if (rc < 0) {
+ if(errstr) Snprintf(errstr, errstrlen, "Failed to determine the mtu of %s!", tmpifr.ifr_name);
+ *howmany=-1;
+ return NULL;
+ } else {
+#ifdef ifr_mtu
+ devs[count].mtu = tmpifr.ifr_mtu;
+#else
+ /* Some systems lack ifr_mtu and a common solution (see pcap, dnet and
+ * others) is using ifr_metric instead
+ */
+ devs[count].mtu = tmpifr.ifr_metric;
+#endif
+ }
+#else
+ devs[count].mtu = 0;
+#endif
+
/* All done with this interface. Increase the count. */
count++;
}
diff --git a/libnetutil/netutil.h b/libnetutil/netutil.h
index 637598410..3e5837982 100644
--- a/libnetutil/netutil.h
+++ b/libnetutil/netutil.h
@@ -228,6 +228,7 @@ struct interface_info {
u16 netmask_bits; /* CIDR-style. So 24 means class C (255.255.255.0)*/
devtype device_type; /* devt_ethernet, devt_loopback, devt_p2p, devt_other */
int device_up; /* True if the device is up (enabled) */
+ int mtu; /* Interface's MTU size */
u8 mac[6]; /* Interface MAC address if device_type is devt_ethernet */
};
diff --git a/nse_nmaplib.cc b/nse_nmaplib.cc
index 185875ec4..edba40cab 100644
--- a/nse_nmaplib.cc
+++ b/nse_nmaplib.cc
@@ -132,6 +132,7 @@ void set_hostinfo(lua_State *L, Target *currenths) {
lua_setfield(L, -2, "mac_addr_src");
}
setsfield(L, -1, "interface", currenths->deviceName());
+ setnfield(L, -1, "interface_mtu", currenths->MTU());
if ((u32)(currenths->v4host().s_addr))
{
struct in_addr adr = currenths->v4host();
diff --git a/output.cc b/output.cc
index 10c2e91b6..eac12791b 100644
--- a/output.cc
+++ b/output.cc
@@ -336,13 +336,14 @@ int print_iflist(void) {
if (o.debugging)
log_write(LOG_STDOUT, "Reason: %s\n", errstr);
} else {
- int devcol = 0, shortdevcol = 1, ipcol = 2, typecol = 3, upcol = 4, maccol = 5;
- Tbl = new NmapOutputTable(numifs + 1, 6);
+ int devcol = 0, shortdevcol = 1, ipcol = 2, typecol = 3, upcol = 4, mtucol = 5, maccol = 6;
+ Tbl = new NmapOutputTable(numifs + 1, 7);
Tbl->addItem(0, devcol, false, "DEV", 3);
Tbl->addItem(0, shortdevcol, false, "(SHORT)", 7);
Tbl->addItem(0, ipcol, false, "IP/MASK", 7);
Tbl->addItem(0, typecol, false, "TYPE", 4);
Tbl->addItem(0, upcol, false, "UP", 2);
+ Tbl->addItem(0, mtucol, false, "MTU", 3);
Tbl->addItem(0, maccol, false, "MAC", 3);
for (i = 0; i < numifs; i++) {
Tbl->addItem(i + 1, devcol, false, iflist[i].devfullname);
@@ -366,6 +367,7 @@ int print_iflist(void) {
Tbl->addItem(i + 1, typecol, false, "other");
Tbl->addItem(i + 1, upcol, false,
(iflist[i].device_up ? "up" : "down"));
+ Tbl->addItemFormatted(i + 1, mtucol, false, "%d", iflist[i].mtu);
}
log_write(LOG_PLAIN, "************************INTERFACES************************\n");
log_write(LOG_PLAIN, "%s\n", Tbl->printableTable(NULL));
diff --git a/scripts/path-mtu.nse b/scripts/path-mtu.nse
new file mode 100644
index 000000000..2f875314d
--- /dev/null
+++ b/scripts/path-mtu.nse
@@ -0,0 +1,391 @@
+description = [[
+Performs simple Path MTU Discovery to the target host.
+
+TCP or UDP packets are sent to the host with the DF (don't fragment) bit
+set and with varying amounts of data. If an ICMP Fragmentation Needed
+is received, or no reply is received after retransmissions, the amount
+of data is lowered and another packet is sent. This continues until
+(assuming no errors occur) a reply from the final host is received,
+indicating the packet reached the host without being fragmented.
+
+Not all MTUs are attempted so as to not expend too much time or network
+resources. Currently the relatively short list of MTUs to try contains
+the plateau values from Table 7-1 in RFC 1191, "Path MTU Discovery".
+Using these values significantly cuts down the MTU search space. On top
+of that, this list is rarely traversed in whole because:
+ * the MTU of the outgoing interface is used as a starting point, and
+ * we can jump down the list when an intermediate router sending a
+ "can't fragment" message includes its next hop MTU (as described
+ in RFC 1191 and required by RFC 1812)
+]]
+
+---
+-- @usage
+-- nmap --script path-mtu target
+--
+-- @output
+-- Host script results:
+-- |_path-mtu: 1492 <= PMTU < 1500
+--
+-- Host script results:
+-- |_path-mtu: PMTU == 1006
+
+author = "Kris Katterjohn"
+
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+
+categories = {"safe", "discovery"}
+
+require 'bin'
+require 'packet'
+
+local IPPROTO_ICMP = packet.IPPROTO_ICMP
+local IPPROTO_TCP = packet.IPPROTO_TCP
+local IPPROTO_UDP = packet.IPPROTO_UDP
+
+-- Number of times to retransmit for no reply before dropping to
+-- another MTU value
+local RETRIES = 1
+
+-- RFC 1191, Table 7-1: Plateaus. Even the massive MTU values are
+-- here since we skip down the list based on the outgoing interface
+-- so its no harm.
+local MTUS = {
+ 65535,
+ 32000,
+ 17914,
+ 8166,
+ 4352,
+ 2002,
+ 1492,
+ 1006,
+ 508,
+ 296,
+ 68
+}
+
+-- Find the index in MTUS{} to use based on the MTU +new+. If +new+ is in
+-- between values in MTUS, then insert it into the table appropriately.
+local searchmtu = function(cidx, new)
+ if new == 0 then
+ return cidx
+ end
+
+ while cidx <= #MTUS do
+ if new >= MTUS[cidx] then
+ if new ~= MTUS[cidx] then
+ table.insert(MTUS, cidx, new)
+ end
+ return cidx
+ end
+ cidx = cidx + 1
+ end
+ return cidx
+end
+
+local dport = function(ip)
+ if ip.ip_p == IPPROTO_TCP then
+ return ip.tcp_dport
+ elseif ip.ip_p == IPPROTO_UDP then
+ return ip.udp_dport
+ end
+end
+
+local sport = function(ip)
+ if ip.ip_p == IPPROTO_TCP then
+ return ip.tcp_sport
+ elseif ip.ip_p == IPPROTO_UDP then
+ return ip.udp_sport
+ end
+end
+
+-- Checks how we should react to this packet
+local checkpkt = function(reply, orig)
+ local ip = packet.Packet:new(reply, reply:len())
+
+ if ip.ip_p == IPPROTO_ICMP then
+ if ip.icmp_type ~= 3 then
+ return "recap"
+ end
+ -- Port Unreachable
+ if ip.icmp_code == 3 then
+ local is = ip.buf:sub(ip.icmp_offset + 9)
+ local ip2 = packet.Packet:new(is, is:len())
+
+ -- Check sent packet against ICMP payload
+ if ip2.ip_p ~= IPPROTO_UDP or
+ ip2.ip_p ~= orig.ip_p or
+ ip2.ip_bin_src ~= orig.ip_bin_src or
+ ip2.ip_bin_dst ~= orig.ip_bin_dst or
+ sport(ip2) ~= sport(orig) or
+ dport(ip2) ~= dport(orig) then
+ return "recap"
+ end
+
+ return "gotreply"
+ end
+ -- Frag needed, DF set
+ if ip.icmp_code == 4 then
+ local val = ip:u16(ip.icmp_offset + 6)
+ return "nextmtu", val
+ end
+ return "recap"
+ end
+
+ if ip.ip_p ~= orig.ip_p or
+ ip.ip_bin_src ~= orig.ip_bin_dst or
+ ip.ip_bin_dst ~= orig.ip_bin_src or
+ dport(ip) ~= sport(orig) or
+ sport(ip) ~= dport(orig) then
+ return "recap"
+ end
+
+ return "gotreply"
+end
+
+-- This is all we can use since we can get various protocols back from
+-- different hosts
+local callback = function(size, layer2, layer3)
+ local ip = packet.Packet:new(layer3, layer3:len())
+ return bin.pack('A', ip.ip_bin_dst)
+end
+
+-- Updates a packet's info and calculates checksum
+local updatepkt = function(ip)
+ if ip.ip_p == IPPROTO_TCP then
+ ip:tcp_set_sport(math.random(0x401, 0xffff))
+ ip:tcp_set_seq(math.random(1, 0x7fffffff))
+ ip:tcp_count_checksum()
+ elseif ip.ip_p == IPPROTO_UDP then
+ ip:udp_set_sport(math.random(0x401, 0xffff))
+ ip:udp_set_length(ip.ip_len - ip.ip_hl * 4)
+ ip:udp_count_checksum()
+ end
+ ip:ip_count_checksum()
+end
+
+-- Set up packet header and data to satisfy a certain MTU
+local setmtu = function(pkt, mtu)
+ if pkt.ip_len < mtu then
+ pkt.buf = pkt.buf .. string.rep("\0", mtu - pkt.ip_len)
+ else
+ pkt.buf = pkt.buf:sub(1, mtu)
+ end
+
+ pkt:ip_set_len(mtu)
+ pkt.packet_length = mtu
+ updatepkt(pkt)
+end
+
+local basepkt = function(proto)
+ local ibin = bin.pack("H",
+ "4500 0014 0000 4000 8000 0000 0000 0000 0000 0000"
+ )
+ local tbin = bin.pack("H",
+ "0000 0000 0000 0000 0000 0000 6002 0c00 0000 0000 0204 05b4"
+ )
+ local ubin = bin.pack("H",
+ "0000 0000 0800 0000"
+ )
+
+ if proto == IPPROTO_TCP then
+ return ibin .. tbin
+ elseif proto == IPPROTO_UDP then
+ return ibin .. ubin
+ end
+end
+
+-- Creates a Packet object for the given proto and port
+local genericpkt = function(host, proto, port)
+ local pkt = basepkt(proto)
+ local ip = packet.Packet:new(pkt, pkt:len())
+
+ ip:ip_set_bin_src(host.bin_ip_src)
+ ip:ip_set_bin_dst(host.bin_ip)
+
+ ip:set_u8(ip.ip_offset + 9, proto)
+ ip.ip_p = proto
+
+ ip:ip_set_len(pkt:len())
+
+ if proto == IPPROTO_TCP then
+ ip:tcp_parse(false)
+ ip:tcp_set_dport(port)
+ elseif proto == IPPROTO_UDP then
+ ip:udp_parse(false)
+ ip:udp_set_dport(port)
+ end
+
+ updatepkt(ip)
+
+ return ip
+end
+
+local ipproto = function(p)
+ if p == "tcp" then
+ return IPPROTO_TCP
+ elseif p == "udp" then
+ return IPPROTO_UDP
+ end
+ return -1
+end
+
+-- Determines how to probe
+local getprobe = function(host)
+ local combos = {
+ { "tcp", "open" },
+ { "tcp", "closed" },
+ -- udp/open probably only happens when Nmap sends proper
+ -- payloads, which doesn't happen in here
+ { "udp", "closed" }
+ }
+ local proto = nil
+ local port = nil
+
+ for _, c in ipairs(combos) do
+ port = nmap.get_ports(host, nil, c[1], c[2])
+ if port then
+ proto = c[1]
+ break
+ end
+ end
+
+ return proto, port
+end
+
+-- Sets necessary probe data in registry
+local setreg = function(host, proto, port)
+ if not nmap.registry[host.ip] then
+ nmap.registry[host.ip] = {}
+ end
+ nmap.registry[host.ip]['pathmtuprobe'] = {
+ ['proto'] = proto,
+ ['port'] = port
+ }
+end
+
+hostrule = function(host)
+ if not nmap.is_privileged() then
+ if not nmap.registry['pathmtu'] then
+ nmap.registry['pathmtu'] = {}
+ end
+ if nmap.registry['pathmtu']['rootfail'] then
+ return false
+ end
+ nmap.registry['pathmtu']['rootfail'] = true
+ if nmap.verbosity() > 0 then
+ nmap.log_write("stdout", "PATH-MTU: not running for lack of privileges")
+ end
+ return false
+ end
+ if not (host.interface and host.interface_mtu) then
+ return false
+ end
+ local proto, port = getprobe(host)
+ if not (proto and port) then
+ return false
+ end
+ setreg(host, proto, port.number)
+ return true
+end
+
+action = function(host)
+ local m, r
+ local gotit = false
+ local mtuset
+ local sock = nmap.new_dnet()
+ local pcap = nmap.new_socket()
+ local proto = nmap.registry[host.ip]['pathmtuprobe']['proto']
+ local port = nmap.registry[host.ip]['pathmtuprobe']['port']
+ local saddr = packet.toip(host.bin_ip_src)
+ local daddr = packet.toip(host.bin_ip)
+ local try = nmap.new_try()
+ local status, pkt, ip
+
+ try(sock:ip_open())
+
+ try = nmap.new_try(function() sock:ip_close() end)
+
+ pcap:pcap_open(host.interface, 104, 0, callback, "dst host " .. saddr .. " and (icmp or (" .. proto .. " and src host " .. daddr .. " and src port " .. port .. "))")
+
+ pcap:set_timeout(3000)
+
+ m = searchmtu(1, host.interface_mtu)
+
+ mtuset = MTUS[m]
+
+ local pkt = genericpkt(host, ipproto(proto), port)
+
+ while m <= #MTUS do
+ setmtu(pkt, MTUS[m])
+
+ r = 0
+ status = false
+ while true do
+ if not status then
+ if not sock:ip_send(pkt.buf) then
+ -- Got a send error, perhaps EMSGSIZE
+ -- when we don't know our interface's
+ -- MTU. Drop an MTU and keep trying.
+ break
+ end
+ end
+
+ pcap:pcap_register(bin.pack('A', pkt.ip_bin_src))
+
+ status, _, _, ip = pcap:pcap_receive()
+
+ if status then
+ local t, v = checkpkt(ip, pkt)
+ if t == "gotreply" then
+ gotit = true
+ break
+ elseif t == "recap" then
+ elseif t == "nextmtu" then
+ if v == 0 then
+ -- Router didn't send its
+ -- next-hop MTU. Just drop
+ -- a level.
+ break
+ end
+ -- Lua's lack of a continue statement
+ -- for loop control sucks, so dec m
+ -- here as it's inc'd below. Ugh.
+ m = searchmtu(m, v) - 1
+ mtuset = v
+ break
+ end
+ else
+ if r >= RETRIES then
+ break
+ end
+ r = r + 1
+ end
+ end
+
+ if gotit then
+ break
+ end
+
+ m = m + 1
+ end
+
+ pcap:close()
+ sock:ip_close()
+
+ if not gotit then
+ if nmap.debugging() > 0 then
+ return "Error: Unable to determine PMTU (no replies)"
+ end
+ return
+ end
+
+ if MTUS[m] == mtuset then
+ return "PMTU == " .. MTUS[m]
+ elseif m == 1 then
+ return "PMTU >= " .. MTUS[m]
+ else
+ return "" .. MTUS[m] .. " <= PMTU < " .. MTUS[m - 1]
+ end
+end
+
diff --git a/scripts/script.db b/scripts/script.db
index ce6f72500..7e9487dbb 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -92,6 +92,7 @@ Entry { filename = "oracle-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "oracle-enum-users.nse", categories = { "auth", "intrusive", } }
Entry { filename = "oracle-sid-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } }
+Entry { filename = "path-mtu.nse", categories = { "discovery", "safe", } }
Entry { filename = "pgsql-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "pjl-ready-message.nse", categories = { "intrusive", } }
Entry { filename = "pop3-brute.nse", categories = { "auth", "intrusive", } }
diff --git a/targets.cc b/targets.cc
index 5321f3463..496a0802c 100644
--- a/targets.cc
+++ b/targets.cc
@@ -480,6 +480,7 @@ Target *nexthost(HostGroupState *hs, TargetGroup *exclude_group,
if (hs->current_batch_sz == 0) /* Because later ones can have different src addy and be cut off group */
o.decoys[o.decoyturn] = t->v4source();
t->setDeviceNames(rnfo.ii.devname, rnfo.ii.devfullname);
+ t->setMTU(rnfo.ii.mtu);
// printf("Target %s %s directly connected, goes through local iface %s, which %s ethernet\n", t->NameIP(), t->directlyConnected()? "IS" : "IS NOT", t->deviceName(), (t->ifType() == devt_ethernet)? "IS" : "IS NOT");
}