linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

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 }