0

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?

Adam Lindberg
  • 16,447
  • 6
  • 65
  • 85
  • You don't show any code for your neighbor discovery. For example, how are you calculating the solicited-node multicast addresses? You need to join the multicast group(s) for your solicited node address(es) on your interface, and you send multicast to the solicited node address of the target. – Ron Maupin Mar 28 '18 at 13:28
  • I added the code for the neighbor advertisement. The solicited-node multicast address is the one generated by the `ping6` program on the client, not in my stack, right? I'm just trying to respond to the neighbor solicitation request. Two questions: 1. Do I reply to the correct address in my ICMPv6 neighbor advertisement, and 2. If I need to join the multicast group for my stack address, how would I go about doing that in an implementation based on libpcap? – Adam Lindberg Mar 28 '18 at 14:00
  • The solicited node multicast address needs to be generated by your stack. You send neighbor discovery messages to the solicited node multicast addresses, and you use the IPv6 multicast OUI for the layer-2 address. Your stack must also subscribe to any solicited node multicast addresses for the addresses assigned to the interface. – Ron Maupin Mar 28 '18 at 14:06
  • The question is about responding to neighbor discovery requests, not sending them. About subscribing to the solicited node multicast address, I'm not sure it is necessary. I clearly receive the packet, can generate and send the reply and see it reach the client (as seen in the tcpdump output above), it's just not saved in the client. I fail to see how anything I do on the host (where the stack lives) should affect the received response on the client? – Adam Lindberg Mar 28 '18 at 14:12
  • IPv6 requires that you subscribe to the solicited node multicast address for every IPv6 address assigned to an interface. That is why it helps to have the last 64 bits of all your assigned addresses be the same. ND requests are send to the solicited node multicast address. IPv4 ARP uses broadcast, and that interrupts every host on the LAN. IPv6 doesn't have broadcast, so it uses the solicited node multicast, which probably only interrupts one host subscribing to that multicast group. This was an IPv6 improvement over IPv4. – Ron Maupin Mar 28 '18 at 14:18
  • "Requires" it how? How would I do this using libpcap? I clearly get the messages already, and the responses are sent to the client (over which I have "no control" so to speak, since my stack is not running there). How come the client doesn't add the address to its routing table? – Adam Lindberg Mar 28 '18 at 14:20
  • Check _[RFC 4291, IP Version 6 Addressing Architecture, Section 2.8, A Node's Required Addresses](https://tools.ietf.org/html/rfc4291#section-2.8)_: "_The Solicited-Node multicast address for each of its unicast and anycast addresses._" – Ron Maupin Mar 28 '18 at 14:22
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/167742/discussion-between-adam-lindberg-and-ron-maupin). – Adam Lindberg Mar 28 '18 at 14:31

0 Answers0