Problem with converting IPv6 addresses to strings using ipv6_addr_to_str

Hello everyone :slight_smile:

I am trying to retrieve an IPv6 address with the “highest” avaible scope and convert it into a string. While retrieving the IP addresses and determining their scope works fine I have, however, problems converting an ipv6_addr_t * pointer into a sting using ipv6_addr_to_str. Despite the fact that I used the same code as in the definition of ipv6_addr_print (see here), ipv6_addr_to_str changes the ip address that is represented by the string. Using ipv6_addr_print itself, however, prints out the correct result. I encountered this behavior both on native as well as on an ESP32.

Is there some way for me to fix this or could this be a bug within the ipv6_addr_to_str function? Thanks in advance!

You should be able to reproduce the problem using the following code and make all term:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "xtimer.h"
#include "shell.h"
#include "fmt.h"
#include "net/netif.h"
#include "net/ipv6/addr.h"

#define MAIN_QUEUE_SIZE (4)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];

static ipv6_addr_t *get_base_ip_address(void){
    const int MAX_ADRESSES_TO_CHECK = 5;
    netif_t* interface = NULL;
    ipv6_addr_t* local_address = NULL;
    ipv6_addr_t* ula_address = NULL;

    while ((interface = netif_iter(interface)) != NULL) {
        ipv6_addr_t adresses[MAX_ADRESSES_TO_CHECK];
        netif_get_opt(interface, NETOPT_IPV6_ADDR, 0, adresses, sizeof(adresses));
        for (int i = 0; i < MAX_ADRESSES_TO_CHECK; i++)
            ipv6_addr_t* current_address = &adresses[i];

            if (current_address == NULL) {
            printf("Loop begin\n");
            if (ipv6_addr_is_global(current_address)) {
                printf("Global address: ");
                printf("Loop end\n");
                return current_address;
            else if (ipv6_addr_is_unique_local_unicast(current_address)) {
                printf("Unique local unicast: ");
                ula_address = current_address;
            else if (ipv6_addr_is_link_local(current_address)) {
                printf("Link local address: ");
                local_address = current_address;
            printf("Loop end\n");

    if (ula_address != NULL) {
        return ula_address;
        return 0;
    else if (local_address != NULL) {
        return local_address;
    return NULL;

int ip_test_cmd(int argc, char **argv)
    if (argc != 2)
        (void)puts("Usage: iptest test");
        return 1;
    if (strcmp(argv[1], "test") == 0) 
        char address_as_string[IPV6_ADDR_MAX_STR_LEN];
        ipv6_addr_t *base_ip_adress = get_base_ip_address();
        ipv6_addr_to_str(address_as_string, base_ip_adress, sizeof(address_as_string));
        return 0;

    return 1;

static const shell_command_t shell_commands[] = {
    {"iptest", "Get an IP address of the highest order possible.", ip_test_cmd},
    {NULL, NULL, NULL}};

int main(void)
    /* for the thread running the shell */
    msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);

    /* start shell */
    puts("All up, running the shell now");
    char line_buf[SHELL_DEFAULT_BUFSIZE];
    shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);

    return 0;


# name of your application
APPLICATION = ipaddr_test

# If no BOARD is found in the environment, use this default:
BOARD ?= native

# This has to be the absolute path to the RIOT base directory:

# Include packages that pull up and auto-init the link layer.
# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
USEMODULE += gnrc_netdev_default
USEMODULE += auto_init_gnrc_netif
# Activate ICMPv6 error messages
USEMODULE += gnrc_icmpv6_error
# Specify the mandatory networking modules for IPv6 and UDP
USEMODULE += gnrc_ipv6_router_default
USEMODULE += gnrc_udp
# Add a routing protocol
USEMODULE += gnrc_rpl
USEMODULE += auto_init_gnrc_rpl
# This application dumps received packets to STDIO using the pktdump module
USEMODULE += gnrc_pktdump
# Additional networking modules that can be dropped if not needed
USEMODULE += gnrc_icmpv6_echo
# Add also the shell, some shell commands
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += netstats_l2
USEMODULE += netstats_ipv6
USEMODULE += netstats_rpl

# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:

# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1

include $(RIOTBASE)/Makefile.include

Hi @JKRhb , I’m not sure of your problem but might help knowing that ipvN_addr_from_str (being N, 4 or 6) modifies the input buffer. I had a similar issue some time ago, and I had to check how it was coded to solve it. Maybe is happening similar with your issue.

Have a nice one :slight_smile:

1 Like

Hi, you’re returning the pointer to an ipv6_addr_t struct from addresses, which is defined in get_base_ip_address. I’m think the pointer will be undefined after returning to the function.

The better way to return the address after calling get_base_ip_address would be to accept a ipv6_addr_t struct pointer as parameter and in get_base_ip_address, use memcpy to copy the found addres to the parameter back.

Something like this:

int get_base_ip_address(ipv6_addr_t *res) {
    if (ipv6_addr_is_global(current_address) {
        memcpy(res, current_address, sizeof(ipv6_addr_t));
        return 0;

ipv6_addr_t base_ip_address = {0};
if (get_base_ip_address(&base_ip_address) < 0) {
    return -1;

Works like a charm now :slight_smile: Thank you so much!