master
c 477 lines 14 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "libnetdata/libnetdata.h"
4
5 // --------------------------------------------------------------------------------------------------------------------
6 // connect to another host/port
7
8 // connect_to_this_unix()
9 // path the path of the unix socket
10 // timeout the timeout for establishing a connection
11
12 static inline int connect_to_unix(const char *path, struct timeval *timeout) {
13 int fd = socket(AF_UNIX, SOCK_STREAM | DEFAULT_SOCKET_FLAGS, 0);
14 if(fd == -1) {
15 nd_log(NDLS_DAEMON, NDLP_ERR,
16 "Failed to create UNIX socket() for '%s'",
17 path);
18
19 return -1;
20 }
21
22 if(timeout) {
23 if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) timeout, sizeof(struct timeval)) < 0)
24 nd_log(NDLS_DAEMON, NDLP_ERR,
25 "Failed to set timeout on UNIX socket '%s'",
26 path);
27 }
28
29 sock_setcloexec(fd, true);
30
31 struct sockaddr_un addr;
32 memset(&addr, 0, sizeof(addr));
33 addr.sun_family = AF_UNIX;
34 strncpyz(addr.sun_path, path, sizeof(addr.sun_path) - 1);
35
36 if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
37 nd_log(NDLS_DAEMON, NDLP_ERR,
38 "Cannot connect to UNIX socket on path '%s'.",
39 path);
40
41 close(fd);
42 return -1;
43 }
44
45 nd_log(NDLS_DAEMON, NDLP_DEBUG,
46 "Connected to UNIX socket on path '%s'.",
47 path);
48
49 return fd;
50 }
51
52 // connect_to_this_ip46()
53 // protocol IPPROTO_TCP, IPPROTO_UDP
54 // socktype SOCK_STREAM, SOCK_DGRAM
55 // host the destination hostname or IP address (IPv4 or IPv6) to connect to
56 // if it resolves to many IPs, all are tried (IPv4 and IPv6)
57 // scope_id the if_index id of the interface to use for connecting (0 = any)
58 // (used only under IPv6)
59 // service the service name or port to connect to
60 // timeout the timeout for establishing a connection
61
62 int connect_to_this_ip46(
63 int protocol,
64 int socktype,
65 const char *host,
66 uint32_t scope_id,
67 const char *service,
68 struct timeval *timeout,
69 bool *fallback_ipv4)
70 {
71 struct addrinfo hints;
72 struct addrinfo *ai_head = NULL, *ai = NULL;
73
74 memset(&hints, 0, sizeof(hints));
75 hints.ai_family = PF_UNSPEC; /* Allow IPv4 or IPv6 */
76 hints.ai_socktype = socktype;
77 hints.ai_protocol = protocol;
78
79 int ai_err = getaddrinfo(host, service, &hints, &ai_head);
80 if (ai_err != 0) {
81
82 nd_log(NDLS_DAEMON, NDLP_ERR,
83 "Cannot resolve host '%s', port '%s': %s",
84 host, service, gai_strerror(ai_err));
85
86 return -ND_SOCK_ERR_CANNOT_RESOLVE_HOSTNAME;
87 }
88
89 char hostBfr[NI_MAXHOST + 1];
90 char servBfr[NI_MAXSERV + 1];
91
92 ND_LOG_STACK lgs[] = {
93 ND_LOG_FIELD_TXT(NDF_DST_IP, hostBfr),
94 ND_LOG_FIELD_TXT(NDF_DST_PORT, servBfr),
95 ND_LOG_FIELD_END(),
96 };
97 ND_LOG_STACK_PUSH(lgs);
98
99 int fd = -1;
100 for (ai = ai_head; ai != NULL && fd == -1; ai = ai->ai_next) {
101 if(nd_thread_signaled_to_cancel()) break;
102
103 if (fallback_ipv4 && *fallback_ipv4 && ai->ai_family == PF_INET6)
104 continue;
105
106 if (ai->ai_family == PF_INET6) {
107 struct sockaddr_in6 *pSadrIn6 = (struct sockaddr_in6 *) ai->ai_addr;
108 if(pSadrIn6->sin6_scope_id == 0) {
109 pSadrIn6->sin6_scope_id = scope_id;
110 }
111 }
112
113 getnameinfo(ai->ai_addr,
114 ai->ai_addrlen,
115 hostBfr,
116 sizeof(hostBfr),
117 servBfr,
118 sizeof(servBfr),
119 NI_NUMERICHOST | NI_NUMERICSERV);
120
121 switch (ai->ai_addr->sa_family) {
122 case PF_INET: {
123 struct sockaddr_in *pSadrIn = (struct sockaddr_in *)ai->ai_addr;
124 (void)pSadrIn;
125 break;
126 }
127
128 case PF_INET6: {
129 struct sockaddr_in6 *pSadrIn6 = (struct sockaddr_in6 *) ai->ai_addr;
130 (void)pSadrIn6;
131 break;
132 }
133
134 default: {
135 // Unknown protocol family
136 continue;
137 }
138 }
139
140 fd = socket(ai->ai_family, ai->ai_socktype | DEFAULT_SOCKET_FLAGS, ai->ai_protocol);
141 if(fd != -1) {
142 if(timeout) {
143 if(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) timeout, sizeof(struct timeval)) < 0)
144 nd_log(NDLS_DAEMON, NDLP_ERR,
145 "Failed to set timeout on the socket to ip '%s' port '%s'",
146 hostBfr, servBfr);
147 }
148 sock_setcloexec(fd, true);
149
150 errno_clear();
151 if(connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) {
152 if(errno == EALREADY || errno == EINPROGRESS) {
153 nd_log(NDLS_DAEMON, NDLP_DEBUG,
154 "Waiting for connection to ip %s port %s to be established",
155 hostBfr, servBfr);
156
157 // Convert 'struct timeval' to milliseconds for poll():
158 int timeout_ms = timeout ? (timeout->tv_sec * 1000 + timeout->tv_usec / 1000) : 1000;
159
160 switch(wait_on_socket_or_cancel_with_timeout(NULL, fd, timeout_ms, POLLOUT, NULL)) {
161 case 0: // proceed
162 nd_log(NDLS_DAEMON, NDLP_DEBUG,
163 "connect() to ip %s port %s completed successfully",
164 hostBfr, servBfr);
165 break;
166
167 case -1: // thread cancelled
168 nd_log(NDLS_DAEMON, NDLP_ERR,
169 "Thread is cancelled while connecting to '%s', port '%s'.",
170 hostBfr, servBfr);
171
172 close(fd);
173 fd = -ND_SOCK_ERR_THREAD_CANCELLED;
174 break;
175
176 case 1: // timeout
177 nd_log(NDLS_DAEMON, NDLP_ERR,
178 "Timed out while connecting to '%s', port '%s'.",
179 hostBfr, servBfr);
180
181 close(fd);
182 fd = -ND_SOCK_ERR_TIMEOUT;
183
184 if (fallback_ipv4 && ai->ai_family == PF_INET6)
185 *fallback_ipv4 = true;
186 break;
187
188 default:
189 case 2: // poll error
190 nd_log(NDLS_DAEMON, NDLP_ERR,
191 "Failed to connect to '%s', port '%s'.",
192 hostBfr, servBfr);
193
194 close(fd);
195 fd = -ND_SOCK_ERR_POLL_ERROR;
196 break;
197 }
198 }
199 else {
200 nd_log(NDLS_DAEMON, NDLP_ERR,
201 "Failed to connect to '%s', port '%s'",
202 hostBfr, servBfr);
203
204 close(fd);
205 fd = -ND_SOCK_ERR_CONNECTION_REFUSED;
206 }
207 }
208 }
209 else {
210 nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to socket() to '%s', port '%s'", hostBfr, servBfr);
211 fd = -ND_SOCK_ERR_FAILED_TO_CREATE_SOCKET;
212 }
213 }
214
215 freeaddrinfo(ai_head);
216
217 return fd;
218 }
219
220 static int parse_connection_definition(
221 char *definition,
222 char **host,
223 char **service,
224 char **iface,
225 int *protocol,
226 int *socktype,
227 char **unix_path)
228 {
229 if(!definition || !*definition)
230 return -1;
231
232 *host = definition;
233 *service = NULL;
234 *iface = "";
235 *protocol = IPPROTO_TCP;
236 *socktype = SOCK_STREAM;
237 *unix_path = NULL;
238
239 if(strncmp(*host, "tcp:", 4) == 0) {
240 *host += 4;
241 *protocol = IPPROTO_TCP;
242 *socktype = SOCK_STREAM;
243 }
244 else if(strncmp(*host, "udp:", 4) == 0) {
245 *host += 4;
246 *protocol = IPPROTO_UDP;
247 *socktype = SOCK_DGRAM;
248 }
249 else if(strncmp(*host, "unix:", 5) == 0) {
250 *unix_path = *host + 5;
251 return 1;
252 }
253 else if(**host == '/') {
254 *unix_path = *host;
255 return 1;
256 }
257
258 char *e = *host;
259 if(*e == '[') {
260 e = ++(*host);
261 while(*e && *e != ']') e++;
262 if(*e == ']') {
263 *e = '\0';
264 e++;
265 }
266 }
267 else {
268 while(*e && *e != ':' && *e != '%') e++;
269 }
270
271 if(*e == '%') {
272 *e = '\0';
273 e++;
274 *iface = e;
275 while(*e && *e != ':') e++;
276 }
277
278 if(*e == ':') {
279 *e = '\0';
280 e++;
281 *service = e;
282 }
283
284 if(!**host)
285 return -1;
286
287 return 0;
288 }
289
290 bool connect_to_definition_get_service(const char *definition, int default_port, char *service, size_t service_size) {
291 if(!service || !service_size)
292 return false;
293
294 snprintfz(service, service_size, "%d", default_port);
295
296 if(!definition || !*definition)
297 return true;
298
299 const char *s = definition;
300 if(strncmp(s, "tcp:", 4) == 0 || strncmp(s, "udp:", 4) == 0)
301 s += 4;
302 else if(strncmp(s, "unix:", 5) == 0 || *s == '/')
303 return true;
304
305 const char *e = s;
306 size_t host_len = 0;
307 if(*e == '[') {
308 const char *host_start = ++e;
309 while(*e && *e != ']') e++;
310 host_len = (size_t)(e - host_start);
311 if(*e == ']')
312 e++;
313 }
314 else {
315 const char *host_start = e;
316 while(*e && *e != ':' && *e != '%') e++;
317 host_len = (size_t)(e - host_start);
318 }
319
320 if(!host_len)
321 return false;
322
323 if(*e == '%') {
324 e++;
325 while(*e && *e != ':') e++;
326 }
327
328 if(*e == ':' && *(e + 1))
329 snprintfz(service, service_size, "%s", e + 1);
330
331 return true;
332 }
333
334 // connect_to_this()
335 //
336 // definition format:
337 //
338 // [PROTOCOL:]IP[%INTERFACE][:PORT]
339 //
340 // PROTOCOL = tcp or udp
341 // IP = IPv4 or IPv6 IP or hostname, optionally enclosed in [] (required for IPv6)
342 // INTERFACE = for IPv6 only, the network interface to use
343 // PORT = port number or service name
344
345 int connect_to_this(const char *definition, int default_port, struct timeval *timeout) {
346 if(!definition || !*definition) {
347 nd_log(NDLS_DAEMON, NDLP_ERR,
348 "Definition '%s' does not specify a host.",
349 definition ? definition : "(null)");
350
351 return -ND_SOCK_ERR_NO_HOST_IN_DEFINITION;
352 }
353
354 CLEAN_CHAR_P *buffer = strdupz(definition);
355
356 char default_service[10 + 1];
357 snprintfz(default_service, 10, "%d", default_port);
358
359 char *host = NULL, *service = NULL, *iface = NULL, *unix_path = NULL;
360 int protocol = 0, socktype = 0;
361 uint32_t scope_id = 0;
362
363 int rc = parse_connection_definition(
364 buffer, &host, &service, &iface, &protocol, &socktype, &unix_path);
365 if(rc == 1)
366 return connect_to_unix(unix_path, timeout);
367
368 if(rc == -1) {
369 nd_log(NDLS_DAEMON, NDLP_ERR,
370 "Definition '%s' does not specify a host.",
371 definition);
372
373 return -ND_SOCK_ERR_NO_HOST_IN_DEFINITION;
374 }
375
376 if(iface && *iface) {
377 scope_id = if_nametoindex(iface);
378 if(!scope_id)
379 nd_log(NDLS_DAEMON, NDLP_ERR,
380 "Cannot find a network interface named '%s'. Continuing without limiting the network interface",
381 iface);
382 }
383
384 if(!service || !*service)
385 service = default_service;
386
387
388 return connect_to_this_ip46(protocol, socktype, host, scope_id, service, timeout, NULL);
389 }
390
391 void foreach_entry_in_connection_string(const char *destination, bool (*callback)(char *entry, void *data), void *data) {
392 const char *s = destination;
393 while(*s) {
394 const char *e = s;
395
396 // skip separators, moving both s(tart) and e(nd)
397 while(isspace((uint8_t)*e) || *e == ',') s = ++e;
398
399 // move e(nd) to the first separator
400 while(*e && !isspace((uint8_t)*e) && *e != ',') e++;
401
402 // is there anything?
403 if(!*s || s == e) break;
404
405 CLEAN_CHAR_P *buf = mallocz((size_t)(e - s) + 1);
406 strncpyz(buf, s, (size_t)(e - s));
407
408 if(callback(buf, data)) break;
409
410 s = e;
411 }
412 }
413
414 struct connect_to_one_of_data {
415 int default_port;
416 struct timeval *timeout;
417 size_t *reconnects_counter;
418 char *connected_to;
419 size_t connected_to_size;
420 int sock;
421 };
422
423 static bool connect_to_one_of_callback(char *entry, void *data) {
424 struct connect_to_one_of_data *t = data;
425
426 if(t->reconnects_counter)
427 t->reconnects_counter++;
428
429 t->sock = connect_to_this(entry, t->default_port, t->timeout);
430 if(t->sock != -1) {
431 if(t->connected_to && t->connected_to_size) {
432 strncpyz(t->connected_to, entry, t->connected_to_size);
433 t->connected_to[t->connected_to_size - 1] = '\0';
434 }
435
436 return true;
437 }
438
439 return false;
440 }
441
442 int connect_to_one_of(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size) {
443 struct connect_to_one_of_data t = {
444 .default_port = default_port,
445 .timeout = timeout,
446 .reconnects_counter = reconnects_counter,
447 .connected_to = connected_to,
448 .connected_to_size = connected_to_size,
449 .sock = -1,
450 };
451
452 foreach_entry_in_connection_string(destination, connect_to_one_of_callback, &t);
453
454 return t.sock;
455 }
456
457 static bool connect_to_one_of_urls_callback(char *entry, void *data) {
458 char *s = strchr(entry, '/');
459 if(s) *s = '\0';
460
461 return connect_to_one_of_callback(entry, data);
462 }
463
464 int connect_to_one_of_urls(const char *destination, int default_port, struct timeval *timeout, size_t *reconnects_counter, char *connected_to, size_t connected_to_size) {
465 struct connect_to_one_of_data t = {
466 .default_port = default_port,
467 .timeout = timeout,
468 .reconnects_counter = reconnects_counter,
469 .connected_to = connected_to,
470 .connected_to_size = connected_to_size,
471 .sock = -1,
472 };
473
474 foreach_entry_in_connection_string(destination, connect_to_one_of_urls_callback, &t);
475
476 return t.sock;
477 }