| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "util/log.h" |
| #include "util/tracer_id.h" |
| |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <ifaddrs.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| /** |
| * @file tracer_id.c |
| * |
| * Implements process IDs for the HTrace C client. |
| */ |
| |
| /** |
| * The maximum number of bytes that can be in a process ID substitution |
| * variable. |
| */ |
| #define MAX_VAR_NAME 32 |
| |
| enum ip_addr_type { |
| ADDR_TYPE_IPV6_LOOPBACK = 0, |
| ADDR_TYPE_IPV4_LOOPBACK, |
| ADDR_TYPE_IPV6_OTHER, |
| ADDR_TYPE_IPV4_OTHER, |
| ADDR_TYPE_IPV6_SITE_LOCAL, |
| ADDR_TYPE_IPV4_SITE_LOCAL |
| }; |
| |
| static int handle_process_subst_var(struct htrace_log *lg, char **out, |
| const char *var, const char *tname); |
| |
| enum ip_addr_type get_ipv4_addr_type(const struct sockaddr_in *ip); |
| |
| enum ip_addr_type get_ipv6_addr_type(const struct sockaddr_in6 *ip); |
| |
| static int append_char(char **out, int *j, char c) |
| { |
| char *nout = realloc(*out, *j + 2); // leave space for NULL |
| if (!nout) { |
| return 0; |
| } |
| *out = nout; |
| (*out)[*j] = c; |
| *j = *j + 1; |
| (*out)[*j] = '\0'; |
| return 1; |
| } |
| |
| char *calculate_tracer_id(struct htrace_log *lg, const char *fmt, |
| const char *tname) |
| { |
| int i = 0, j = 0, escaping = 0, v = 0; |
| char *out = NULL, *var = NULL; |
| |
| out = strdup(""); |
| if (!out) { |
| goto oom; |
| } |
| while (1) { |
| char c = fmt[i++]; |
| if (c == '\0') { |
| break; |
| } else if (c == '\\') { |
| if (!escaping) { |
| escaping = 1; |
| continue; |
| } |
| } |
| switch (v) { |
| case 0: |
| if (c == '%') { |
| if (!escaping) { |
| if (!append_char(&var, &v, '%')) { |
| goto oom; |
| } |
| continue; |
| } |
| } |
| break; |
| case 1: |
| if (c == '{') { |
| if (!escaping) { |
| if (!append_char(&var, &v, '{')) { |
| goto oom; |
| } |
| continue; |
| } |
| } |
| if (!append_char(&out, &j, '%')) { |
| goto oom; |
| } |
| break; |
| default: |
| if (c == '}') { |
| if (!escaping) { |
| if (!append_char(&var, &v, '}')) { |
| goto oom; |
| } |
| var[v++] = '\0'; |
| if (!handle_process_subst_var(lg, &out, var, tname)) { |
| goto oom; |
| } |
| free(var); |
| var = NULL; |
| j = strlen(out); |
| v = 0; |
| continue; |
| } |
| } |
| escaping = 0; |
| if (!append_char(&var, &v, c)) { |
| goto oom; |
| } |
| continue; |
| } |
| escaping = 0; |
| v = 0; |
| if (!append_char(&out, &j, c)) { |
| goto oom; |
| } |
| } |
| out[j] = '\0'; |
| if (v > 0) { |
| htrace_log(lg, "calculate_tracer_id(%s): unterminated process ID " |
| "substitution variable at the end of the format string.", |
| fmt); |
| } |
| free(var); |
| return out; |
| |
| oom: |
| htrace_log(lg, "calculate_tracer_id(tname=%s): OOM\n", tname); |
| free(out); |
| free(var); |
| return NULL; |
| } |
| |
| static int handle_process_subst_var(struct htrace_log *lg, char **out, |
| const char *var, const char *tname) |
| { |
| char *nout = NULL; |
| |
| if (strcmp(var, "%{tname}") == 0) { |
| if (asprintf(&nout, "%s%s", *out, tname) < 0) { |
| htrace_log(lg, "handle_process_subst_var(var=%s): OOM", var); |
| return 0; |
| } |
| free(*out); |
| *out = nout; |
| } else if (strcmp(var, "%{ip}") == 0) { |
| char ip_str[256]; |
| get_best_ip(lg, ip_str, sizeof(ip_str)); |
| if (asprintf(&nout, "%s%s", *out, ip_str) < 0) { |
| htrace_log(lg, "handle_process_subst_var(var=%s): OOM", var); |
| return 0; |
| } |
| free(*out); |
| *out = nout; |
| } else if (strcmp(var, "%{pid}") == 0) { |
| char pid_str[64]; |
| pid_t pid = getpid(); |
| |
| snprintf(pid_str, sizeof(pid_str), "%lld", (long long)pid); |
| if (asprintf(&nout, "%s%s", *out, pid_str) < 0) { |
| htrace_log(lg, "handle_process_subst_var(var=%s): OOM", var); |
| return 0; |
| } |
| free(*out); |
| *out = nout; |
| } else { |
| htrace_log(lg, "handle_process_subst_var(var=%s): unknown process " |
| "ID substitution variable.\n", var); |
| } |
| return 1; |
| } |
| |
| /** |
| * Get the "best" IP address for this node. |
| * |
| * This is complicated since nodes can have multiple network interfaces, |
| * and each network interface can have multiple IP addresses. What we're |
| * looking for here is an IP address that will serve to identify this node |
| * to HTrace. So we prefer site-local addresess (i.e. private ones on the |
| * LAN) to publicly routable interfaces. If there are multiple addresses |
| * to choose from, we select the one which comes first in textual sort |
| * order. This should ensure that we at least consistently call each node |
| * by a single name. |
| */ |
| void get_best_ip(struct htrace_log *lg, char *ip_str, size_t ip_str_len) |
| { |
| struct ifaddrs *head, *ifa; |
| enum ip_addr_type ty = ADDR_TYPE_IPV4_LOOPBACK, nty; |
| char temp_ip_str[128]; |
| |
| snprintf(ip_str, ip_str_len, "%s", "127.0.0.1"); |
| if (getifaddrs(&head) < 0) { |
| int res = errno; |
| htrace_log(lg, "get_best_ip: getifaddrs failed: %s\n", terror(res)); |
| return; |
| } |
| for (ifa = head; ifa; ifa = ifa->ifa_next){ |
| if (!ifa->ifa_addr) { |
| continue; |
| } |
| if (ifa->ifa_addr->sa_family == AF_INET) { |
| struct sockaddr_in *addr = |
| (struct sockaddr_in *)ifa->ifa_addr; |
| nty = get_ipv4_addr_type(addr); |
| if (nty < ty) { |
| continue; |
| } |
| if (!inet_ntop(AF_INET, &addr->sin_addr, temp_ip_str, |
| sizeof(temp_ip_str))) { |
| htrace_log(lg, "get_best_ip_impl: inet_ntop(%s, AF_INET) " |
| "failed\n", ifa->ifa_name); |
| continue; |
| } |
| if ((nty == ty) && (strcmp(temp_ip_str, ip_str) > 0)) { |
| continue; |
| } |
| snprintf(ip_str, ip_str_len, "%s", temp_ip_str); |
| ty = nty; |
| } else if (ifa->ifa_addr->sa_family == AF_INET6) { |
| struct sockaddr_in6 *addr = |
| (struct sockaddr_in6 *)ifa->ifa_addr; |
| nty = get_ipv6_addr_type(addr); |
| if (nty < ty) { |
| continue; |
| } |
| if (!inet_ntop(AF_INET6, &addr->sin6_addr, temp_ip_str, |
| sizeof(temp_ip_str))) { |
| htrace_log(lg, "get_best_ip_impl: inet_ntop(%s, AF_INET6) " |
| "failed\n", ifa->ifa_name); |
| continue; |
| } |
| if ((nty == ty) && (strcmp(temp_ip_str, ip_str) > 0)) { |
| continue; |
| } |
| snprintf(ip_str, ip_str_len, "%s", temp_ip_str); |
| ty = nty; |
| } |
| } |
| freeifaddrs(head); |
| } |
| |
| enum ip_addr_type get_ipv4_addr_type(const struct sockaddr_in *ip) |
| { |
| union { |
| uint8_t b[4]; |
| uint32_t addr; |
| } addr; |
| addr.addr = ip->sin_addr.s_addr; // always big-endian |
| if (addr.b[0] == 127) { // 127.0.0.0/24 |
| return ADDR_TYPE_IPV4_LOOPBACK; |
| } |
| if ((addr.b[0] == 10) && (addr.b[1] == 0) && (addr.b[2] == 0)) { |
| return ADDR_TYPE_IPV4_SITE_LOCAL; // 10.0.0.0/8 |
| } |
| if ((addr.b[0] == 192) && (addr.b[1] == 168)) { |
| return ADDR_TYPE_IPV4_SITE_LOCAL; // 192.168.0.0/16 |
| } |
| if ((addr.b[0] == 172) && (addr.b[1] == 16) && ((addr.b[2] & 0xf0) == 0)) { |
| return ADDR_TYPE_IPV4_SITE_LOCAL; // 172.16.0.0/12 |
| } |
| return ADDR_TYPE_IPV4_OTHER; |
| } |
| |
| enum ip_addr_type get_ipv6_addr_type(const struct sockaddr_in6 *ip) |
| { |
| if (IN6_IS_ADDR_LOOPBACK(ip)) { |
| return ADDR_TYPE_IPV6_LOOPBACK; |
| } else if (IN6_IS_ADDR_SITELOCAL(ip)) { |
| return ADDR_TYPE_IPV6_SITE_LOCAL; |
| } else { |
| return ADDR_TYPE_IPV6_OTHER; |
| } |
| } |
| |
| // vim:ts=4:sw=4:et |