Performing a DNS query on an OpenThread SED to get a synthesized IPv6 Address from an IPv4-only CoAP Server

I’ve been setting up an OpenThread network with multiple OT Sleepy End Devices (SEDs) that periodically transmit sensor data to an IPv4-only CoAP server. To enable communication between the OT devices and the CoAP server, I need the synthesized IPv6 address. To test DNS resolution, I first flashed an nRF54L15-DK with the ot-cli sample, configured the network settings (network key, PAN ID, channel), joined the network as an MTD, and then ran the following command:

> ot dns resolve4 <hostname>

This command returned the synthesized IPv6 address of the CoAP server, indicating that the OpenThread Border Router (OTBR)—which is based on a Raspberry Pi 4 and an nRF52840 dongle—is functioning correctly. Therefore, it appears that NAT64/DNS64 is working as expected.

Then, I used the synthesized IPv6 address to send data to the CoAP server using the code below, and I was able to verify that the data successfully arrived at the server:

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>

#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor/sht4x.h>

#include <zephyr/net/openthread.h>
#include <openthread/coap.h>
#include <openthread/link.h>

LOG_MODULE_REGISTER(main_app, LOG_LEVEL_INF);

static const struct device* sht_dev = DEVICE_DT_GET(DT_NODELABEL(sht4x));

struct sensor_data {
  float relativeHumidity;
  float temperature;
};

char myPayloadJson[256] = "";

void coap_init(void) {
  otInstance* p_instance = openthread_get_default_instance();
  otError error = otCoapStart(p_instance, OT_DEFAULT_COAP_PORT);
  if (error != OT_ERROR_NONE) {
    LOG_ERR("Failed to start CoAP: %d. (line %i)\n", error, __LINE__);
  }
}

static void coap_send_data_response_cb(void* p_context, otMessage* p_message, const otMessageInfo* p_message_info, otError result) {
  if (result == OT_ERROR_NONE) {
    LOG_INF("Delivery confirmed.\n");

  } else {
    LOG_WRN("Delivery not confirmed: %d\n", result);
  }
}

static void coap_send_data_request(void) {
  otError error = OT_ERROR_NONE;
  otMessage* myMessage;
  otMessageInfo myMessageInfo;
  otInstance* myInstance = openthread_get_default_instance();


  otIp6Address serverAddress;
  otIp6AddressFromString("/*CoAP server ipv6 string here */", &serverAddress);

  do { 
    myMessage = otCoapNewMessage(myInstance, NULL);
    if (myMessage == NULL) {
      LOG_ERR("Failed to allocate message for CoAP request\n");
      return;
    }
    otCoapMessageInit(myMessage, OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_PUT);

    error = otCoapMessageAppendUriPathOptions(myMessage, "put-resource");
    if (error != OT_ERROR_NONE) {
      break;
    }

    error = otCoapMessageAppendContentFormatOption(myMessage, OT_COAP_OPTION_CONTENT_FORMAT_JSON);
    if (error != OT_ERROR_NONE) {
      break;
    }

    error = otCoapMessageSetPayloadMarker(myMessage);
    if (error != OT_ERROR_NONE) {
      break;
    }

    error = otMessageAppend(myMessage, myPayloadJson, strlen(myPayloadJson));
    if (error != OT_ERROR_NONE) {
      break;
    }

    memset(&myMessageInfo, 0, sizeof(myMessageInfo));
    memcpy(&myMessageInfo.mPeerAddr, &serverAddress, sizeof(serverAddress));
    myMessageInfo.mPeerPort = OT_DEFAULT_COAP_PORT;

    error = otCoapSendRequest(myInstance, myMessage, &myMessageInfo, coap_send_data_response_cb, NULL);
  } while (false);

  if (error != OT_ERROR_NONE) {
    LOG_ERR("Failed to send CoAP Request: %d. (line %i)\n", error, __LINE__);
    otMessageFree(myMessage);
  } else {
    LOG_INF("CoAP data transmitted. (line %i)\n", __LINE__);
  }
}


int main(void) {
  struct sensor_data data;
  struct sensor_value temp, humidity;
  int ret;

  if (device_init(sht_dev) || !device_is_ready(sht_dev)) {
    LOG_ERR("I2C device \"%s\" not found or not ready (line %i)", sht_dev->name, __LINE__);
    return 0;
  }


  /* Init CoAP */
  coap_init();

  while (1) {
   
    ret = sensor_sample_fetch(sht_dev);
    if (!ret) {
      sensor_channel_get(sht_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
      sensor_channel_get(sht_dev, SENSOR_CHAN_HUMIDITY, &humidity);
      LOG_INF("Temp: %d.%06d C, RH: %d.%06d%%", temp.val1, temp.val2, humidity.val1, humidity.val2);
    } else {
      LOG_ERR("Sensor read failed: %d", ret);
    }

    // Create the payload to transmit via OpenThread
    data.temperature = sensor_value_to_float(&temp);
    data.relativeHumidity = sensor_value_to_float(&humidity);

   snprintf(myPayloadJson, sizeof(myPayloadJson),
         "{\"relativeHumidity\":%.1f,\"temperature\":%.1f}",
         (double)data.relativeHumidity, (double)data.temperature);

    if (ret >= 0) {
      LOG_INF("JSON Output: %s\n", myPayloadJson);
    } else {
      LOG_ERR("JSON encoding failed: %d. (line %i)\n", ret, __LINE__);
    }
    coap_send_data_request();

    /* Go Sleep */
    k_sleep(K_SECONDS(10));
  }
}

However, since I have multiple devices, it becomes inconvenient when the CoAP server’s IPv4 address changes. In that case, I have to manually check the new synthesized IPv6 address, hardcode it into the firmware, and re-flash all devices — which is quite tedious. To solve this, I’d like to implement a DNS query directly on the OT SED devices so they can dynamically retrieve the IPv6 address. Since the ot-cli sample is capable of performing this query, it indicates that such functionality should be possible to implement.

I inspected the ncs ot-cli sample repository but couldn’t determine which source files correspond to each CLI command, making it difficult to replicate the implementation. How does the build system for this sample work? Does it use the official Google OpenThread repository? I was able to locate the code executed for the dns resolve4 command there:

I tried implementing the DNS query using my nRF Connect SDK application (using NCS v3.1). The application builds successfully, but when I run it, I don’t receive any response. My implementation is shown below:

main.c

#include <stdint.h>
#include <stdio.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#include <openthread/thread.h>
#include <openthread/instance.h>
#include <openthread/ip6.h>
#include <openthread/dns.h>
#include <openthread/dns_client.h>
#include <zephyr/net/openthread.h>

#define OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
#define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_NAT64_ENABLE

#define OPENTHREAD_CONFIG_IP6_ENABLE

LOG_MODULE_REGISTER(main_app, LOG_LEVEL_INF);

void handle_dns_response(otError aResult, const otDnsAddressResponse* aResponse, void* aContext) {
  if (aResult == OT_ERROR_NONE && aResponse) {
    otIp6Address address;
    uint32_t ttl;
    uint16_t index = 0;

    // Iterate over all addresses in the response
    while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE) {
      char buf[OT_IP6_ADDRESS_STRING_SIZE];
      otIp6AddressToString(&address, buf, sizeof(buf));

      LOG_INF("Synthesized IPv6: %s (TTL: %" PRIu32 "). (line %i)", buf, ttl, __LINE__);
      // ...
      index++;
    }
  } else {
    LOG_ERR("DNS resolution failed: %d. (line %i)", aResult, __LINE__);
  }
}

void query_dns_address(otInstance* ot) {
  // otDnsQueryConfig config = {0};
  otError error;

  const otDnsQueryConfig* queryConfig = otDnsClientGetDefaultConfig(ot);

  if (queryConfig != NULL) {
    char buf[OT_IP6_ADDRESS_STRING_SIZE];
    otIp6AddressToString(&queryConfig->mServerSockAddr.mAddress, buf, sizeof(buf));
    LOG_INF("NAT64 Server Addr: %s. (line %i)", buf, __LINE__);
  } else {
    LOG_WRN("No DNS query config available. (line %i)", __LINE__);
  }

  error = otDnsClientResolveIp4Address(ot, "time.google.com", handle_dns_response, NULL, queryConfig);

  switch (error) {
    case OT_ERROR_NONE:
      LOG_INF("Query sent successfully. aCallback will be invoked to report the status. (line %i)", __LINE__);
      break;
    case OT_ERROR_NO_BUFS:
      LOG_INF("Insufficient buffer to prepare and send query. (line %i)", __LINE__);
      break;
    case OT_ERROR_INVALID_ARGS:
      LOG_INF("The host name is not valid format or NAT64 is not enabled in config. (line %i)", __LINE__);
      break;
    case OT_ERROR_INVALID_STATE:
      LOG_INF("Cannot send query since Thread interface is not up. (line %i)", __LINE__);
      break;
  }
}

int main(void) {
  otInstance* instance = openthread_get_default_instance();

  if (instance == NULL) {
    LOG_INF("Failed to get default OpenThread instance");
    return -1;
  }

  while (1) {
    for (int i = 0; i < 5000; i++) {
      LOG_INF("%i", i);

      /* Check OT Role */
      otDeviceRole role = otThreadGetDeviceRole(openthread_get_default_instance());

      switch (role) {
        case OT_DEVICE_ROLE_DISABLED:
          LOG_INF("The Thread stack is disabled.");
          break;
        case OT_DEVICE_ROLE_DETACHED:
          LOG_INF("Not currently participating in a Thread network/partition.");
          break;
        case OT_DEVICE_ROLE_CHILD:
          LOG_INF("The Thread Child role.");
          break;
        case OT_DEVICE_ROLE_ROUTER:
          LOG_INF("The Thread Router role.");
          break;
        case OT_DEVICE_ROLE_LEADER:
          LOG_INF("The Thread Leader role.");
          break;
      }

      query_dns_address(instance);
      k_sleep(K_SECONDS(10));
    }
  }

  return 0;
}


prj.conf:

# STACKS
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_MAIN_STACK_SIZE=4096

CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y

# RTT
CONFIG_USE_SEGGER_RTT=y

# LOGS
CONFIG_LOG=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_RTT_BUFFER=0
CONFIG_SENSOR_LOG_LEVEL_DBG=y

# OpenThread (Generic)
CONFIG_NETWORKING=y
CONFIG_OPENTHREAD=y
CONFIG_NET_IPV6=y
CONFIG_NET_L2_OPENTHREAD=y
CONFIG_OPENTHREAD_THREAD_VERSION_1_3=y
CONFIG_OPENTHREAD_COAP=y
CONFIG_OPENTHREAD_MANUAL_START=n
CONFIG_OPENTHREAD_DNS_CLIENT=y
CONFIG_DNS_RESOLVER=y
CONFIG_OPENTHREAD_SNTP_CLIENT=y
# OpenThread Sleep-End-Device
CONFIG_OPENTHREAD_MTD=y
#CONFIG_OPENTHREAD_MTD_SED=y 
#CONFIG_OPENTHREAD_POLL_PERIOD=30000

# OpenThread Network identity
CONFIG_OPENTHREAD_NETWORK_NAME="my-ot"
CONFIG_OPENTHREAD_CHANNEL=12
CONFIG_OPENTHREAD_PANID=43537
CONFIG_OPENTHREAD_XPANID="de:e2:27:71:01:3c:35:26"
CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff"

# Enable UDP since SNTP uses UDP
CONFIG_NET_UDP=y

# Enable IPv6 (Thread uses IPv6)
CONFIG_NET_IPV6=y

# Enable sockets if you plan to use BSD socket APIs
CONFIG_NET_SOCKETS=y

# Increase networking stack size (important for OpenThread apps)
CONFIG_NET_TX_STACK_SIZE=2048
CONFIG_NET_RX_STACK_SIZE=2048

RTT Logs:

[00:01:20.063,012] <inf> main_app: 8
[00:01:20.063,020] <inf> main_app: The Thread Child role.
[00:01:20.063,119] <inf> main_app: NAT64 Server Addr: 2001:4860:4860:0:0:0:0:8888. (line 50)
[00:01:20.063,414] <inf> main_app: Query sent successfully. aCallback will be invoked to report the status. (line 59)
[00:01:28.062,251] <err> main_app: DNS resolution failed: 28. (line 37)
[00:01:30.063,492] <inf> main_app: 9
[00:01:30.063,501] <inf> main_app: The Thread Child role.
[00:01:30.063,599] <inf> main_app: NAT64 Server Addr: 2001:4860:4860:0:0:0:0:8888. (line 50)
[00:01:30.063,894] <inf> main_app: Query sent successfully. aCallback will be invoked to report the status. (line 59)
[00:01:38.064,683] <err> main_app: DNS resolution failed: 28. (line 37)
[00:01:40.063,971] <inf> main_app: 10
[00:01:40.063,980] <inf> main_app: The Thread Child role.
[00:01:40.064,079] <inf> main_app: NAT64 Server Addr: 2001:4860:4860:0:0:0:0:8888. (line 50)
[00:01:40.064,374] <inf> main_app: Query sent successfully. aCallback will be invoked to report the status. (line 59)
[00:01:48.064,107] <err> main_app: DNS resolution failed: 28. (line 37)
[00:01:50.064,452] <inf> main_app: 11
[00:01:50.064,460] <inf> main_app: The Thread Child role.
[00:01:50.064,559] <inf> main_app: NAT64 Server Addr: 2001:4860:4860:0:0:0:0:8888. (line 50)
[00:01:50.064,854] <inf> main_app: Query sent successfully. aCallback will be invoked to report the status. (line 59)
[00:01:58.064,491] <err> main_app: DNS resolution failed: 28. (line 37)
[00:02:00.060,795] <dbg> temp_nrf5_mpsl: temp_nrf5_mpsl_sample_fetch: sample: 119
[00:02:00.060,808] <dbg> temp_nrf5_mpsl: temp_nrf5_mpsl_channel_get: Temperature:29,750000
[00:02:00.064,929] <inf> main_app: 12
[00:02:00.064,934] <inf> main_app: The Thread Child role.
[00:02:00.065,032] <inf> main_app: NAT64 Server Addr: 2001:4860:4860:0:0:0:0:8888. (line 50)
[00:02:00.065,327] <inf> main_app: Query sent successfully. aCallback will be invoked to report the status. (line 59)
[00:02:08.064,907] <err> main_app: DNS resolution failed: 28. (line 37)

I'm getting the hard-coded default Google DNS from the OpenThread library instead of my local DNS64 server address (e.g., fd84:...). I know the local NAT64/DNS64 is working because when I use the ot-cli sample, I get the correct synthesized IPv6 addresses.

Are there any guidelines on what I might be missing and how to generate the synthesized IPv6 address in the firmware?

  • This is also a procedure required to obtain the time from an SNTP server: using the ot-cli sample, I can perform a DNS query to Google’s NTP server, and then use the resulting synthesized IPv6 address to perform an SNTP query:

    $ ot dns resolve4 time.google.com
    DNS response for time.google.com. - fd84:eb3c:3ceb:2:0:0:d8ef:230c TTL:4123 fd84:eb3c:3ceb:2:0:0:d8ef:2304 TTL:4123 fd84:eb3c:3ceb:2:0:0:d8ef:2300 TTL:4123 fd84:eb3c:3ceb:2:0:0:d8ef:2308 TTL:4123 
    
    $ ot sntp query fd84:eb3c:3ceb:2:0:0:d8ef:230c
    SNTP response - Unix time: 1762369805 (era: 0)
    Done

    I’d like to have both the DNS and SNTP queries working correctly. This would allow me to send data to the CoAP server over OpenThread using its hostname, and also include an up-to-date timestamp in the data payload. The plan is to perform an SNTP query at power-on to synchronize the time, and then use the internal RTC to keep Zephyr’s system time updated.

    I’ve been following the Google OpenThread API documentation from this webpage, which is very well organized. It clearly explains how to implement functions such as otDnsClientResolveIp4Address() and provides detailed information about the related data types.

  • Hi,

    I'll have to get back to you on this if if that is okay. I am not sure about this myself, so I'll have to find someone to ask. Let me know if how urgent this is changes, or anything pops-up.

    Regards,

    Elfving

  • Sure.  I’m looking forward to hearing your suggestions on how to handle this situation.

  • 1) I finally got the DNS query working! It turns out the main issue was the missing Kconfig option CONFIG_OPENTHREAD_SRP_CLIENT=y.

    main.c

    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include <zephyr/device.h>
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    
    #include <openthread/thread.h>
    #include <openthread/link.h>
    #include <openthread/instance.h>
    #include <openthread/ip6.h>
    #include <openthread/dns.h>
    #include <openthread/dns_client.h>
    #include <zephyr/net/openthread.h>
    
    #define OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE 
    #define OPENTHREAD_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT 10000
    
    LOG_MODULE_REGISTER(main_app, LOG_LEVEL_INF);
    
    
    void handle_dns_response(otError aResult, const otDnsAddressResponse* aResponse, void* aContext) {
      if (aResult == OT_ERROR_NONE && aResponse) {
        otIp6Address address;
        uint32_t ttl;
        uint16_t index = 0;
    
        // Iterate over all addresses in the response
        while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE) {
          char buf[OT_IP6_ADDRESS_STRING_SIZE];
          otIp6AddressToString(&address, buf, sizeof(buf));
    
          LOG_INF("Synthesized IPv6: %s (TTL: %" PRIu32 "). (line %i)", buf, ttl, __LINE__);
          // ...
          index++;
        }
      } else {
        LOG_ERR("DNS resolution failed: %d. (line %i)", aResult, __LINE__);
      }
    }
    
    void query_dns_address(otInstance* ot) {
      otError error;
      otDnsQueryConfig queryConfig;
      otDnsQueryConfig *config = &queryConfig;
    
      // Zero out the configuration structure
      memset(config, 0, sizeof(*config));
    
      error = otDnsClientResolveIp4Address(ot, "time.google.com", handle_dns_response, NULL, config);
    
      switch (error) {
        case OT_ERROR_NONE:
          LOG_INF("Query sent successfully. aCallback will be invoked to report the status. (line %i)", __LINE__);
          break;
        case OT_ERROR_NO_BUFS:
          LOG_INF("Insufficient buffer to prepare and send query. (line %i)", __LINE__);
          break;
        case OT_ERROR_INVALID_ARGS:
          LOG_INF("The host name is not valid format or NAT64 is not enabled in config. (line %i)", __LINE__);
          break;
        case OT_ERROR_INVALID_STATE:
          LOG_INF("Cannot send query since Thread interface is not up. (line %i)", __LINE__);
          break;
      }
    }
    
    int main(void) {
      otInstance *instance;
      otDeviceRole role;
    
      while (1) {
        for (int i = 0; i < 5000; i++) {
          LOG_INF("%i", i);
    
          instance = openthread_get_default_instance();
          if (instance == NULL) {
            LOG_ERR("Failed to get default OpenThread instance. (Line %i)", __LINE__);
            return -1;
          }
    
          /* Check OT Role */
          role = otThreadGetDeviceRole(instance);
    
          switch (role) {
            case OT_DEVICE_ROLE_DISABLED:
              LOG_INF("The Thread stack is disabled.");
              break;
            case OT_DEVICE_ROLE_DETACHED:
              LOG_INF("Not currently participating in a Thread network/partition.");
              break;
            case OT_DEVICE_ROLE_CHILD:
              LOG_INF("The Thread Child role.");
              break;
            case OT_DEVICE_ROLE_ROUTER:
              LOG_INF("The Thread Router role.");
              break;
            case OT_DEVICE_ROLE_LEADER:
              LOG_INF("The Thread Leader role.");
              break;
          }
    
          if (role > OT_DEVICE_ROLE_DETACHED) {
            query_dns_address(instance);
          }
          k_sleep(K_SECONDS(20));
        }
      }
    
      return 0;
    }

    prj.conf

    # STACKS
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    CONFIG_MAIN_STACK_SIZE=4096
    
    CONFIG_CBPRINTF_FP_SUPPORT=y
    CONFIG_PM_DEVICE=y
    CONFIG_PM_DEVICE_RUNTIME=y
    
    # RTT
    CONFIG_USE_SEGGER_RTT=y
    
    # LOGS
    CONFIG_LOG=y
    CONFIG_LOG_BACKEND_RTT=y
    CONFIG_LOG_BACKEND_RTT_BUFFER=0
    CONFIG_SENSOR_LOG_LEVEL_DBG=y
    
    # OpenThread (Generic)
    CONFIG_NETWORKING=y
    CONFIG_OPENTHREAD=y
    CONFIG_NET_IPV6=y
    CONFIG_NET_L2_OPENTHREAD=y
    CONFIG_OPENTHREAD_THREAD_VERSION_1_3=y
    CONFIG_OPENTHREAD_COAP=y
    CONFIG_OPENTHREAD_MANUAL_START=n
    CONFIG_OPENTHREAD_DNS_CLIENT=y
    CONFIG_DNS_RESOLVER=y
    CONFIG_OPENTHREAD_SRP_CLIENT=y
    
    # OpenThread Sleep-End-Device
    CONFIG_OPENTHREAD_MTD=y
    #CONFIG_OPENTHREAD_MTD_SED=y 
    #CONFIG_OPENTHREAD_POLL_PERIOD=30000
    
    # OpenThread Network identity
    CONFIG_OPENTHREAD_NETWORK_NAME="my-ot"
    CONFIG_OPENTHREAD_CHANNEL=12
    CONFIG_OPENTHREAD_PANID=43537
    CONFIG_OPENTHREAD_XPANID="de:e2:27:71:01:3c:35:26"
    CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff"
    
    # Enable IPv6 (Thread uses IPv6)
    CONFIG_NET_IPV6=y
    CONFIG_NET_IPV6_NBR_CACHE=y
    CONFIG_NET_IPV6_MLD=y
    
    # Enable sockets if you plan to use BSD socket APIs
    CONFIG_NET_SOCKETS=y
    
    # Increase networking stack size (important for OpenThread apps)
    CONFIG_NET_TX_STACK_SIZE=2048
    CONFIG_NET_RX_STACK_SIZE=2048
    
    

    logs:

    [00:00:00.178,175] <inf> main_app: 0
    [00:00:00.178,186] <inf> main_app: Not currently participating in a Thread network/partition.
    [00:00:20.178,242] <inf> main_app: 1
    [00:00:20.178,252] <inf> main_app: The Thread Child role.
    [00:00:20.178,823] <inf> main_app: Query sent successfully. aCallback will be invoked to report the status. (line 59)
    [00:00:26.220,098] <inf> main_app: Synthesized IPv6: fd84:eb3c:3ceb:2:0:0:d8ef:2308 (TTL: 4102). (line 35)
    [00:00:26.220,402] <inf> main_app: Synthesized IPv6: fd84:eb3c:3ceb:2:0:0:d8ef:2300 (TTL: 4102). (line 35)
    [00:00:26.220,727] <inf> main_app: Synthesized IPv6: fd84:eb3c:3ceb:2:0:0:d8ef:230c (TTL: 4102). (line 35)
    [00:00:26.221,082] <inf> main_app: Synthesized IPv6: fd84:eb3c:3ceb:2:0:0:d8ef:2304 (TTL: 4102). (line 35)

    2) However, the issue now is with the SNTP query. Using the OT-CLI sample, I can run the following commands to retrieve the timestamp:

    $ ot dns resolve4 time.google.com
    DNS response for time.google.com. - fd84:eb3c:3ceb:2:0:0:d8ef:2308 TTL:2700 fd84:eb3c:3ceb:2:0:0:d8ef:2300 TTL:2700 fd84:eb3c:3ceb:2:0:0:d8ef:230c TTL:2700 fd84:eb3c:3ceb:2:0:0:d8ef:2304 TTL:2700 
    Done

    $ ot sntp query fd84:eb3c:3ceb:2:0:0:d8ef:2308 
    SNTP response - Unix time: 1762784807 (era: 0)
    Done

    However, in my code I’m not able to get an SNTP query response. I’ve added the following Kconfig option: CONFIG_OPENTHREAD_SNTP_CLIENT=y, but it seems that nRF Connect for VS Code doesn’t even detect the #include <openthread/sntp.h> header.

    Below is my SNTP code:

    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include <zephyr/device.h>
    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    
    #include <openthread/thread.h>
    #include <openthread/link.h>
    #include <openthread/instance.h>
    #include <openthread/ip6.h>
    #include <openthread/dns.h>
    #include <openThread/sntp.h>
    #include <openthread/dns_client.h>
    #include <zephyr/net/openthread.h>
    
    #define OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_NAT64_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE 
    #define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE 
    #define OPENTHREAD_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE 
    #define OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE 1
    #define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT 10000
    
    LOG_MODULE_REGISTER(main_app, LOG_LEVEL_INF);
    
    static const char *ntpServer = "fd84:eb3c:3ceb:2:0:0:d8ef:2308";
    
    static void sntp_response_handler(void *aContext, uint64_t aTime, otError aResult)
    {
        if (aResult == OT_ERROR_NONE) {
            LOG_INF("SNTP response - Unix time: %" PRIu64, aTime);
        } else {
            LOG_ERR("SNTP query failed with error: %d", aResult);
        }
    }
    
    void send_sntp_query(otInstance *ot)
    {
        otError error;
        otSntpQuery query = {0};
        otMessageInfo messageInfo = {0};
        otIp6Address ip6 = {0};
    
        if (otIp6AddressFromString(ntpServer, &ip6) != OT_ERROR_NONE) {
            LOG_ERR("Invalid IPv6 address: %s", ntpServer);
            return;
        }
    
        messageInfo.mPeerAddr = ip6;
        messageInfo.mPeerPort = OT_SNTP_DEFAULT_SERVER_PORT;
        messageInfo.mSockPort = 0;
    
        query.mMessageInfo = &messageInfo;
    
        LOG_INF("Sending SNTP query to %s", ntpServer);
    
        error = otSntpClientQuery(ot, &query, sntp_response_handler, NULL);
    
        if (error == OT_ERROR_NONE) {
            LOG_INF("SNTP query sent");
        } else {
            LOG_ERR("Failed to send SNTP query: %d", error);
        }
    }
    
    int main(void) {
      otInstance* instance;
      otDeviceRole role;
    
      while (1) {
        for (int i = 0; i < 5000; i++) {
          LOG_INF("%i", i);
    
          instance = openthread_get_default_instance();
          if (instance == NULL) {
            LOG_ERR("Failed to get default OpenThread instance. (Line %i)", __LINE__);
            return -1;
          }
    
          /* Check OT Role */
          role = otThreadGetDeviceRole(instance);
    
          switch (role) {
            case OT_DEVICE_ROLE_DISABLED:
              LOG_INF("The Thread stack is disabled.");
              break;
            case OT_DEVICE_ROLE_DETACHED:
              LOG_INF("Not currently participating in a Thread network/partition.");
              break;
            case OT_DEVICE_ROLE_CHILD:
              LOG_INF("The Thread Child role.");
              break;
            case OT_DEVICE_ROLE_ROUTER:
              LOG_INF("The Thread Router role.");
              break;
            case OT_DEVICE_ROLE_LEADER:
              LOG_INF("The Thread Leader role.");
              break;
          }
    
          if (role > OT_DEVICE_ROLE_DETACHED) {
            send_sntp_query(instance);
          }
    
          k_sleep(K_SECONDS(20));
        }
      }
    
      return 0;
    }

    And logs:

    [00:30:00.087,175] <inf> main_app: The Thread Child role.
    [00:30:00.087,208] <inf> main_app: Sending SNTP query to fd84:eb3c:3ceb:2:0:0:d8ef:2308
    [00:30:00.087,402] <inf> main_app: SNTP query sent
    [00:30:09.087,587] <err> main_app: SNTP query failed with error: 28

      This is becoming quite urgent to resolve.

     

Related