linkd

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

ipc.c (21759B)


      1 #define _GNU_SOURCE
      2 #include <arpa/inet.h>
      3 #include <errno.h>
      4 #include <libgen.h>
      5 #include <net/if.h>
      6 #include <netdb.h>
      7 #include <netinet/in.h>
      8 #include <stdarg.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <strings.h>
     13 #include <sys/socket.h>
     14 #include <sys/stat.h>
     15 #include <sys/types.h>
     16 #include <sys/un.h>
     17 #include <unistd.h>
     18 
     19 #include "finwo/resp.h"
     20 #include "rxi/log.h"
     21 
     22 #include "config/daemon.h"
     23 #include "config/ifaces.h"
     24 #include "config/ports.h"
     25 #include "ipc.h"
     26 #include "netlink/netlink.h"
     27 #include "netlink/rtnl.h"
     28 #include "util/auth.h"
     29 
     30 #define RBUF_MAX (1024 * 1024)
     31 
     32 struct listener {
     33   int   fd;
     34   int   is_unix;
     35   char *path;      // unix only, for unlink on shutdown
     36 };
     37 
     38 struct ipc_conn {
     39   int    fd;
     40   int    is_unix;
     41   int    role;
     42   uid_t  uid;
     43 
     44   char  *rbuf; size_t rlen, rcap;
     45   char  *wbuf; size_t wlen, wcap, wsent;
     46 
     47   int    close_after_write;
     48   struct ipc_conn *next;
     49 };
     50 
     51 static struct listener  listeners[IPC_MAX_LISTENERS];
     52 static int              listener_n = 0;
     53 static struct ipc_conn *conns      = NULL;
     54 static int              conn_n     = 0;
     55 
     56 static int wbuf_append(struct ipc_conn *c, const char *data, size_t len) {
     57   if (c->wlen + len > c->wcap) {
     58     size_t cap = c->wcap ? c->wcap : 1024;
     59     while (cap < c->wlen + len) cap *= 2;
     60     char *g = realloc(c->wbuf, cap);
     61     if (!g) return -1;
     62     c->wbuf = g;
     63     c->wcap = cap;
     64   }
     65   memcpy(c->wbuf + c->wlen, data, len);
     66   c->wlen += len;
     67   return 0;
     68 }
     69 
     70 static void reply_obj(struct ipc_conn *c, resp_object *o) {
     71   if (!o) return;
     72   char  *buf = NULL;
     73   size_t len = 0;
     74   if (resp_serialize(o, &buf, &len) == 0) {
     75     wbuf_append(c, buf, len);
     76     free(buf);
     77   }
     78   resp_free(o);
     79 }
     80 
     81 static void reply_ok(struct ipc_conn *c) {
     82   reply_obj(c, resp_simple_init("OK"));
     83 }
     84 
     85 static void reply_err(struct ipc_conn *c, const char *fmt, ...) {
     86   char msg[512];
     87   va_list ap;
     88   va_start(ap, fmt);
     89   vsnprintf(msg, sizeof(msg), fmt, ap);
     90   va_end(ap);
     91   reply_obj(c, resp_error_init(msg));
     92 }
     93 
     94 static int run_hook(const char *cmd) {
     95   if (!cmd || !*cmd) return 0;
     96   log_info("ipc: running hook: %s", cmd);
     97   int rc = system(cmd);
     98   if (rc != 0) {
     99     log_warn("hook `%s` failed with %d", cmd, rc);
    100     return -1;
    101   }
    102   return 0;
    103 }
    104 
    105 static int do_ifup(struct ipc_conn *c, const char *ifname) {
    106   struct linkd_iface *iface = ifaces_find(ifname);
    107   if (!iface) {
    108     log_info("ipc: ifup %s (not in interfaces, just bringing link up)", ifname);
    109     if (rtnl_link_up(ifname) != 0) {
    110       reply_err(c, "ifup %s failed", ifname);
    111       return -1;
    112     }
    113     return 0;
    114   }
    115   log_info("ipc: ifup %s", ifname);
    116   if (iface->pre_up && run_hook(iface->pre_up) != 0) {
    117     reply_err(c, "pre-up hook failed for %s", ifname);
    118     return -1;
    119   }
    120 
    121   if (iface->vlan_id >= 0) {
    122     const char *raw = iface->vlan_raw_device;
    123     char tmp[IF_NAMESIZE];
    124     if (!raw) {
    125       char *dot = strrchr(iface->name, '.');
    126       if (dot) {
    127         size_t len = (size_t)(dot - iface->name);
    128         if (len < sizeof(tmp)) {
    129           memcpy(tmp, iface->name, len);
    130           tmp[len] = '\0';
    131           raw = tmp;
    132         }
    133       }
    134     }
    135     if (raw) {
    136       log_info("ipc: ifup %s vlan %d on %s", ifname, iface->vlan_id, raw);
    137       rtnl_vlan_create(ifname, raw, iface->vlan_id);
    138     }
    139   }
    140 
    141   // `ifup <vrf>` alone must work, not only a full apply.
    142   if (iface_is_vrf(iface)) {
    143     log_info("ipc: ifup %s vrf table %d", ifname, iface->vrf_table);
    144     rtnl_vrf_create(ifname, (uint32_t)iface->vrf_table);
    145   }
    146 
    147   if (iface_is_bridge(iface)) {
    148     log_info("ipc: ifup %s bridge (ports %s)", ifname,
    149              iface->bridge_ports ? iface->bridge_ports : "(none)");
    150     rtnl_bridge_create(ifname);
    151     if (iface->bridge_stp) {
    152       bool on = !strcasecmp(iface->bridge_stp, "on") || !strcasecmp(iface->bridge_stp, "yes");
    153       rtnl_bridge_set_stp(ifname, on);
    154     }
    155     if (iface->bridge_vlan_aware >= 0) {
    156       rtnl_bridge_set_vlan_aware(ifname, iface->bridge_vlan_aware);
    157     }
    158     if (iface->bridge_ports && strcasecmp(iface->bridge_ports, "none") != 0) {
    159       char *ports = strdup(iface->bridge_ports);
    160       char *tok = strtok(ports, " \t");
    161       while (tok) {
    162         if (strcasecmp(tok, "none") == 0) { tok = strtok(NULL, " \t"); continue; }
    163         log_info("ipc: ifup %s bridge add port %s", ifname, tok);
    164         rtnl_bridge_add_port(ifname, tok);
    165         rtnl_link_up(tok);
    166         tok = strtok(NULL, " \t");
    167       }
    168       free(ports);
    169     }
    170   }
    171 
    172   // Enslave before addresses land: entering a VRF flushes them.
    173   if (iface->vrf_master) {
    174     rtnl_vrf_add_port(iface->vrf_master, ifname);
    175   }
    176   if (rtnl_link_up(ifname) != 0) {
    177     reply_err(c, "ifup %s: link up failed", ifname);
    178     return -1;
    179   }
    180   for (struct iface_addr *a = iface->addrs; a; a = a->next) {
    181     log_info("ipc: ifup %s addr %s", ifname, a->address);
    182     rtnl_addr_add(ifname, a->address, a->netmask);
    183   }
    184   if (iface->gateway) {
    185     log_info("ipc: ifup %s gateway %s", ifname, iface->gateway);
    186     rtnl_route_add_default(iface->gateway, ifname);
    187   }
    188   if (iface->mtu && rtnl_link_set_mtu(ifname, iface->mtu) != 0) {
    189     reply_err(c, "ifup %s: mtu failed", ifname);
    190     return -1;
    191   }
    192   if (iface->hwaddress && rtnl_link_set_hwaddr(ifname, iface->hwaddress) != 0) {
    193     reply_err(c, "ifup %s: hwaddress failed", ifname);
    194     return -1;
    195   }
    196   if (iface->post_up && run_hook(iface->post_up) != 0) {
    197     reply_err(c, "post-up hook failed for %s", ifname);
    198     return -1;
    199   }
    200   return 0;
    201 }
    202 
    203 static int do_ifdown(struct ipc_conn *c, const char *ifname) {
    204   struct linkd_iface *iface = ifaces_find(ifname);
    205   if (!iface) {
    206     log_info("ipc: ifdown %s (not in interfaces, just bringing link down)", ifname);
    207     if (rtnl_link_down(ifname) != 0) {
    208       reply_err(c, "ifdown %s failed", ifname);
    209       return -1;
    210     }
    211     return 0;
    212   }
    213   log_info("ipc: ifdown %s", ifname);
    214   if (iface->pre_down && run_hook(iface->pre_down) != 0) {
    215     reply_err(c, "pre-down hook failed for %s", ifname);
    216     return -1;
    217   }
    218   if (rtnl_link_down(ifname) != 0) {
    219     reply_err(c, "ifdown %s failed", ifname);
    220     return -1;
    221   }
    222   if (iface->post_down && run_hook(iface->post_down) != 0) {
    223     reply_err(c, "post-down hook failed for %s", ifname);
    224     return -1;
    225   }
    226   return 0;
    227 }
    228 
    229 static int count_ifaces(void) {
    230   int n = 0;
    231   for (struct linkd_iface *c = ifaces_list(); c; c = c->next) n++;
    232   return n;
    233 }
    234 
    235 static int do_ifreload(struct ipc_conn *c) {
    236   log_info("ipc: ifreload start, old list has %d", count_ifaces());
    237   // TODO: real diff-apply; this frees and re-parses.
    238   ifaces_free();
    239   ports_free();
    240 
    241   const struct linkd_daemon_cfg *dcfg = daemon_cfg();
    242   if (load_namespace("interfaces", dcfg->iface, dcfg->iface_n) < 0) {
    243     reply_err(c, "ifreload: failed to reload interface configuration");
    244     return -1;
    245   }
    246   if (load_namespace("ports", dcfg->ports, dcfg->ports_n) < 0) {
    247     reply_err(c, "ifreload: failed to reload port configuration");
    248     return -1;
    249   }
    250   log_info("ipc: ifreload done, new list has %d", count_ifaces());
    251   return 0;
    252 }
    253 
    254 // Flat key/value pairs (RESP2 map). Keys may repeat: several addresses.
    255 static void iface_to_map(resp_object *m, const struct linkd_iface *i) {
    256   char num[16];
    257   resp_array_append_bulk(m, "name");
    258   resp_array_append_bulk(m, i->name);
    259   if (i->method) {
    260     resp_array_append_bulk(m, "method");
    261     resp_array_append_bulk(m, i->method);
    262   }
    263   resp_array_append_bulk(m, "auto");
    264   resp_array_append_bulk(m, i->auto_flag ? "yes" : "no");
    265   for (struct iface_addr *a = i->addrs; a; a = a->next) {
    266     resp_array_append_bulk(m, "address");
    267     resp_array_append_bulk(m, a->address);
    268     if (a->netmask) {
    269       resp_array_append_bulk(m, "netmask");
    270       resp_array_append_bulk(m, a->netmask);
    271     }
    272   }
    273   if (i->gateway)          { resp_array_append_bulk(m, "gateway");   resp_array_append_bulk(m, i->gateway); }
    274   if (i->mtu)              { snprintf(num, sizeof(num), "%d", i->mtu);
    275                              resp_array_append_bulk(m, "mtu");       resp_array_append_bulk(m, num); }
    276   if (i->hwaddress)        { resp_array_append_bulk(m, "hwaddress"); resp_array_append_bulk(m, i->hwaddress); }
    277   if (i->vlan_raw_device)  { resp_array_append_bulk(m, "vlan-raw-device"); resp_array_append_bulk(m, i->vlan_raw_device); }
    278   if (i->vlan_id >= 0)     { snprintf(num, sizeof(num), "%d", i->vlan_id);
    279                              resp_array_append_bulk(m, "vlan-id");   resp_array_append_bulk(m, num); }
    280   if (i->bridge_ports)     { resp_array_append_bulk(m, "bridge-ports"); resp_array_append_bulk(m, i->bridge_ports); }
    281   if (i->bridge_stp)       { resp_array_append_bulk(m, "bridge-stp");   resp_array_append_bulk(m, i->bridge_stp); }
    282   if (i->bridge_vlan_aware >= 0) {
    283     resp_array_append_bulk(m, "bridge-vlan-aware");
    284     resp_array_append_bulk(m, i->bridge_vlan_aware ? "yes" : "no");
    285   }
    286   if (i->vrf_table >= 0)   { snprintf(num, sizeof(num), "%d", i->vrf_table);
    287                              resp_array_append_bulk(m, "vrf-table"); resp_array_append_bulk(m, num); }
    288   if (i->vrf_master)       { resp_array_append_bulk(m, "vrf");       resp_array_append_bulk(m, i->vrf_master); }
    289   if (i->pre_up)           { resp_array_append_bulk(m, "pre-up");    resp_array_append_bulk(m, i->pre_up); }
    290   if (i->post_up)          { resp_array_append_bulk(m, "post-up");   resp_array_append_bulk(m, i->post_up); }
    291   if (i->pre_down)         { resp_array_append_bulk(m, "pre-down");  resp_array_append_bulk(m, i->pre_down); }
    292   if (i->post_down)        { resp_array_append_bulk(m, "post-down"); resp_array_append_bulk(m, i->post_down); }
    293 }
    294 
    295 static int do_ifquery(struct ipc_conn *c, const char *ifname) {
    296   if (ifname) {
    297     struct linkd_iface *i = ifaces_find(ifname);
    298     if (!i) {
    299       reply_err(c, "no such interface: %s", ifname);
    300       return -1;
    301     }
    302     resp_object *m = resp_array_init();
    303     iface_to_map(m, i);
    304     reply_obj(c, m);
    305     return 0;
    306   }
    307 
    308   resp_object *outer = resp_array_init();
    309   for (struct linkd_iface *i = ifaces_list(); i; i = i->next) {
    310     resp_object *m = resp_array_init();
    311     iface_to_map(m, i);
    312     resp_array_append_obj(outer, m);
    313   }
    314   reply_obj(c, outer);
    315   return 0;
    316 }
    317 
    318 struct command {
    319   const char *name;
    320   int         min_args;   // including the verb
    321   int         max_args;   // -1 = unlimited
    322   int         role;       // minimum role
    323 };
    324 
    325 static const struct command commands[] = {
    326   { "PING",     1, 2, IPC_ROLE_NONE     },
    327   { "INFO",     1, 1, IPC_ROLE_READONLY },
    328   { "COMMAND",  1, 2, IPC_ROLE_READONLY },
    329   { "IFQUERY",  1, 2, IPC_ROLE_READONLY },
    330   { "IFUP",     2, 2, IPC_ROLE_FULL     },
    331   { "IFDOWN",   2, 2, IPC_ROLE_FULL     },
    332   { "IFRELOAD", 1, 1, IPC_ROLE_FULL     },
    333   { "AUTH",     2, 3, IPC_ROLE_NONE     },
    334   { "QUIT",     1, 1, IPC_ROLE_NONE     },
    335 };
    336 static const size_t commands_n = sizeof(commands) / sizeof(commands[0]);
    337 
    338 static const char *arg(const resp_object *cmd, size_t i) {
    339   if (!cmd || cmd->type != RESPT_ARRAY || i >= cmd->u.arr.n) return NULL;
    340   const resp_object *e = &cmd->u.arr.elem[i];
    341   if (e->type != RESPT_BULK && e->type != RESPT_SIMPLE) return NULL;
    342   return e->u.s;
    343 }
    344 
    345 static void do_info(struct ipc_conn *c) {
    346   char buf[512];
    347   snprintf(buf, sizeof(buf),
    348            "# Server\r\n"
    349            "name:linkd\r\n"
    350            "version:" __TARGET "\r\n"
    351            "\r\n"
    352            "# Clients\r\n"
    353            "connected_clients:%d\r\n"
    354            "\r\n"
    355            "# Config\r\n"
    356            "interfaces:%d\r\n",
    357            conn_n, count_ifaces());
    358   resp_object *o = calloc(1, sizeof(*o));
    359   if (!o) return;
    360   o->type = RESPT_BULK;
    361   o->u.s  = strdup(buf);
    362   reply_obj(c, o);
    363 }
    364 
    365 static void do_command_list(struct ipc_conn *c) {
    366   resp_object *a = resp_array_init();
    367   for (size_t i = 0; i < commands_n; i++) {
    368     resp_object *e = resp_array_init();
    369     resp_array_append_bulk(e, commands[i].name);
    370     resp_array_append_int(e, commands[i].max_args < 0
    371                               ? -commands[i].min_args : commands[i].max_args);
    372     resp_array_append_obj(a, e);
    373   }
    374   reply_obj(c, a);
    375 }
    376 
    377 static void do_auth(struct ipc_conn *c, const resp_object *cmd) {
    378   const struct linkd_daemon_cfg *cfg = daemon_cfg();
    379   if (!cfg->authfile) {
    380     reply_err(c, "Client sent AUTH, but no password file is configured");
    381     return;
    382   }
    383 
    384   const char *user = arg(cmd, 1);
    385   const char *pass = arg(cmd, 2);
    386   if (!pass) { pass = user; user = "default"; }
    387   if (!user || !pass) {
    388     reply_err(c, "wrong number of arguments for 'AUTH'");
    389     return;
    390   }
    391 
    392   int role = auth_check(cfg->authfile, user, pass);
    393   if (role == AUTH_ROLE_NONE) {
    394     log_warn("ipc: failed AUTH for user '%s'", user);
    395     reply_err(c, "WRONGPASS invalid username-password pair");
    396     return;
    397   }
    398 
    399   c->role = (role == AUTH_ROLE_FULL) ? IPC_ROLE_FULL : IPC_ROLE_READONLY;
    400   log_info("ipc: user '%s' authenticated (%s)", user,
    401            c->role == IPC_ROLE_FULL ? "full" : "readonly");
    402   reply_ok(c);
    403 }
    404 
    405 static void dispatch(struct ipc_conn *c, const resp_object *cmd) {
    406   const char *verb = arg(cmd, 0);
    407   if (!verb) {
    408     reply_err(c, "ERR malformed command");
    409     return;
    410   }
    411 
    412   size_t argc = (cmd->type == RESPT_ARRAY) ? cmd->u.arr.n : 0;
    413 
    414   const struct command *spec = NULL;
    415   for (size_t i = 0; i < commands_n; i++) {
    416     if (!strcasecmp(verb, commands[i].name)) { spec = &commands[i]; break; }
    417   }
    418   if (!spec) {
    419     reply_err(c, "unknown command '%s'", verb);
    420     return;
    421   }
    422 
    423   if ((int)argc < spec->min_args ||
    424       (spec->max_args >= 0 && (int)argc > spec->max_args)) {
    425     reply_err(c, "wrong number of arguments for '%s'", verb);
    426     return;
    427   }
    428 
    429   if (c->role < spec->role) {
    430     reply_err(c, c->role == IPC_ROLE_NONE
    431                    ? "NOAUTH Authentication required"
    432                    : "NOPERM this command requires full access");
    433     return;
    434   }
    435 
    436   if (!strcasecmp(verb, "PING")) {
    437     const char *msg = arg(cmd, 1);
    438     if (msg) {
    439       resp_object *o = calloc(1, sizeof(*o));
    440       if (o) { o->type = RESPT_BULK; o->u.s = strdup(msg); reply_obj(c, o); }
    441     } else {
    442       reply_obj(c, resp_simple_init("PONG"));
    443     }
    444   } else if (!strcasecmp(verb, "QUIT")) {
    445     reply_ok(c);
    446     c->close_after_write = 1;
    447   } else if (!strcasecmp(verb, "INFO")) {
    448     do_info(c);
    449   } else if (!strcasecmp(verb, "COMMAND")) {
    450     do_command_list(c);
    451   } else if (!strcasecmp(verb, "AUTH")) {
    452     do_auth(c, cmd);
    453   } else if (!strcasecmp(verb, "IFQUERY")) {
    454     do_ifquery(c, arg(cmd, 1));
    455   } else if (!strcasecmp(verb, "IFUP")) {
    456     if (do_ifup(c, arg(cmd, 1)) == 0) reply_ok(c);
    457   } else if (!strcasecmp(verb, "IFDOWN")) {
    458     if (do_ifdown(c, arg(cmd, 1)) == 0) reply_ok(c);
    459   } else if (!strcasecmp(verb, "IFRELOAD")) {
    460     if (do_ifreload(c) == 0) reply_ok(c);
    461   }
    462 }
    463 
    464 static void conn_close(struct ipc_conn *c) {
    465   struct ipc_conn **p = &conns;
    466   while (*p && *p != c) p = &(*p)->next;
    467   if (*p) *p = c->next;
    468   if (c->fd >= 0) close(c->fd);
    469   free(c->rbuf);
    470   free(c->wbuf);
    471   free(c);
    472   conn_n--;
    473 }
    474 
    475 static void conn_accept(struct listener *l) {
    476   int fd = accept4(l->fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
    477   if (fd < 0) return;
    478 
    479   if (conn_n >= IPC_MAX_CONNS) {
    480     log_warn("ipc: connection limit reached, rejecting");
    481     close(fd);
    482     return;
    483   }
    484 
    485   struct ipc_conn *c = calloc(1, sizeof(*c));
    486   if (!c) { close(fd); return; }
    487   c->fd      = fd;
    488   c->is_unix = l->is_unix;
    489   c->role    = IPC_ROLE_NONE;
    490 
    491   // Root over unix is pre-authenticated; the kernel vouches for the uid.
    492   if (l->is_unix) {
    493     struct ucred cred;
    494     socklen_t len = sizeof(cred);
    495     if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == 0) {
    496       c->uid = cred.uid;
    497       if (cred.uid == 0) c->role = IPC_ROLE_FULL;
    498     }
    499   }
    500 
    501   c->next = conns;
    502   conns   = c;
    503   conn_n++;
    504 }
    505 
    506 static void conn_read(struct ipc_conn *c) {
    507   char buf[4096];
    508   for (;;) {
    509     ssize_t n = read(c->fd, buf, sizeof(buf));
    510     if (n == 0) { conn_close(c); return; }
    511     if (n < 0) {
    512       if (errno == EINTR) continue;
    513       if (errno == EAGAIN || errno == EWOULDBLOCK) break;
    514       conn_close(c);
    515       return;
    516     }
    517     if (c->rlen + (size_t)n > RBUF_MAX) {
    518       log_warn("ipc: request too large, dropping client");
    519       conn_close(c);
    520       return;
    521     }
    522     if (c->rlen + (size_t)n > c->rcap) {
    523       size_t cap = c->rcap ? c->rcap : 4096;
    524       while (cap < c->rlen + (size_t)n) cap *= 2;
    525       char *g = realloc(c->rbuf, cap);
    526       if (!g) { conn_close(c); return; }
    527       c->rbuf = g;
    528       c->rcap = cap;
    529     }
    530     memcpy(c->rbuf + c->rlen, buf, (size_t)n);
    531     c->rlen += (size_t)n;
    532     if ((size_t)n < sizeof(buf)) break;
    533   }
    534 
    535   // Drain every complete command; this is what makes pipelining work.
    536   for (;;) {
    537     resp_object *cmd = NULL;
    538     int used = resp_read_buf(c->rbuf, c->rlen, &cmd);
    539     if (used <= 0) {
    540       // 0 = nothing yet, <0 = partial; wait for more data.
    541       if (cmd) resp_free(cmd);
    542       break;
    543     }
    544     dispatch(c, cmd);
    545     resp_free(cmd);
    546 
    547     memmove(c->rbuf, c->rbuf + used, c->rlen - (size_t)used);
    548     c->rlen -= (size_t)used;
    549     if (c->rlen == 0) break;
    550   }
    551 }
    552 
    553 static void conn_write(struct ipc_conn *c) {
    554   while (c->wsent < c->wlen) {
    555     ssize_t n = write(c->fd, c->wbuf + c->wsent, c->wlen - c->wsent);
    556     if (n < 0) {
    557       if (errno == EINTR) continue;
    558       if (errno == EAGAIN || errno == EWOULDBLOCK) return;
    559       conn_close(c);
    560       return;
    561     }
    562     c->wsent += (size_t)n;
    563   }
    564   c->wlen = c->wsent = 0;
    565   if (c->close_after_write) conn_close(c);
    566 }
    567 
    568 static int listen_unix(const char *path) {
    569   struct sockaddr_un addr;
    570 
    571   {
    572     char *d = strdup(path);
    573     if (d) { mkdir(dirname(d), 0755); free(d); }
    574   }
    575   unlink(path);
    576 
    577   int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
    578   if (fd < 0) {
    579     log_error("ipc: socket: %s", strerror(errno));
    580     return -1;
    581   }
    582 
    583   memset(&addr, 0, sizeof(addr));
    584   addr.sun_family = AF_UNIX;
    585   strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
    586 
    587   if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
    588     log_error("ipc: bind %s: %s", path, strerror(errno));
    589     close(fd);
    590     return -1;
    591   }
    592   if (chmod(path, 0600) != 0) {
    593     log_warn("ipc: chmod %s: %s", path, strerror(errno));
    594   }
    595   if (listen(fd, 8) != 0) {
    596     log_error("ipc: listen %s: %s", path, strerror(errno));
    597     close(fd);
    598     unlink(path);
    599     return -1;
    600   }
    601   return fd;
    602 }
    603 
    604 static int listen_tcp(const char *hostport) {
    605   char *copy = strdup(hostport);
    606   if (!copy) return -1;
    607 
    608   char *host = copy;
    609   char *port = strrchr(copy, ':');
    610   if (port) { *port++ = '\0'; } else { port = (char *)"6789"; }
    611   if (*host == '\0') host = NULL;
    612 
    613   struct addrinfo hints = {
    614     .ai_family   = AF_UNSPEC,
    615     .ai_socktype = SOCK_STREAM,
    616     .ai_flags    = AI_PASSIVE,
    617   };
    618   struct addrinfo *res = NULL;
    619   int gai = getaddrinfo(host, port, &hints, &res);
    620   if (gai != 0) {
    621     log_error("ipc: resolve %s: %s", hostport, gai_strerror(gai));
    622     free(copy);
    623     return -1;
    624   }
    625 
    626   int fd = -1;
    627   for (struct addrinfo *ai = res; ai; ai = ai->ai_next) {
    628     fd = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK, ai->ai_protocol);
    629     if (fd < 0) continue;
    630     int one = 1;
    631     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    632     if (bind(fd, ai->ai_addr, ai->ai_addrlen) == 0 && listen(fd, 8) == 0) break;
    633     close(fd);
    634     fd = -1;
    635   }
    636   freeaddrinfo(res);
    637   free(copy);
    638 
    639   if (fd < 0) log_error("ipc: cannot listen on %s", hostport);
    640   return fd;
    641 }
    642 
    643 int ipc_init(void) {
    644   const struct linkd_daemon_cfg *cfg = daemon_cfg();
    645   int ok = 0;
    646 
    647   for (size_t i = 0; i < cfg->listen_n && listener_n < IPC_MAX_LISTENERS; i++) {
    648     const char *a = cfg->listen[i];
    649     int   is_unix = !strncmp(a, "unix://", 7);
    650     int   fd;
    651 
    652     if (is_unix) {
    653       fd = listen_unix(a + 7);
    654     } else {
    655       // No way to authorise a tcp client without one; SO_PEERCRED is
    656       // unix-only.
    657       if (!cfg->authfile) {
    658         log_error("ipc: %s needs `authfile` configured; refusing to listen", a);
    659         continue;
    660       }
    661       fd = listen_tcp(a + 6);
    662     }
    663     if (fd < 0) continue;
    664 
    665     listeners[listener_n].fd      = fd;
    666     listeners[listener_n].is_unix = is_unix;
    667     listeners[listener_n].path    = is_unix ? strdup(a + 7) : NULL;
    668     listener_n++;
    669     ok++;
    670     log_info("ipc: listening on %s", a);
    671   }
    672 
    673   return ok ? 0 : -1;
    674 }
    675 
    676 void ipc_fini(void) {
    677   while (conns) conn_close(conns);
    678   for (int i = 0; i < listener_n; i++) {
    679     if (listeners[i].fd >= 0) close(listeners[i].fd);
    680     if (listeners[i].path) {
    681       unlink(listeners[i].path);
    682       free(listeners[i].path);
    683     }
    684   }
    685   listener_n = 0;
    686 }
    687 
    688 int ipc_pollfds(struct pollfd *pfds, int max) {
    689   int n = 0;
    690   for (int i = 0; i < listener_n && n < max; i++) {
    691     pfds[n].fd      = listeners[i].fd;
    692     pfds[n].events  = POLLIN;
    693     pfds[n].revents = 0;
    694     n++;
    695   }
    696   for (struct ipc_conn *c = conns; c && n < max; c = c->next) {
    697     pfds[n].fd      = c->fd;
    698     pfds[n].events  = POLLIN | (c->wsent < c->wlen ? POLLOUT : 0);
    699     pfds[n].revents = 0;
    700     n++;
    701   }
    702   return n;
    703 }
    704 
    705 void ipc_dispatch(struct pollfd *pfds, int n) {
    706   for (int i = 0; i < n; i++) {
    707     if (!pfds[i].revents) continue;
    708 
    709     int is_listener = 0;
    710     for (int l = 0; l < listener_n; l++) {
    711       if (listeners[l].fd == pfds[i].fd) {
    712         conn_accept(&listeners[l]);
    713         is_listener = 1;
    714         break;
    715       }
    716     }
    717     if (is_listener) continue;
    718 
    719     // Look up by fd: an earlier fd may have closed and freed this one.
    720     struct ipc_conn *c = conns;
    721     while (c && c->fd != pfds[i].fd) c = c->next;
    722     if (!c) continue;
    723 
    724     if (pfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { conn_close(c); continue; }
    725     if (pfds[i].revents & POLLIN)  conn_read(c);
    726 
    727     // conn_read may have closed it; re-find before writing.
    728     struct ipc_conn *still = conns;
    729     while (still && still != c) still = still->next;
    730     if (!still) continue;
    731 
    732     if (c->wsent < c->wlen) conn_write(c);
    733   }
    734 }