It's already 2021: IPv6 has been created  more than 20 years ago, and nowadays more than one third of the traffic on the Internet is using IPv6. Yet, in the previous 3 post about enabling NuttX on the ESP32, I made a big mistake by forgetting to enable IPv6!

So, in this post, we'll try to get an IPv6 configuration working. At the same time, we will try get rid of the IPv4 NAT which was required for the bridge setup, and which would not be necessary any more using IPv6 prefix delegation.

Configuration

Enabling IPv6 is as simple as:

Networking Support  --->
   Internet Protocol Selection  --->
      [*] IPv6

But of course, after all the features enabled in the past 3 post, the probability the the compilation works the first time is quite small. And indeed, here is the error:

xtensa-esp32-elf-ld: nuttxspace/nuttx/staging/libarch.a(esp32_wlan.o): in function `wlan_ifup':
esp32_wlan.c:(.text.wlan_ifup+0x45): undefined reference to `winfo'
make[1]: *** [nuttx] Error 1

Fortunately, that is just a typo issue with a function incorrectly named winfo while it should ninfo . So, after patching the file, compilation works fine

Run Time

After booting, the networking configuration is still not quite right:

nsh> ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c4 at UP
        inet addr:192.168.1.182 DRaddr:192.168.1.1 Mask:255.255.255.0
        inet6 addr: fc00::2/112
        inet6 DRaddr: fc00::1/112

Static IP address configuration

Before getting into the advanced IPv6 address configuration using DHCPv6 or SLAAC, let's first try instead to use a predefined IPv6 address. To make sue the configuration tool is working, let's just use the same IPv6 configuration  as the default one:

ifconfig wlan0 fc00::1 inet6 gateway fc00::2

That works fine. However, when trying to input longer IPv6 address, the NSH console fails to accept longer inputs... That's yet another configuration which should be changed, to allow "longer command lines"..

Application Configuration  --->
    NSH Library  --->
        Command Line Configuration  --->
        	(200) Max command line length

After this change, it is possible to configure the IPv6 address:

nsh> ifconfig eth0 2888:8201:2848:abcd:0123:0123:0123:d58e inet6 gateway 2888:8201:2848:abcd::1

nsh> ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c5 at UP
        inet addr:192.168.1.183 DRaddr:192.168.1.1 Mask:255.255.255.0
        inet6 addr: 2408:8206:2640:a833:4e5:c85d:bf9:d58e/64
        inet6 DRaddr: 2408:8206:2640:a833::1/64

Ping6 application

The default ping NSH command does not support IPv6. To enable ping6, the following needs to be added:

Networking Support  --->
   ICMPv6 Networking Support  --->
      [*] Enable ICMPv6 networking
Application Configuration  --->
   System Libraries and NSH Add-Ons  --->
   	  [*] ICMPv6 'ping6' command (NEW)

Voila, after fixing another small compilation bug due to an unreferenced function we are now ready to ping openwrt.com , aka 2600:3c02:1::2d4f:f40e:

nsh > ping6 2600:3c02:1::2d4f:f40e
psock_socket: ERROR: socket address family unsupported: domain:10, type:2, protocol:58
socket: ERROR: psock_socket() failed: -106
ERROR: socket() failed: 106

ICMPv6 socket support

Well... that did not work as expected! It fails on domain (family)= PF_INET6, type = SOCK_DGRAM and protocol = IPPROTO_ICMP6. The missing link is the NET_ICMPv6_SOCKET configuration which needs to be enabled too

Networking Support  --->
   ICMPv6 Networking Support  --->
      [*]   IPPROTO_ICMP6 socket support
      [*]   Solicit destination addresses

We also enable the "solicit destination addresses", which covers the ICMPv6 Neighbour Discovery (ND) protocol, for the solicitation packet. That's the same as ARP, but for IPv6. Without it, the stack would not be able to resolve the ethernet address associated to the IPv6 gateway.

nsh> ping6 2600:3c02:1::2d4f:f40e
sendto_request: Outgoing ICMPv6 packet length: 105 (65)
neighbor_dumpentry: Entry found: 2408:8206:2640:a833:0000:0000:0000:0001
neighbor_ethernet_out: Outgoing IPv6 Packet length: 119 (65)
emac_transmit: d_buf=0x3ffb2cb6 d_len=119
emac_recvframe: RX bytes 119
icmpv6_poll_eventhandler: flags: 0002
icmpv6_datahandler: Buffered 94 bytes
icmpv6_readahead: Received 65 bytes (of 94)
56 bytes from 2600:3c02:1::2d4f:f40e icmp_seq=0 time=410 ms

DNSv6 support

Let's try to ping6 with the domain name instead of the direct IP address:

nsh> ping6 openwrt.com
udp_send: Outgoing UDP packet length: 57
udp_readahead: Received 57 bytes (of 74)
dns_recv_response: ID 37905
dns_recv_response: Query 128
dns_recv_response: Error 0
dns_recv_response: Num questions 1, answers 1, authrr 0, extrarr 0
dns_recv_response: Question: type=001c, class=0001
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=001c, class=0001, ttl=0034ab, length=0010
dns_recv_response: IPv6 address: 0000:0000:0100:0000:0200:3c00:0000:2600
...
udp_readahead: Received 45 bytes (of 62)
dns_recv_response: ID 46525
dns_recv_response: Query 128
dns_recv_response: Error 0
dns_recv_response: Num questions 1, answers 1, authrr 0, extrarr 0
dns_recv_response: Question: type=0001, class=0001
dns_parse_name: Compressed answer
dns_recv_response: Answer: type=0001, class=0001, ttl=0034cf, length=0004
dns_recv_response: IPv4 address: 72.52.179.175
PING6 2600

sendto_request: Outgoing ICMPv6 packet length: 105 (65)
icmpv6_readahead: Received 65 bytes (of 94)
56 bytes from 2600:3c02:1::2d4f:f40e icmp_seq=0 time=450 ms

Good news, all works fine, so no special configuration needed here.

Let's SLAAC ...

When it comes to IPv6, the common approach is to use SLAAC rather than DHCPv6. The main advantage of SLAAC is its simplicity - it requires to have ICMPv6 (which we have already confirmed to work), and support for two Neighbour discovery (ND) packets: Router discovery and advertisement  (RD/RA).

Looking at the NuttX code, one can find the needed code to handle router solicitations:

/****************************************************************************
 * Name: icmpv6_rsolicit
 *
 * Description:
 *   Set up to send an ICMPv6 Router Solicitation message.  This version
 *   is for a standalone solicitation.  If formats:
 *
 *   - The IPv6 header
 *   - The ICMPv6 Neighbor Router Message
 *
 *   The device IP address should have been set to the link local address
 *   prior to calling this function.
 *
****************************************************************************/

void icmpv6_rsolicit(FAR struct net_driver_s *dev);

This code is only available when activating the configuration is NET_ICMPv6_AUTOCONF .

Networking Support  --->
   ICMPv6 Networking Support  --->
       [*]   ICMPv6 auto-configuration

Compilation works fine, but after flashing the new firmware and sending the interface UP using the ifconfig eth0 up , nothing happens - and only IPv4 address is obtained via the DHCPv4.

The missing link is the netlib_icmpv6_autoconfiguration function, which is responsible for sending the SIOCIFAUTOCONF ioctl to the driver, which will in turn call the icmpv6_autoconfig function, and then call our icmpv6_rsolicit. The problem is that this netlib_icmpv6_autoconfiguration function is only called during the netlib initialisation process (aka netinit), but not during the nsh net commands, especially the ifconfig eth0 up one . A quick fix consists is adding this line before:

git diff nshlib/nsh_netcmds.c                                    08:49:40
diff --git a/nshlib/nsh_netcmds.c b/nshlib/nsh_netcmds.c
index d4633e10..1e4605c4 100644
--- a/nshlib/nsh_netcmds.c
+++ b/nshlib/nsh_netcmds.c
@@ -918,6 +918,12 @@ int cmd_ifconfig(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv)
 #endif /* CONFIG_NET_IPv4 */
 #endif /* CONFIG_NETINIT_DHCPC || CONFIG_NETINIT_DNS */

+#ifdef CONFIG_NET_ICMPv6_AUTOCONF
+  /* Perform ICMPv6 auto-configuration */
+  netlib_icmpv6_autoconfiguration(ifname);
+#endif
+
+
 #if defined(CONFIG_NETINIT_DHCPC)
   /* Get the MAC address of the NIC */

Voila, after this fix, the ifconfig eth0 up will successfully acquire it's global IPv6 via SLAAC:

nsh> ifconfig eth0 up
icmpv6_autoconfig: Auto-configuring eth0
icmpv6_autoconfig: lladdr=fe80:0000:0000:0000:aa03:2aff:fea1:2cc5
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_solicit: Outgoing ICMPv6 Neighbor Solicitation length: 72 (32)
icmpv6_router_eventhandler: flags: 6000 sent: 0
icmpv6_rsolicit: Outgoing ICMPv6 Router Solicitation length: 56 (16)
icmpv6_rwait: Waiting...
icmpv6_rnotify: Notified
icmpv6_setaddresses: preflen=64 netmask=ffff:ffff:ffff:ffff:0000:0000:0000:0000
icmpv6_setaddresses: prefix=2408:8206:2640:a833:0000:0000:0000:0000
icmpv6_setaddresses: IP address=2408:8206:2640:a833:aa03:2aff:fea1:2cc5
icmpv6_setaddresses: DR address=fe80:0000:0000:0000:ee41:18ff:fe0c:53dc
icmpv6_rwait_cancel: Canceling...
icmpv6_autoconfig: Timed out... retrying 1
icmpv6_router_eventhandler: flags: 6000 sent: 0
icmpv6_rsolicit: Outgoing ICMPv6 Router Solicitation length: 56 (16)
icmpv6_rnotify: Notified
icmpv6_setaddresses: preflen=64 netmask=ffff:ffff:ffff:ffff:0000:0000:0000:0000
icmpv6_setaddresses: prefix=2408:8206:2640:a833:0000:0000:0000:0000
icmpv6_setaddresses: IP address=2408:8206:2640:a833:aa03:2aff:fea1:2cc5
icmpv6_setaddresses: DR address=fe80:0000:0000:0000:ee41:18ff:fe0c:53dc
icmpv6_rwait: Waiting...
icmpv6_rwait_cancel: Canceling...

Voila, it's not fully working :-)

nsh> ifconfig
eth0    Link encap:Ethernet HWaddr a8:03:2a:a1:2c:c5 at UP
        inet addr:192.168.1.183 DRaddr:192.168.1.1 Mask:255.255.255.0
        inet6 addr: 2408:8206:2640:a833:aa03:2aff:fea1:2cc5/64
        inet6 DRaddr: fe80::ee41:18ff:fe0c:53dc/64

Conclusion

That's was not a straightforward configuration, and there are quite a few patches needed for the NuttX source code to compile, but fortunately, IPv6 is now working natively on our ESP32 hardware running NuttX. The natural next step would be to even remove the IPv4 configuration and only get an IPv6 network, but we'll keep this configuration for a later post.

In the next post, we'll be looking at enabling the DHCPv6 server and prefix delegation, as a way to remove the need for the IPv4 NAT in our ETH-WIFI bridge setup.

There are also now quite  a few fixes that should be pushed upstream, so I'll make a PR and update this post when the PR is ready.


Photo credits:  flickr.com/photos/anniemole/313981428/in/photostream/