DNSServiceDiscoveryUnicast/main.c (181 lines of code) (raw):
/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
// This sample C application shows how to perform a DNS service discovery. It makes queries using
// multicast to local network and processes responses from the available DNS responders.
//
// It uses the API for the following Azure Sphere application libraries:
// - log (displays messages in the Device Output window during debugging)
// - networking (get network interface connection status)
// - eventloop (system invokes handlers for timer events)
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <arpa/inet.h>
#include <applibs/log.h>
#include <applibs/networking.h>
#include <curl/curl.h>
#include "dns-sd.h"
#include "eventloop_timer_utilities.h"
/// <summary>
/// Exit codes for this application. These are used for the
/// application exit code. They must all be between zero and 255,
/// where zero is reserved for successful termination.
/// </summary>
typedef enum {
ExitCode_Success = 0,
ExitCode_TermHandler_SigTerm = 1,
ExitCode_ConnectionTimer_Consume = 2,
ExitCode_ConnectionTimer_ConnectionReady = 3,
ExitCode_ConnectionTimer_Disarm = 4,
ExitCode_Init_EventLoop = 5,
ExitCode_Init_ConnectionTimer = 7,
ExitCode_Init_ConfigureDnsServers = 8,
ExitCode_Main_EventLoopFail = 9
} ExitCode;
// File descriptors - initialized to invalid value
static bool isNetworkStackReady = false;
static EventLoop *eventLoop = NULL;
static EventLoopTimer *connectionTimer = NULL;
static EventRegistration *dnsEventReg = NULL;
static const Networking_InterfaceConnectionStatus RequiredNetworkStatus =
Networking_InterfaceConnectionStatus_IpAvailable;
static const char NetworkInterface[] = "eth0";
static const char DnsSDServiceType[] = "_http._tcp.home";
static const char DnsSDServerIp[] = "w.x.y.z"; // Replace this with your DNS server for service discovery
static const char OtherDnsServerIp[] = "w.x.y.z"; // Replace this with a second DNS server for normal resolution
// Termination state
static volatile sig_atomic_t exitCode = ExitCode_Success;
static void DoFetch(const char* url);
static void DoQuery(void);
static void TerminationHandler(int signalNumber);
static void ConnectionTimerEventHandler(EventLoopTimer *timer);
static ExitCode InitializeAndStartDnsServiceDiscovery(void);
static void Cleanup(void);
/// <summary>
/// Signal handler for termination requests. This handler must be async-signal-safe.
/// </summary>
static void TerminationHandler(int signalNumber)
{
// Don't use Log_Debug here, as it is not guaranteed to be async-signal-safe.
exitCode = ExitCode_TermHandler_SigTerm;
}
static void DoFetch(const char* url)
{
CURL* curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
Log_Debug("curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
#define ANSWER_BUF_SIZE 2048u
static void DoQuery(void)
{
static char answerBuf[ANSWER_BUF_SIZE];
ServiceInstanceDetails *details = NULL;
int answerSize = SendServiceDiscoveryQuery(DnsSDServiceType, answerBuf, ANSWER_BUF_SIZE);
if (answerSize <= 0) {
goto fail;
}
int result = ProcessDnsResponse(&details, answerBuf, answerSize);
if (result != 0) {
goto fail;
}
if (details && details->name) {
Log_Debug("INFO: DNS Service Discovery has found an instance: %s.\n", details->name);
if (!details->host) {
Log_Debug("INFO: Requesting SRV and TXT details for the instance.\n");
answerSize = SendServiceInstanceDetailsQuery(details->name, answerBuf, ANSWER_BUF_SIZE);
result = ProcessDnsResponse(&details, answerBuf, answerSize);
if (result != 0) {
goto fail;
}
// NOTE: The TXT data is simply treated as a string and isn't parsed here. You should
// replace this with your own production logic.
Log_Debug("\tName: %s\n\tHost: %s\n\tIPv4 Address: %s\n\tPort: %hd\n\tTXT Data: %.*s\n",
details->name, details->host, inet_ntoa(details->ipv4Address), details->port,
details->txtDataLength, details->txtData);
DoFetch(details->host);
}
}
fail:
FreeServiceInstanceDetails(details);
}
/// <summary>
/// Check whether the required network connection status has been met.
/// </summary>
/// <param name="interface">The network interface to perform the check on.</param>
/// <param name="ipAddressAvailable">The result of the connection status check.</param>
/// <returns>0 on success, or -1 on failure.</returns>
int IsConnectionReady(const char *interface, bool *ipAddressAvailable)
{
Networking_InterfaceConnectionStatus status;
if (Networking_GetInterfaceConnectionStatus(interface, &status) == 0) {
Log_Debug("INFO: Network interface %s status: 0x%02x\n", interface, status);
isNetworkStackReady = true;
} else {
if (errno == EAGAIN) {
Log_Debug("INFO: The networking stack isn't ready yet, will try again later.\n");
return 0;
} else {
Log_Debug("ERROR: Networking_GetInterfaceConnectionStatus: %d (%s)\n", errno,
strerror(errno));
return -1;
}
}
*ipAddressAvailable = (status & RequiredNetworkStatus) != 0;
return 0;
}
/// <summary>
/// The timer event handler to check whether network connection is ready and send DNS service
/// discovery queries.
/// </summary>
static void ConnectionTimerEventHandler(EventLoopTimer *timer)
{
if (ConsumeEventLoopTimerEvent(timer) != 0) {
exitCode = ExitCode_ConnectionTimer_Consume;
return;
}
// Check whether the network connection is ready.
bool isConnectionReady = false;
if (IsConnectionReady(NetworkInterface, &isConnectionReady) != 0) {
exitCode = ExitCode_ConnectionTimer_ConnectionReady;
} else if (isConnectionReady) {
// Connection is ready, send a DNS service discovery query.
DoQuery();
}
}
/// <summary>
/// Set up SIGTERM termination handler and event handlers.
/// </summary>
/// <returns>
/// ExitCode_Success if all resources were allocated successfully; otherwise another
/// ExitCode value which indicates the specific failure.
/// </returns>
static ExitCode InitializeAndStartDnsServiceDiscovery(void)
{
// Configure DNS servers - you should specify at least the service discovery server,
// and also a secondary if using the provided container (which does not permit recursive
// lookups)
static const size_t numOfDnsServerAddressSpecified = 2;
static const char *dnsServerIpAddress[] = { DnsSDServerIp, OtherDnsServerIp };
Networking_IpConfig ipConfig;
// Convert the addresses from the numbers-and-dots notation into integers.
struct in_addr dnsServers[numOfDnsServerAddressSpecified];
for (int i = 0; i < numOfDnsServerAddressSpecified; i++) {
if (inet_pton(AF_INET, dnsServerIpAddress[i], &dnsServers[i]) != 1) {
Log_Debug("ERROR: Invalid DNS server address or address family specified.\n");
return ExitCode_Init_ConfigureDnsServers;
}
}
Networking_IpConfig_Init(&ipConfig);
int result =
Networking_IpConfig_EnableCustomDns(&ipConfig, dnsServers, numOfDnsServerAddressSpecified);
if (result != 0) {
Log_Debug("ERROR: Networking_IpConfig_EnableCustomDns: %d (%s)\n", errno, strerror(errno));
Networking_IpConfig_Destroy(&ipConfig);
return ExitCode_Init_ConfigureDnsServers;
}
result = Networking_IpConfig_Apply(NetworkInterface, &ipConfig);
Networking_IpConfig_Destroy(&ipConfig);
if (result != 0) {
Log_Debug("ERROR: Networking_IpConfig_Apply: %d (%s)\n", errno, strerror(errno));
return ExitCode_Init_ConfigureDnsServers;
}
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = TerminationHandler;
sigaction(SIGTERM, &action, NULL);
eventLoop = EventLoop_Create();
if (eventLoop == NULL) {
Log_Debug("Could not create event loop.\n");
return ExitCode_Init_EventLoop;
}
// Check network interface status at the specified period until it is ready.
// This also defines the frequency at which the sample sends out DNS-SD queries.
static const struct timespec checkInterval = {.tv_sec = 10, .tv_nsec = 0};
connectionTimer =
CreateEventLoopPeriodicTimer(eventLoop, &ConnectionTimerEventHandler, &checkInterval);
if (connectionTimer == NULL) {
return ExitCode_Init_ConnectionTimer;
}
return ExitCode_Success;
}
/// <summary>
/// Clean up the resources previously allocated.
/// </summary>
static void Cleanup(void)
{
DisposeEventLoopTimer(connectionTimer);
EventLoop_UnregisterIo(eventLoop, dnsEventReg);
EventLoop_Close(eventLoop);
}
int main(void)
{
Log_Debug("INFO: DNS Service Discovery sample starting.\n");
exitCode = InitializeAndStartDnsServiceDiscovery();
// Use event loop to wait for events and trigger handlers, until an error or SIGTERM happens
while (exitCode == ExitCode_Success) {
EventLoop_Run_Result result = EventLoop_Run(eventLoop, -1, true);
// Continue if interrupted by signal, e.g. due to breakpoint being set.
if (result == EventLoop_Run_Failed && errno != EINTR) {
exitCode = ExitCode_Main_EventLoopFail;
}
}
Cleanup();
Log_Debug("INFO: Application exiting.\n");
return exitCode;
}