| 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 | } |