-
Notifications
You must be signed in to change notification settings - Fork 38
/
main.c
281 lines (232 loc) · 9.59 KB
/
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/* 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;
}