linkd

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

ipc.c (22086B)


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