I'm implementing my own IPv6 networking stack and am having problems getting ICMPv6 neighbor discovery to work. The stack works by capturing packets of an existing interface, picking out the ones addressed to it and sending out new packets from the "virtual" address of the stack.
To test this I have an Ubuntu machine, with an LXC container used as a testing client to send traffic from. The Ubuntu host has the following bridge interface for LXC containers:
ubuntu:~$ ifconfig
...
lxcbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.3.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::216:3eff:fe00:0 prefixlen 64 scopeid 0x20<link>
ether 00:16:3e:00:00:00 txqueuelen 1000 (Ethernet)
RX packets 522282 bytes 85867541 (85.8 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 554574 bytes 51636337 (51.6 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
...
The container has the following network interface:
my-continer:~$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.3.40 netmask 255.255.255.0 broadcast 10.0.3.255
inet6 fe80::216:3eff:fe72:dc1c prefixlen 64 scopeid 0x20<link>
ether 00:16:3e:72:dc:1c txqueuelen 1000 (Ethernet)
RX packets 554722 bytes 51647709 (51.6 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 522297 bytes 93181219 (93.1 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
...
If I ping the Ubuntu host from the LXC container, it of course works:
my-continer:~$ sudo tcpdump -i eth0 -nn icmp6 -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:56:44.435633 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe72:dc1c > ff02::1:ff00:0: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fe80::216:3eff:fe00:0
source link-address option (1), length 8 (1): 00:16:3e:72:dc:1c
0x0000: 0016 3e72 dc1c
11:56:44.435649 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe00:0 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is fe80::216:3eff:fe00:0, Flags [solicited, override]
destination link-address option (2), length 8 (1): 00:16:3e:00:00:00
0x0000: 0016 3e00 0000
11:56:44.435652 IP6 (flowlabel 0x4fb04, hlim 64, next-header ICMPv6 (58) payload length: 64) fe80::216:3eff:fe72:dc1c > fe80::216:3eff:fe00:0: [icmp6 sum ok] ICMP6, echo request, seq 1
11:56:44.435660 IP6 (flowlabel 0xbb56b, hlim 64, next-header ICMPv6 (58) payload length: 64) fe80::216:3eff:fe00:0 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, echo reply, seq 1
11:56:45.465749 IP6 (flowlabel 0x4fb04, hlim 64, next-header ICMPv6 (58) payload length: 64) fe80::216:3eff:fe72:dc1c > fe80::216:3eff:fe00:0: [icmp6 sum ok] ICMP6, echo request, seq 2
11:56:45.465768 IP6 (flowlabel 0xbb56b, hlim 64, next-header ICMPv6 (58) payload length: 64) fe80::216:3eff:fe00:0 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, echo reply, seq 2
11:56:46.490187 IP6 (flowlabel 0x4fb04, hlim 64, next-header ICMPv6 (58) payload length: 64) fe80::216:3eff:fe72:dc1c > fe80::216:3eff:fe00:0: [icmp6 sum ok] ICMP6, echo request, seq 3
11:56:46.490204 IP6 (flowlabel 0xbb56b, hlim 64, next-header ICMPv6 (58) payload length: 64) fe80::216:3eff:fe00:0 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, echo reply, seq 3
My own stack is configured with the mac address 0c:22:38:4e:9a:bc
and the IPv6 address fe80::216:3eff:fe00:1234
.
The implementation is in Erlang, and the code for processing the neighbor solicitation packet looks as follows:
process(#ipv6{headers = [#icmpv6{type = neighbor_solicitation, payload = {IP6, _}}|_]} = Packet, {IP6, Mac}) ->
#ipv6{src = Src} = Packet,
send(#ipv6{
src = IP6,
dst = Src,
next = ?IP_PROTO_ICMPv6,
hlim = 16#FF,
headers = [
{icmpv6, #icmpv6{
type = neighbor_advertisement,
code = 0,
payload = {IP6, #{source_link_layer_addr => Mac}}
}}
]
});
Packet
is the incoming neighbor solicitation request and {IP6, Mac}
is the IPv6 address and mac address that the stack has been configured with.
When pinging it, the packets reach the interface on the host and gets picked up by libpcap. I generate a response to the address resolution but pinging never succeeds:
my-continer:~$ ping6 -I eth0 fe80::216:3eff:fe00:1244
PING fe80::216:3eff:fe00:1244(fe80::216:3eff:fe00:1244) from fe80::216:3eff:fe72:dc1c%eth0 eth0: 56 data bytes
From fe80::216:3eff:fe72:dc1c%eth0 icmp_seq=1 Destination unreachable: Address unreachable
From fe80::216:3eff:fe72:dc1c%eth0 icmp_seq=2 Destination unreachable: Address unreachable
From fe80::216:3eff:fe72:dc1c%eth0 icmp_seq=3 Destination unreachable: Address unreachable
^C
--- fe80::216:3eff:fe00:1244 ping statistics ---
4 packets transmitted, 0 received, +3 errors, 100% packet loss, time 3065ms
The trace shows that the neighbor advertisement packets reach the container:
my-continer:~$ sudo tcpdump -i eth0 -nn icmp6 -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
12:04:23.751427 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe72:dc1c > ff02::1:ff00:1234: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fe80::216:3eff:fe00:1234
source link-address option (1), length 8 (1): 00:16:3e:72:dc:1c
0x0000: 0016 3e72 dc1c
12:04:23.753662 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe00:1234 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is fe80::216:3eff:fe00:1234, Flags [solicited, override]
source link-address option (1), length 8 (1): 0c:22:38:4e:9a:bc
0x0000: 0c22 384e 9abc
12:04:24.761850 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe72:dc1c > ff02::1:ff00:1234: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fe80::216:3eff:fe00:1234
source link-address option (1), length 8 (1): 00:16:3e:72:dc:1c
0x0000: 0016 3e72 dc1c
12:04:24.765901 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe00:1234 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is fe80::216:3eff:fe00:1234, Flags [solicited, override]
source link-address option (1), length 8 (1): 0c:22:38:4e:9a:bc
0x0000: 0c22 384e 9abc
12:04:25.786994 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe72:dc1c > ff02::1:ff00:1234: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fe80::216:3eff:fe00:1234
source link-address option (1), length 8 (1): 00:16:3e:72:dc:1c
0x0000: 0016 3e72 dc1c
12:04:25.789413 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::216:3eff:fe00:1234 > fe80::216:3eff:fe72:dc1c: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is fe80::216:3eff:fe00:1234, Flags [solicited, override]
source link-address option (1), length 8 (1): 0c:22:38:4e:9a:bc
0x0000: 0c22 384e 9abc
They look identical to the ones sent to and from the real interface, except for the addresses themselves.
Checking the routing table shows that the address resolution for the virtual address didn't work for some reason:
my-continer:~$ sudo ip -6 neigh
fe80::216:3eff:fe00:0 dev eth0 lladdr 00:16:3e:00:00:00 STALE
fe80::216:3eff:fe00:1234 dev eth0 FAILED
What is the reason for the failed neighbor discovery here? What is missing in my implementation of the ICMPv6 protocol to make the container accept the virtual address as a valid route?