For the first part you can use nmap as long as you can somehow limit the range, as per your comment this already should give you the host you want:
sudo nmap -PR -sn 192.168.0-255.0-255 -e <interface-to-test>
This does discovery based on ARP and if succesful an additional ICMP ping for aliveness afterwards. This one took me about a second on a range with no active local hosts and 5s with 4 active hosts. So you can even expand it to a bit larger range, but not the full IPv4 address space unless you have a day or two. In that case I'd just hook up wireshark or tcpdump and wait for a gratuitous ARP.
edit: For this to work you have to configure your "source machine" with an IP in the subnet you want to test. I assumed it would use the DAD mode of ARP when going out of the subnet or when no ip is configured, but it just doesn't do anything. I added a more generic version to a script I wrote for the algorithm below, but it is a bit slower than simply using nmap to get this result.
Detecting the configured netmask is a bit trickier. But I think this procedure would work, the main idea is that a host will send out an ARP request for hosts in its subnet and nothing or an ARP request for its default-gw for hosts not in its subnet.
- Start with the second to smallest subnet
N=29
. - Pick an IP
X
from this subnet formed by the host's IP and the subnet maskN
. Make sure the picked IP is not the host's IP and not network/broadcast. Also make sure this IP is not a part of the subnet formed by the host's IP and maskN+1
. - Ping the other host with source
X
(you don't care if it answers, just send out a request) - If you see an ARP request for
X
, decreaseN
with one. go back to 2 - If you don't see an ARP request for
X
,N+1
is the subnet searched.
One flaw might be that a overambitious network stack implementation might learn the MAC from the incoming ICMP request, but I personally do not know of any end-device stack that works this way.
I don't know if there are tools that do this for you, but it should be easy to do manually with ping, tcpdump and a subnet calculator ;). Or if you feel up to some hacking, it's probably not that much work to implement this with scapy
I went along and wrote a full python scapy script myself that should work, I tested it on my home network on a linksys homegw, another linux machine and an android device:
from __future__ import print_function, absolute_import, unicode_literals
from scapy.base_classes import Net
from scapy.config import conf
from scapy.layers.inet import Ether, ARP, ICMP, IP
from scapy.sendrecv import srp, debug
import scapy.route
iface = b'eth0'
subnet_to_test = b'192.168.1.0/24'
#or:
subnet_to_test = b'192.168.1.*'
#IP/MAC discovery
pkt = Ether(dst=b'ff:ff:ff:ff:ff:ff') / ARP(psrc=b'0.0.0.0', pdst=subnet_to_test, hwdst=b'ff:ff:ff:ff:ff:ff')
responses = srp(pkt, timeout=1, retry=0, verbose=0, iface=iface)
for r in responses[0]:
found = r[1].getfieldval('psrc')
foundmac = r[1].getfieldval('hwsrc')
n = 29
conf.debug_match = 1
while n > -1:
net = Net("{}/{}".format(found, n))
my_src = net.choice()
while my_src in Net("{}/{}".format(found, n + 1)):
my_src = net.choice()
pkt = Ether(dst=foundmac) / IP(dst=found, src=my_src) / ICMP(type=8)
resp = srp(pkt, timeout=1, retry=0, verbose=0, iface=iface, filter=b'ether dst FF:FF:FF:FF:FF:FF and arp')
received = [x for x in debug.recv if x.haslayer(ARP) and x.getfieldval('pdst') == my_src]
received.extend(x[1] for x in resp[0] if x[1].haslayer(ARP) and x[1].getfieldval('pdst') == my_src)
if len(received) == 0:
print("Found host: {}/{} on mac {}".format(found, n + 1, foundmac))
break
n -= 1