Well, it's complicated... You've probably noticed that on occasion you will already see some of the host names appear in the DNS PTR lookups done by logstash or tcpdump or whatever. Those appear because some devices have DHCPv6 enabled and those addresses are recorded in /var/hosts/odchpd, where dnsmasq picks them up. (Try cat /var/hosts/odhcp and see what you get.)
For other hosts running SLAAC, the router never actually knows what the addresses are (at least with any confidence), and they often change at 12h or 24h intervals. You can see some of the current ones with ip -6 neigh show, which gives you both IPv6 and MAC addresses, which you can then process into IP-hostname pairs in a hosts file, but be aware that these addresses come and go.
To start you on your journey, you can create that "other" host file like this:
$ ifconfig eth0 | grep inet6
addr=<grab one of the addresses 2600:9900:...:8d8c or whatever>
$ echo "$addr router.lan" >> /var/hosts/my_lan_hosts
$ killall -1 dnsmasq
... dnsmasq re-reads all its config, including new 'my_lan_hosts' ...
$ nslookup router.lan
... should show at least the value entered above ...
And also the one that states that all my android devices are especially more problematic as they won't offer - even with a manual config - any way to go with dhcpv6 (contrary to one only iphone in my lan, which is resolved).
My understanding is that SLAAC makes the newly connected device constructs it's ipv6 based on the ULA value in the RA emitted by my router in response to a broadcast request sent by the device.
=> Would it be any way to customize the ULA value responded in the RA by the router, based on the MAC address seen in the broadcast request ? To give a prefix wich would be a subnet of the initial /64 provided by my ISP ?
SLAAC creates a new IPv6 address based on the network prefix and the device ID suffix, which can be random or based on the MAC. If you disable the privacy you can have the same addresses Link Local, Unique Local Address, and Global Unicast Address (if the network prefix is the same).
No need to change the ULA prefix.
In general, if you have a static prefix delegated by your ISP you can create some hostname entries for the devices which don't use DHCPv6, as long as you turn off the randomization of the device id.
Ok, you sent me down a rabbit hole, so I whipped up this script to harvest neighbors and create a hosts file. Save it on your router as, say, ip6scan, then run with ip6scan scan to see what it will generate. You'll need to fix the function special_case_names to contain MAC-hostname pairs specific to your network, but hopefully most of the host names will be detected automatically.
EDIT: Also, change the information in the isp_router by using route -A inet6 and picking through for the IP address of your ISP's upstream. I just made up the name "cox_upstream" to be something that would be obvious to me.
Once you've got those sorted, run ip6scan update and it will populate the hosts file and restart dnsmasq, then your logstash output should show host names instead of IPs for your local devices. (The update will also scan the existing hosts file and add new ones as they are created, so run it a couple times and see what it does.)
To undo it, simply rm /var/hosts/ipv6neigh ; killall -1 dnsmasq and you're back to normal (this should be an option in the script, exercise left to the reader ).
#!/bin/sh
# vim: set expandtab softtabstop=4 shiftwidth=4:
#-------------------------------------------------------------------------------
# Scan the network for unknown IPv6 addresses, attempt to associate them with
# a hostname via their MAC as it appears in the IPv4 DHCP leases file.
# Record any newly found addresses in a dnsmasq hosts file.
#-------------------------------------------------------------------------------
action="${1:-"scan"}"
isp_router="fe80::227:90ff:fe0d:5419 cox_upstream cox.net"
special_case_names() {
# This is a high-maintenance bit. Maybe put it in config???
#
# Special case known IPv6 devices that don't report host names in DHCPv4.
# Android phones, some amazon devices, others? TODO: put in config...
name="$1"
mac="$2"
case "$mac" in
'48:43:dd:11:22:33') name='kindle' ;;
'80:58:f8:11:22:33') name='phone' ;;
esac
echo "$name"
}
hostname=$(uci get system.@system[0].hostname)
domain=$(uci get dhcp.lan.domain)
router_lan_addresses=$(ifstatus lan |
jsonfilter -e '@["ipv6-prefix-assignment"][*]["local-address"].address')
neighbors=/var/hosts/ipv6neigh
v4leases=/var/dhcp.leases
v6leases=/var/hosts/odhcpd
now=$(date -Im | sed 's/......$//')
[ ! -e "$neighbors" ] && touch $neighbors
#-------------------------------------------------------------------------------
write_line() {
# Record the given ip and name, but only if the entry doesn't already
# appear in the neighbors file.
local ip6="$1"
local name="$2"
local domain="$3"
if grep -q "$ip6" "$neighbors" ; then
# echo "already there $ip6"
return
fi
if grep -q $ip6 "$v6leases" ; then
printf "%-40s %-30s %s\n" "#$ip6" "$name" "-- defined via DHCP"
return
fi
printf "%-40s %-30s %-16s #%s\n" "$ip6" "${name}.${domain}" "$name" "$now"
}
write_host() {
# Search the IPv4 dhcp leases for a specific MAC and use the corresponding
# host name to create IPv6 entries in odhcp's dnsmasq directory.
# The output from ip neigh contains "*" for host names, hence noglob.
set -o noglob
local ip6="$1"
local mac="$2"
local host=$(grep "$mac" "$v4leases")
if [ -n "$host" ] ; then
local name=$(echo "$host" | awk '{print $4}')
name=$(special_case_names "$name" "$mac")
if [ "$name" = "*" ] ; then
echo "# Unknown host, add: '$mac') name='hostname' ;;"
else
write_line "$ip6" "$name" "$domain"
fi
fi
}
#-------------------------------------------------------------------------------
_scan() {
# Special case our upstream router. Discover via 'route -A inet6'.
write_line $isp_router
# Gather "self" addresses on both up- and downstream interfaces.
for iface in "br-lan" "eth0" ; do
for ip6 in $(ifconfig $iface | awk 'sub(/\/.*/, "") {print $3}') ; do
write_line "$ip6" "${hostname}-${iface}" "$domain"
done
done
# Collect all the true neighbors.
ip -6 neigh show dev br-lan | grep -E 'REACHABLE|STALE' | while IFS= read -r line ; do
write_host $(echo "$line" | awk '{print $1,$3}')
done
}
scan_all() {
# "Prime the pump" by pinging the all-nodes address using the ULA
# and GUA of the router's lan interface.
# The prefix extraction is pretty sketchy, but it works for me.
for addr in $router_lan_addresses ; do
prefix=$(echo "$addr" | awk 'BEGIN {FS="::"} ; {print $1}')
for a in $(ping6 -c3 -I $addr ff02::1%br-lan |
awk "/bytes from $prefix/"' {print substr($4, 1, length($4)-1)}') ; do
ping6 -c1 $a >> /dev/null
done
done
_scan | sort -fb +1
}
#-------------------------------------------------------------------------------
case "${action}" in
"scan")
scan_all
;;
"update")
new="/tmp/ips$$"
n_old=$(cat $neighbors | wc -l)
scan_all | sort -u $neighbors - | sort -fb +1 > $new
diff $new $neighbors
if [ "$?" -eq 0 ] ; then
echo 'No new addresses...'
rm $new
else
mv $new $neighbors
killall -1 dnsmasq
logread -e dnsmasq | tail -5
n_new=$(cat $neighbors | wc -l)
echo "$(($n_new - $n_old)) new addresses..."
fi
;;
esac
If privacy extensions are disabled on the SLAAC client, the IPv6 address can be turned back into a MAC address and looked up in the DHCPv4 lease database. Basically take the last 64bit of the address, strip out the ff:fe part in the middle and mask byte 1 with ~0x02.
Iirc dnsmasq uses this approach to cross-correlate IPv4 and IPv6 identities of hosts when it is serving both DHCPv6 and RAs. However on OpenWrt, IPv6 handling is done by odhcpd, so this mechanism does not come into play.
Yeah, most of my hosts do have EUI-64 addresses, but I've also got net.ipv6.conf.eth0.use_tempaddr = 2 on all my linux boxes, so they cycle through SLAAC pretty quickly. That's why I tried the ping ff02::1 and ip -6 neigh show to get most of them to show up. Seems to work well enough that my tcpdump traces show all lan devices with symbolic names; I wouldn't put it into production, but great for quick debugging.
Hmm, now that I look, there are six hosts (of 22) without any EUI-64 LLA (or ULA or GUA):
1 x Raspian on a Pi
1 x Fedora on x86
4 x Windows on x86
Looks like Windows has full privacy options set by default (although they also all get DCHPv6 addresses).
There were (very) few things that weren't working out of the box in my case, namely:
the two sort -fb +1 : on my router, +1 is interpreted as a filename, so I had to remove it to avoid an error
sort: +1: No such file or directory
domain=$(uci get dhcp.lan.domain) doesn't work in my case ; i hardcoded it (lan) ; a check in my /etc/config/dhcp file shows option domain 'lan' in my section config dnsmasq, not in config dhcp 'lan' ; maybe a misconfig i introduced ?
also, obviously,i had to opkg install diffutils
Now it's working like a charm ; many, many thanks for this script !
Well, that's odd. I'm using the standard sort that's part of coreutils... In any case, the +1 is intended to sort on "field + 1", i.e., the host name column of the file, so it doesn't really matter as long as it's sorted consistently for later when the old and new files are passed to diff.
Could you do a which sort then ll on it and tell me if yours is also from coreutils? That sort of bothers me that it didn't just work (I just tried it on a couple of different releases, 22.03 and snapshot, works in both)...
The domain value could be anything, really, I don't think it's a misconfiguration on your part, just that I always set both of those values identically in my setups. Hardcoding it to 'xyz.network', or anything, is just fine.
Oops, I forgot about diff being an "extra"! I should add a hash diff || echo 'Run: opkg install diffutils' or something like that at the top...
If it matters to anyone, -k2 can be used in place of the +1 on the sort commands to sort the host names file by grouping the hosts together, rather than sorting by the IP... Might make it easier to navigate, if you're inclined to edit it manually.
Nevermind! I found that adblock installs the coreutils sort. By default, 'sort' is supplied by busybox, which supports neither the '+n' nor '-kF' options (it errors on the + and silently ignores the -k).