client.c (5779B)
1 #define _GNU_SOURCE 2 #include <errno.h> 3 #include <signal.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <sys/types.h> 7 #include <sys/wait.h> 8 #include <unistd.h> 9 10 #include "rxi/log.h" 11 12 #include "config/daemon.h" 13 #include "dhcp.h" 14 15 #define TOKEN "{{interface}}" 16 #define TOKEN_LEN (sizeof(TOKEN) - 1) 17 18 struct dhcp_proc { 19 char *ifname; 20 pid_t pid; 21 struct dhcp_proc *next; 22 }; 23 24 static struct dhcp_proc *procs; 25 26 // --------------------------------------------------------------------------- 27 28 static char *render(const char *src, const char *ifname) { 29 size_t ilen = strlen(ifname); 30 size_t n = 0; 31 for (const char *p = src; (p = strstr(p, TOKEN)) != NULL; p += TOKEN_LEN) n++; 32 33 // Subtract before adding: ifname may be shorter than the token. 34 size_t len = strlen(src) - (n * TOKEN_LEN) + (n * ilen); 35 char *out = malloc(len + 1); 36 if (!out) { 37 log_error("dhcp: out of memory rendering `%s'", src); 38 return NULL; 39 } 40 41 char *w = out; 42 const char *r = src; 43 for (const char *p; (p = strstr(r, TOKEN)) != NULL; r = p + TOKEN_LEN) { 44 memcpy(w, r, (size_t)(p - r)); 45 w += p - r; 46 memcpy(w, ifname, ilen); 47 w += ilen; 48 } 49 strcpy(w, r); 50 return out; 51 } 52 53 // --------------------------------------------------------------------------- 54 // PATH resolution 55 56 static char *find_in_path(const char *bin) { 57 if (strchr(bin, '/')) return access(bin, X_OK) == 0 ? strdup(bin) : NULL; 58 59 const char *path = getenv("PATH"); 60 if (!path || !*path) path = "/sbin:/usr/sbin:/bin:/usr/bin"; 61 62 char *dup = strdup(path); 63 if (!dup) return NULL; 64 65 char *found = NULL; 66 for (char *dir = strtok(dup, ":"); dir; dir = strtok(NULL, ":")) { 67 char cand[512]; 68 if ((size_t)snprintf(cand, sizeof(cand), "%s/%s", dir, bin) >= sizeof(cand)) continue; 69 if (access(cand, X_OK) == 0) { found = strdup(cand); break; } 70 } 71 free(dup); 72 return found; 73 } 74 75 // --------------------------------------------------------------------------- 76 77 // poll(2) is never restarted, so this drops nl_run out of its wait and the 78 // reap at the top of that loop runs immediately. SA_RESTART keeps the 79 // blocking waitpid in plugin.c from being interrupted. 80 static void sigchld_noop(int sig) { (void)sig; } 81 82 static void arm_sigchld(void) { 83 static int armed = 0; 84 if (armed) return; 85 armed = 1; 86 87 struct sigaction sa; 88 memset(&sa, 0, sizeof(sa)); 89 sa.sa_handler = sigchld_noop; 90 sa.sa_flags = SA_RESTART; 91 sigemptyset(&sa.sa_mask); 92 if (sigaction(SIGCHLD, &sa, NULL) != 0) { 93 log_warn("dhcp: sigaction SIGCHLD: %s", strerror(errno)); 94 } 95 } 96 97 // --------------------------------------------------------------------------- 98 // process table 99 100 static struct dhcp_proc *proc_find(const char *ifname) { 101 for (struct dhcp_proc *p = procs; p; p = p->next) { 102 if (!strcmp(p->ifname, ifname)) return p; 103 } 104 return NULL; 105 } 106 107 static void proc_drop(struct dhcp_proc *victim) { 108 struct dhcp_proc **pp = &procs; 109 while (*pp) { 110 if (*pp == victim) { 111 *pp = victim->next; 112 free(victim->ifname); 113 free(victim); 114 return; 115 } 116 pp = &(*pp)->next; 117 } 118 } 119 120 // --------------------------------------------------------------------------- 121 122 int dhcp_start(const char *ifname) { 123 if (proc_find(ifname)) return 0; 124 125 const struct linkd_daemon_cfg *cfg = daemon_cfg(); 126 size_t argc = cfg->dhcp_client_n; 127 if (!argc) { 128 log_error("dhcp: %s: no dhcp_client configured", ifname); 129 return -1; 130 } 131 const char *const *tpl = (const char *const *)cfg->dhcp_client; 132 arm_sigchld(); 133 134 char **argv = calloc(argc + 1, sizeof(char *)); 135 if (!argv) { 136 log_error("dhcp: %s: out of memory", ifname); 137 return -1; 138 } 139 for (size_t i = 0; i < argc; i++) { 140 argv[i] = render(tpl[i], ifname); 141 if (!argv[i]) { 142 for (size_t j = 0; j < i; j++) free(argv[j]); 143 free(argv); 144 return -1; 145 } 146 } 147 148 char *exe = find_in_path(argv[0]); 149 if (!exe) { 150 log_error("dhcp: %s: `%s' not found or not executable", ifname, argv[0]); 151 for (size_t i = 0; i < argc; i++) free(argv[i]); 152 free(argv); 153 return -1; 154 } 155 156 pid_t pid = fork(); 157 if (pid < 0) { 158 log_error("dhcp: %s: fork: %s", ifname, strerror(errno)); 159 free(exe); 160 for (size_t i = 0; i < argc; i++) free(argv[i]); 161 free(argv); 162 return -1; 163 } 164 165 if (pid == 0) { 166 signal(SIGPIPE, SIG_DFL); 167 execv(exe, argv); 168 _exit(127); 169 } 170 171 struct dhcp_proc *p = calloc(1, sizeof(*p)); 172 if (!p) { 173 kill(pid, SIGTERM); 174 waitpid(pid, NULL, 0); 175 log_error("dhcp: %s: out of memory", ifname); 176 free(exe); 177 for (size_t i = 0; i < argc; i++) free(argv[i]); 178 free(argv); 179 return -1; 180 } 181 p->ifname = strdup(ifname); 182 p->pid = pid; 183 p->next = procs; 184 procs = p; 185 186 log_info("dhcp: %s: started %s (pid %d)", ifname, exe, (int)pid); 187 188 free(exe); 189 for (size_t i = 0; i < argc; i++) free(argv[i]); 190 free(argv); 191 return 0; 192 } 193 194 void dhcp_stop(const char *ifname) { 195 struct dhcp_proc *p = proc_find(ifname); 196 if (!p) return; 197 log_info("dhcp: %s: stopping pid %d", ifname, (int)p->pid); 198 kill(p->pid, SIGTERM); 199 waitpid(p->pid, NULL, 0); 200 proc_drop(p); 201 } 202 203 void dhcp_stop_all(void) { 204 while (procs) { 205 struct dhcp_proc *p = procs; 206 kill(p->pid, SIGTERM); 207 waitpid(p->pid, NULL, 0); 208 proc_drop(p); 209 } 210 } 211 212 // Loud: the address survives until the lease expires, so the breakage is 213 // otherwise invisible for hours. 214 void dhcp_reap(void) { 215 struct dhcp_proc *p = procs; 216 while (p) { 217 struct dhcp_proc *next = p->next; 218 int status; 219 if (waitpid(p->pid, &status, WNOHANG) == p->pid) { 220 log_error("dhcp: %s: client pid %d exited (status %d), lease will not be renewed", 221 p->ifname, (int)p->pid, WIFEXITED(status) ? WEXITSTATUS(status) : -1); 222 proc_drop(p); 223 } 224 p = next; 225 } 226 }