1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-14 19:59:02 +00:00

Committing MTU-related changes:

* Adding path-mtu.nse for Path MTU Discovery
* Nmap now stores the MTU for interfaces (from SIOCGIFMTU or libdnet)
* Scripts can access the MTU for host.interface via host.interface_mtu
* Nmap prints the MTU for interfaces in --iflist
This commit is contained in:
kris
2010-08-24 01:47:12 +00:00
parent c3a1ec9f02
commit 57664a51cf
11 changed files with 459 additions and 5 deletions

View File

@@ -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]

View File

@@ -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,

View File

@@ -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 */

View File

@@ -1513,6 +1513,15 @@ LUALIB_API int luaopen_openssl(lua_State *L) {
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>host.interface_mtu</option>
</term>
<listitem>
<para>The MTU (maximum transmission unit) for <literal>host.interface</literal>,
or 0 if not known.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>host.bin_ip</option>
</term>

View File

@@ -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++;
}

View File

@@ -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 */
};

View File

@@ -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();

View File

@@ -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));

391
scripts/path-mtu.nse Normal file
View File

@@ -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

View File

@@ -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", } }

View File

@@ -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");
}