linkd

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

netlink.c (21437B)


      1 #define _GNU_SOURCE
      2 
      3 #include <errno.h>
      4 #include <net/if.h>
      5 #include <poll.h>
      6 #include <signal.h>
      7 #include <stdint.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <sys/socket.h>
     12 #include <time.h>
     13 #include <unistd.h>
     14 
     15 #include <linux/fib_rules.h>
     16 #include <linux/if_addr.h>
     17 #include <linux/if_link.h>
     18 #include <linux/neighbour.h>
     19 #include <linux/netlink.h>
     20 #include <linux/rtnetlink.h>
     21 
     22 #include <arpa/inet.h>
     23 
     24 #include "rxi/log.h"
     25 
     26 #include "dataplane.h"
     27 #include "dataplane/plugin.h"
     28 #include "../dhcp.h"
     29 
     30 #include "filter.h"
     31 #include "netlink.h"
     32 #include "../ipc.h"
     33 
     34 #define NL_BUFSZ (64 * 1024)
     35 #define NL_MAX_NH 16
     36 
     37 // VRF masters: master ifindex -> table id + name. Single digits expected.
     38 struct vrf_master {
     39   int ifindex;
     40   uint32_t table;
     41   char name[IF_NAMESIZE];
     42 };
     43 
     44 // Member enslavement: member ifindex -> master ifindex (0 = none).
     45 struct vrf_member {
     46   int ifindex;
     47   int master;
     48 };
     49 
     50 #define VRF_MAX_MASTERS 64
     51 #define VRF_MAX_MEMBERS 256
     52 
     53 static struct vrf_master vrf_masters[VRF_MAX_MASTERS];
     54 static int vrf_master_count = 0;
     55 static struct vrf_member vrf_members[VRF_MAX_MEMBERS];
     56 static int vrf_member_count = 0;
     57 
     58 static volatile sig_atomic_t nl_stop = 0;
     59 static uint32_t nl_seq = 0;
     60 
     61 void nl_request_stop(void) {
     62   nl_stop = 1;
     63 }
     64 
     65 static void vrf_map_reset(void) {
     66   vrf_master_count = 0;
     67   vrf_member_count = 0;
     68 }
     69 
     70 static struct vrf_master *vrf_master_find(int ifindex) {
     71   int i;
     72   for (i = 0; i < vrf_master_count; i++) {
     73     if (vrf_masters[i].ifindex == ifindex) return &vrf_masters[i];
     74   }
     75   return NULL;
     76 }
     77 
     78 static void vrf_master_set(int ifindex, uint32_t table, const char *name) {
     79   struct vrf_master *m = vrf_master_find(ifindex);
     80   if (m) {
     81     m->table = table;
     82   } else {
     83     if (vrf_master_count >= VRF_MAX_MASTERS) {
     84       log_warn("netlink: vrf master table full, ignoring %s", name ? name : "?");
     85       return;
     86     }
     87     m = &vrf_masters[vrf_master_count++];
     88     m->ifindex = ifindex;
     89     m->table = table;
     90   }
     91   if (name) {
     92     strncpy(m->name, name, sizeof(m->name) - 1);
     93     m->name[sizeof(m->name) - 1] = '\0';
     94   }
     95 }
     96 
     97 static void vrf_master_remove(int ifindex) {
     98   int i;
     99   for (i = 0; i < vrf_master_count; i++) {
    100     if (vrf_masters[i].ifindex == ifindex) {
    101       vrf_masters[i] = vrf_masters[--vrf_master_count];
    102       return;
    103     }
    104   }
    105 }
    106 
    107 static void vrf_member_set(int ifindex, int master) {
    108   int i;
    109   for (i = 0; i < vrf_member_count; i++) {
    110     if (vrf_members[i].ifindex == ifindex) {
    111       vrf_members[i].master = master;
    112       return;
    113     }
    114   }
    115   if (vrf_member_count >= VRF_MAX_MEMBERS) return;
    116   vrf_members[vrf_member_count].ifindex = ifindex;
    117   vrf_members[vrf_member_count].master = master;
    118   vrf_member_count++;
    119 }
    120 
    121 static void vrf_member_remove(int ifindex) {
    122   int i;
    123   for (i = 0; i < vrf_member_count; i++) {
    124     if (vrf_members[i].ifindex == ifindex) {
    125       vrf_members[i] = vrf_members[--vrf_member_count];
    126       return;
    127     }
    128   }
    129 }
    130 
    131 // Table owning an interface: master's table, or MAIN when unenslaved/unknown.
    132 static uint32_t vrf_table_of_iface(int ifindex) {
    133   int i;
    134   int master = 0;
    135   for (i = 0; i < vrf_member_count; i++) {
    136     if (vrf_members[i].ifindex == ifindex) {
    137       master = vrf_members[i].master;
    138       break;
    139     }
    140   }
    141   if (!master) return RT_TABLE_MAIN;
    142   for (i = 0; i < vrf_master_count; i++) {
    143     if (vrf_masters[i].ifindex == master) return vrf_masters[i].table;
    144   }
    145   return RT_TABLE_MAIN;
    146 }
    147 
    148 // rtm_table is 8-bit; ids above 255 arrive via RTA_TABLE with rtm_table set to
    149 // RT_TABLE_COMPAT. Getting this wrong silently aliases VRF tables onto MAIN.
    150 static uint32_t route_table_of(const struct rtmsg *rtm, struct rtattr **tb) {
    151   uint32_t table = rtm->rtm_table;
    152   if (table == RT_TABLE_COMPAT && tb[RTA_TABLE]) {
    153     memcpy(&table, RTA_DATA(tb[RTA_TABLE]), sizeof(table));
    154   }
    155   return table;
    156 }
    157 
    158 static void parse_rtattr(struct rtattr **tb, int max, struct rtattr *rta, int len) {
    159   memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
    160   while (RTA_OK(rta, len)) {
    161     if (rta->rta_type <= max) tb[rta->rta_type] = rta;
    162     rta = RTA_NEXT(rta, len);
    163   }
    164 }
    165 
    166 // No NDMSG_RTA helper in the UAPI headers; attrs follow the fixed struct.
    167 static struct rtattr *ndmsg_rta(const struct ndmsg *ndm) {
    168   return (struct rtattr *)(((char *)ndm) + NLMSG_ALIGN(sizeof(*ndm)));
    169 }
    170 
    171 static int handle_link(struct nlmsghdr *nh) {
    172   struct ifinfomsg *ifi = NLMSG_DATA(nh);
    173   struct rtattr *tb[IFLA_MAX + 1];
    174   char ifname[IF_NAMESIZE] = "";
    175   uint32_t mtu = 0;
    176   int master = 0;
    177   int is_vrf = 0;
    178   uint32_t vrf_table = 0;
    179   int len;
    180 
    181   len = (int)(nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)));
    182   if (len < 0) return 0;
    183   parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
    184 
    185   if (!tb[IFLA_IFNAME]) return 0;
    186   strncpy(ifname, RTA_DATA(tb[IFLA_IFNAME]), sizeof(ifname) - 1);
    187   if (tb[IFLA_MTU]) memcpy(&mtu, RTA_DATA(tb[IFLA_MTU]), sizeof(mtu));
    188   if (tb[IFLA_MASTER]) memcpy(&master, RTA_DATA(tb[IFLA_MASTER]), sizeof(master));
    189 
    190   // VRF masters carry linkinfo kind "vrf" with the table id nested inside
    191   // IFLA_INFO_DATA as IFLA_VRF_TABLE.
    192   if (tb[IFLA_LINKINFO]) {
    193     struct rtattr *li[IFLA_INFO_MAX + 1];
    194     struct rtattr *rta = RTA_DATA(tb[IFLA_LINKINFO]);
    195     int lilen = (int)RTA_PAYLOAD(tb[IFLA_LINKINFO]);
    196     parse_rtattr(li, IFLA_INFO_MAX, rta, lilen);
    197     if (li[IFLA_INFO_KIND] && !strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf")) {
    198       is_vrf = 1;
    199       if (li[IFLA_INFO_DATA]) {
    200         struct rtattr *vd[IFLA_VRF_MAX + 1];
    201         struct rtattr *vrta = RTA_DATA(li[IFLA_INFO_DATA]);
    202         int vlen = (int)RTA_PAYLOAD(li[IFLA_INFO_DATA]);
    203         parse_rtattr(vd, IFLA_VRF_MAX, vrta, vlen);
    204         if (vd[IFLA_VRF_TABLE]) {
    205           memcpy(&vrf_table, RTA_DATA(vd[IFLA_VRF_TABLE]), sizeof(vrf_table));
    206         }
    207       }
    208     }
    209   }
    210 
    211   if (nh->nlmsg_type == RTM_DELLINK) {
    212     vrf_member_remove(ifi->ifi_index);
    213     if (is_vrf || vrf_master_find(ifi->ifi_index)) {
    214       // Removal drops tracking; a standing exclusion survives re-creation.
    215       if (vrf_table) nl_filter_untrack(vrf_table);
    216       vrf_master_remove(ifi->ifi_index);
    217       log_info("netlink: vrf %s removed", ifname);
    218     }
    219     if (dp_port_admin(ifname, false) != DP_RET_OK) {
    220       log_error("netlink: port_admin %s down failed", ifname);
    221     }
    222     return 0;
    223   }
    224 
    225   vrf_member_set(ifi->ifi_index, master);
    226   if (is_vrf && vrf_table) {
    227     vrf_master_set(ifi->ifi_index, vrf_table, ifname);
    228     if (nl_filter_mgmt_name(ifname)) {
    229       nl_filter_exclude(vrf_table);
    230       log_info("netlink: vrf %s table %u excluded (management)", ifname, vrf_table);
    231     } else {
    232       nl_filter_track(vrf_table);
    233       log_info("netlink: vrf %s table %u tracked", ifname, vrf_table);
    234     }
    235   }
    236 
    237   log_debug("netlink: link %s %s mtu %u", ifname, (ifi->ifi_flags & IFF_UP) ? "up" : "down", mtu);
    238   if (dp_port_admin(ifname, (ifi->ifi_flags & IFF_UP) ? true : false) != DP_RET_OK) {
    239     log_error("netlink: port_admin %s failed", ifname);
    240   }
    241   if (mtu && dp_port_mtu(ifname, mtu) != DP_RET_OK) {
    242     log_error("netlink: port_mtu %s %u failed", ifname, mtu);
    243   }
    244   return 0;
    245 }
    246 
    247 static int handle_addr(struct nlmsghdr *nh) {
    248   struct ifaddrmsg *ifa = NLMSG_DATA(nh);
    249   struct rtattr *tb[IFA_MAX + 1];
    250   char ifname[IF_NAMESIZE] = "";
    251   struct dp_rif rif;
    252   struct dp_addr addr;
    253   void *bytes = NULL;
    254   size_t want = 0;
    255   int len;
    256   int del;
    257 
    258   len = (int)(nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
    259   if (len < 0) return 0;
    260   parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), len);
    261 
    262   if (ifa->ifa_family != AF_INET && ifa->ifa_family != AF_INET6) return 0;
    263   // IFA_LOCAL is the local address on point-to-point v4; IFA_ADDRESS would be
    264   // the peer. Prefer LOCAL whenever present.
    265   if (tb[IFA_LOCAL]) bytes = RTA_DATA(tb[IFA_LOCAL]);
    266   else if (tb[IFA_ADDRESS]) bytes = RTA_DATA(tb[IFA_ADDRESS]);
    267   else return 0;
    268   want = (ifa->ifa_family == AF_INET) ? 4 : 16;
    269 
    270   if (!if_indextoname(ifa->ifa_index, ifname)) return 0;
    271 
    272   memset(&addr, 0, sizeof(addr));
    273   addr.family = ifa->ifa_family;
    274   addr.prefixlen = ifa->ifa_prefixlen;
    275   memcpy(addr.addr, bytes, want);
    276 
    277   memset(&rif, 0, sizeof(rif));
    278   rif.ifname = ifname;
    279   rif.addr = addr;
    280   rif.table = vrf_table_of_iface(ifa->ifa_index);
    281   if (!nl_table_allowed(rif.table)) {
    282     log_debug("netlink: rif %s skipped (table %u not tracked)", ifname, rif.table);
    283     return 0;
    284   }
    285 
    286   del = (nh->nlmsg_type == RTM_DELADDR);
    287   log_debug("netlink: rif %s %s table %u", ifname, del ? "del" : "add", rif.table);
    288   if (del) {
    289     if (dp_rif_del(&rif) != DP_RET_OK) log_error("netlink: rif_del %s failed", ifname);
    290   } else {
    291     if (dp_rif_add(&rif) != DP_RET_OK) log_error("netlink: rif_add %s failed", ifname);
    292   }
    293   return 0;
    294 }
    295 
    296 static int handle_neigh(struct nlmsghdr *nh) {
    297   struct ndmsg *ndm = NLMSG_DATA(nh);
    298   struct rtattr *tb[NDA_MAX + 1];
    299   char ifname[IF_NAMESIZE] = "";
    300   struct dp_neigh neigh;
    301   int len;
    302   int del;
    303 
    304   len = (int)(nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ndm)));
    305   if (len < 0) return 0;
    306   parse_rtattr(tb, NDA_MAX, ndmsg_rta(ndm), len);
    307 
    308   if (ndm->ndm_family != AF_INET && ndm->ndm_family != AF_INET6) return 0;
    309   // Valid-only: incomplete/failed resolutions never reach the dataplane.
    310   del = (nh->nlmsg_type == RTM_DELNEIGH);
    311   if (!del) {
    312     if (ndm->ndm_state & (NUD_FAILED | NUD_INCOMPLETE)) del = 1;
    313     else if (!(ndm->ndm_state & (NUD_PERMANENT | NUD_REACHABLE | NUD_STALE | NUD_DELAY | NUD_PROBE))) {
    314       return 0;
    315     }
    316   }
    317   if (!tb[NDA_DST]) return 0;
    318   if (!del && (!tb[NDA_LLADDR] || RTA_PAYLOAD(tb[NDA_LLADDR]) != 6)) {
    319     log_debug("netlink: neigh skipped (no MAC)");
    320     return 0;
    321   }
    322   if (!if_indextoname(ndm->ndm_ifindex, ifname)) return 0;
    323 
    324   memset(&neigh, 0, sizeof(neigh));
    325   neigh.ifname = ifname;
    326   neigh.addr.family = ndm->ndm_family;
    327   neigh.addr.prefixlen = (ndm->ndm_family == AF_INET) ? 32 : 128;
    328   memcpy(neigh.addr.addr, RTA_DATA(tb[NDA_DST]), (ndm->ndm_family == AF_INET) ? 4 : 16);
    329   if (!del) memcpy(neigh.mac, RTA_DATA(tb[NDA_LLADDR]), 6);
    330   neigh.table = vrf_table_of_iface(ndm->ndm_ifindex);
    331   if (!nl_table_allowed(neigh.table)) {
    332     log_debug("netlink: neigh %s skipped (table %u not tracked)", ifname, neigh.table);
    333     return 0;
    334   }
    335 
    336   log_debug("netlink: neigh %s %s table %u", ifname, del ? "del" : "add", neigh.table);
    337   if (del) {
    338     if (dp_neigh_del(&neigh) != DP_RET_OK) log_error("netlink: neigh_del failed");
    339   } else {
    340     if (dp_neigh_add(&neigh) != DP_RET_OK) log_error("netlink: neigh_add failed");
    341   }
    342   return 0;
    343 }
    344 
    345 static int handle_route(struct nlmsghdr *nh) {
    346   struct rtmsg *rtm = NLMSG_DATA(nh);
    347   struct rtattr *tb[RTA_MAX + 1];
    348   struct dp_route route;
    349   struct dp_nexthop nhops[NL_MAX_NH];
    350   char nhnames[NL_MAX_NH][IF_NAMESIZE];
    351   uint32_t table;
    352   uint32_t metric = 0;
    353   int len;
    354   int del;
    355   size_t nh_count = 0;
    356 
    357   len = (int)(nh->nlmsg_len - NLMSG_LENGTH(sizeof(*rtm)));
    358   if (len < 0) return 0;
    359   parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
    360 
    361   table = route_table_of(rtm, tb);
    362   if (!nl_route_allowed(rtm->rtm_family, rtm->rtm_type, rtm->rtm_scope, table)) {
    363     return 0;
    364   }
    365   if (tb[RTA_PRIORITY]) memcpy(&metric, RTA_DATA(tb[RTA_PRIORITY]), sizeof(metric));
    366 
    367   memset(&route, 0, sizeof(route));
    368   memset(nhops, 0, sizeof(nhops));
    369   memset(nhnames, 0, sizeof(nhnames));
    370   route.dst.family = rtm->rtm_family;
    371   route.dst.prefixlen = rtm->rtm_dst_len;
    372   if (tb[RTA_DST]) {
    373     size_t want = (rtm->rtm_family == AF_INET) ? 4 : 16;
    374     memcpy(route.dst.addr, RTA_DATA(tb[RTA_DST]), want);
    375   }
    376   route.metric = metric;
    377   route.table = table;
    378 
    379   if (tb[RTA_MULTIPATH]) {
    380     // ECMP: nexthops nest gateways inside each rtnexthop, not at top level.
    381     struct rtnexthop *rtnh = RTA_DATA(tb[RTA_MULTIPATH]);
    382     int mplen = (int)RTA_PAYLOAD(tb[RTA_MULTIPATH]);
    383     while (mplen >= (int)sizeof(*rtnh) && RTNH_OK(rtnh, mplen) && nh_count < NL_MAX_NH) {
    384       struct rtattr *hops[RTA_MAX + 1];
    385       int cur = RTNH_ALIGN(rtnh->rtnh_len);
    386       int hlen = (int)rtnh->rtnh_len - (int)sizeof(*rtnh);
    387       memset(hops, 0, sizeof(hops));
    388       if (hlen > 0) parse_rtattr(hops, RTA_MAX, RTNH_DATA(rtnh), hlen);
    389       if (if_indextoname(rtnh->rtnh_ifindex, nhnames[nh_count])) {
    390         nhops[nh_count].ifname = nhnames[nh_count];
    391         nhops[nh_count].gw.family = rtm->rtm_family;
    392         if (hops[RTA_GATEWAY]) {
    393           size_t want = (rtm->rtm_family == AF_INET) ? 4 : 16;
    394           memcpy(nhops[nh_count].gw.addr, RTA_DATA(hops[RTA_GATEWAY]), want);
    395         } else {
    396           nhops[nh_count].gw.family = AF_UNSPEC;
    397         }
    398         nh_count++;
    399       }
    400       rtnh = RTNH_NEXT(rtnh);
    401       mplen -= cur;
    402     }
    403     if (mplen > 0 && nh_count >= NL_MAX_NH) {
    404       log_warn("netlink: ECMP group truncated to %d nexthops", NL_MAX_NH);
    405     }
    406   } else {
    407     uint32_t oif = 0;
    408     if (tb[RTA_OIF]) memcpy(&oif, RTA_DATA(tb[RTA_OIF]), sizeof(oif));
    409     // Directly-connected routes carry OIF without a gateway; represent with
    410     // an empty (AF_UNSPEC) gw rather than inventing one.
    411     if (oif || tb[RTA_GATEWAY]) {
    412       if (oif && !if_indextoname(oif, nhnames[0])) return 0;
    413       nhops[0].ifname = oif ? nhnames[0] : NULL;
    414       nhops[0].gw.family = rtm->rtm_family;
    415       if (tb[RTA_GATEWAY]) {
    416         size_t want = (rtm->rtm_family == AF_INET) ? 4 : 16;
    417         memcpy(nhops[0].gw.addr, RTA_DATA(tb[RTA_GATEWAY]), want);
    418       } else {
    419         nhops[0].gw.family = AF_UNSPEC;
    420       }
    421       nh_count = 1;
    422     }
    423   }
    424 
    425   route.nh = nh_count ? nhops : NULL;
    426   route.nh_count = nh_count;
    427 
    428   del = (nh->nlmsg_type == RTM_DELROUTE);
    429   log_debug("netlink: route %s table %u nh %zu", del ? "del" : "add", table, nh_count);
    430   if (del) {
    431     if (dp_route_del(&route) != DP_RET_OK) log_error("netlink: route_del table %u failed", table);
    432   } else {
    433     if (dp_route_add(&route) != DP_RET_OK) log_error("netlink: route_add table %u failed", table);
    434   }
    435   return 0;
    436 }
    437 
    438 static int handle_nlmsg(struct nlmsghdr *nh) {
    439   switch (nh->nlmsg_type) {
    440   case RTM_NEWLINK:
    441   case RTM_DELLINK:
    442     return handle_link(nh);
    443   case RTM_NEWADDR:
    444   case RTM_DELADDR:
    445     return handle_addr(nh);
    446   case RTM_NEWNEIGH:
    447   case RTM_DELNEIGH:
    448     return handle_neigh(nh);
    449   case RTM_NEWROUTE:
    450   case RTM_DELROUTE:
    451     return handle_route(nh);
    452   default:
    453     return 0;
    454   }
    455 }
    456 
    457 int nl_open(void) {
    458   struct sockaddr_nl addr;
    459   int fd;
    460   int sndbuf = 256 * 1024;
    461   int rcvbuf = 2 * 1024 * 1024;
    462 
    463   fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_ROUTE);
    464   if (fd < 0) {
    465     log_error("netlink: socket failed: %s", strerror(errno));
    466     return -1;
    467   }
    468   // Best effort: large receive buffer so bursts become resyncs, not losses.
    469   // May fail without privilege in dev environments; event flow still works.
    470   if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf)) != 0) {
    471     log_warn("netlink: SO_RCVBUFFORCE failed: %s", strerror(errno));
    472   }
    473   setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
    474 
    475   memset(&addr, 0, sizeof(addr));
    476   addr.nl_family = AF_NETLINK;
    477   // RTMGRP_* are group bitmasks; RTNLGRP_* are group numbers. Do not mix up.
    478   addr.nl_groups = RTMGRP_LINK | RTMGRP_NEIGH
    479     | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR
    480     | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
    481   if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
    482     log_error("netlink: bind failed: %s", strerror(errno));
    483     close(fd);
    484     return -1;
    485   }
    486   return fd;
    487 }
    488 
    489 // Send one GET*+DUMP and dispatch matching replies through handle_nlmsg.
    490 int nl_dump(int fd, uint16_t type, int family) {
    491   struct {
    492     struct nlmsghdr nh;
    493     struct rtgenmsg gen;
    494   } req;
    495   char buf[NL_BUFSZ];
    496   int done = 0;
    497   int rc = 0;
    498 
    499   memset(&req, 0, sizeof(req));
    500   req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.gen));
    501   req.nh.nlmsg_type = type;
    502   req.nh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
    503   req.nh.nlmsg_seq = ++nl_seq;
    504   req.nh.nlmsg_pid = 0;
    505   req.gen.rtgen_family = (uint8_t)family;
    506 
    507   if (send(fd, &req, req.nh.nlmsg_len, 0) < 0) {
    508     log_error("netlink: dump send %u failed: %s", type, strerror(errno));
    509     return -1;
    510   }
    511 
    512   while (!done) {
    513     struct pollfd pfd = { .fd = fd, .events = POLLIN };
    514     struct nlmsghdr *h;
    515     int len;
    516     int pr = poll(&pfd, 1, 5000);
    517     if (pr <= 0) {
    518       log_error("netlink: dump recv %u timed out", type);
    519       return -1;
    520     }
    521     len = recv(fd, buf, sizeof(buf), 0);
    522     if (len < 0) {
    523       if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
    524       log_error("netlink: dump recv %u failed: %s", type, strerror(errno));
    525       return -1;
    526     }
    527     h = (struct nlmsghdr *)buf;
    528     while (NLMSG_OK(h, len)) {
    529       if (h->nlmsg_seq && h->nlmsg_seq != nl_seq) goto next;
    530       if (h->nlmsg_type == NLMSG_DONE) {
    531         done = 1;
    532         break;
    533       }
    534       if (h->nlmsg_type == NLMSG_ERROR) {
    535         struct nlmsgerr *e = NLMSG_DATA(h);
    536         if (e->error) {
    537           log_error("netlink: dump %u error: %s", type, strerror(-e->error));
    538           rc = -1;
    539           done = 1;
    540           break;
    541         }
    542         goto next;
    543       }
    544       handle_nlmsg(h);
    545     next:
    546       h = NLMSG_NEXT(h, len);
    547     }
    548   }
    549   return rc;
    550 }
    551 
    552 // Full converge: VRF map first (routes resolve against it), then RIFs,
    553 // neighbours, routes, then the backend's own resync backstop.
    554 int nl_resync(int fd) {
    555   int fails = 0;
    556 
    557   nl_filter_reset();
    558   vrf_map_reset();
    559 
    560   if (nl_dump(fd, RTM_GETLINK, AF_UNSPEC) != 0) fails++;
    561   if (nl_dump(fd, RTM_GETADDR, AF_INET) != 0) fails++;
    562   if (nl_dump(fd, RTM_GETADDR, AF_INET6) != 0) fails++;
    563   if (nl_dump(fd, RTM_GETNEIGH, AF_INET) != 0) fails++;
    564   if (nl_dump(fd, RTM_GETNEIGH, AF_INET6) != 0) fails++;
    565   if (nl_dump(fd, RTM_GETROUTE, AF_INET) != 0) fails++;
    566   if (nl_dump(fd, RTM_GETROUTE, AF_INET6) != 0) fails++;
    567 
    568   if (dp_resync() != DP_RET_OK) {
    569     log_error("netlink: plugin resync failed");
    570     fails++;
    571   }
    572   if (fails) {
    573     log_warn("netlink: resync finished with %d failures", fails);
    574     return -1;
    575   }
    576   log_info("netlink: resync complete");
    577   return 0;
    578 }
    579 
    580 static long elapsed_ms(const struct timespec *a, const struct timespec *b) {
    581   return (b->tv_sec - a->tv_sec) * 1000L + (b->tv_nsec - a->tv_nsec) / 1000000L;
    582 }
    583 
    584 int nl_run(int fd, int resync_interval_s) {
    585   static char buf[NL_BUFSZ];
    586   struct timespec last_resync;
    587   clock_gettime(CLOCK_MONOTONIC, &last_resync);
    588 
    589   while (!nl_stop) {
    590     dhcp_reap();
    591 
    592     // Netlink occupies slot 0; the IPC server contributes its listeners and
    593     // every live connection after it.
    594     struct pollfd pfds[1 + IPC_MAX_POLLFDS];
    595     int nfds = 1;
    596     struct timespec now;
    597     long wait_ms = 30000;
    598     int pr;
    599 
    600     pfds[0].fd = fd;
    601     pfds[0].events = POLLIN;
    602     pfds[0].revents = 0;
    603     nfds += ipc_pollfds(&pfds[1], IPC_MAX_POLLFDS);
    604 
    605     clock_gettime(CLOCK_MONOTONIC, &now);
    606     if (resync_interval_s > 0) {
    607       long remain = resync_interval_s * 1000L - elapsed_ms(&last_resync, &now);
    608       if (remain <= 0) {
    609         if (nl_resync(fd) != 0) {
    610           // Log-and-continue with a single immediate retry; the next timer
    611           // converges anything still missing.
    612           log_warn("netlink: periodic resync failed, retrying once");
    613           if (nl_resync(fd) != 0) log_error("netlink: periodic resync retry failed");
    614         }
    615         clock_gettime(CLOCK_MONOTONIC, &last_resync);
    616         continue;
    617       }
    618       if (remain < wait_ms) wait_ms = remain;
    619     }
    620 
    621     pr = poll(pfds, nfds, (int)wait_ms);
    622     if (pr < 0) {
    623       if (errno == EINTR) continue;
    624       log_error("netlink: poll failed: %s", strerror(errno));
    625       return -1;
    626     }
    627     if (!pr) continue;
    628 
    629     if (nfds > 1) ipc_dispatch(&pfds[1], nfds - 1);
    630     if (!(pfds[0].revents & POLLIN)) continue;
    631 
    632     for (;;) {
    633       struct nlmsghdr *h;
    634       struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
    635       struct msghdr msg;
    636       struct sockaddr_nl nladdr;
    637       ssize_t len;
    638 
    639       memset(&msg, 0, sizeof(msg));
    640       msg.msg_name = &nladdr;
    641       msg.msg_namelen = sizeof(nladdr);
    642       msg.msg_iov = &iov;
    643       msg.msg_iovlen = 1;
    644 
    645       len = recvmsg(fd, &msg, 0);
    646       if (len < 0) {
    647         if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) break;
    648         if (errno == ENOBUFS) {
    649           // Expected under load, not exceptional: re-converge from dumps.
    650           log_warn("netlink: ENOBUFS, resyncing");
    651           if (nl_resync(fd) != 0) {
    652             log_warn("netlink: ENOBUFS resync failed, retrying once");
    653             nl_resync(fd);
    654           }
    655           clock_gettime(CLOCK_MONOTONIC, &last_resync);
    656           break;
    657         }
    658         log_error("netlink: recvmsg failed: %s", strerror(errno));
    659         return -1;
    660       }
    661       if (len == 0) break;
    662       if (msg.msg_flags & MSG_TRUNC) {
    663         log_warn("netlink: message truncated, resyncing");
    664         if (nl_resync(fd) != 0) nl_resync(fd);
    665         clock_gettime(CLOCK_MONOTONIC, &last_resync);
    666         break;
    667       }
    668 
    669       h = (struct nlmsghdr *)buf;
    670       while (NLMSG_OK(h, (int)len)) {
    671         if (h->nlmsg_type == NLMSG_ERROR) {
    672           struct nlmsgerr *e = NLMSG_DATA(h);
    673           if (e->error == -ENOBUFS) {
    674             log_warn("netlink: ENOBUFS via NLMSG_ERROR, resyncing");
    675             if (nl_resync(fd) != 0) nl_resync(fd);
    676             clock_gettime(CLOCK_MONOTONIC, &last_resync);
    677           } else if (e->error) {
    678             log_error("netlink: NLMSG_ERROR: %s", strerror(-e->error));
    679           }
    680         } else if (h->nlmsg_type != NLMSG_DONE) {
    681           handle_nlmsg(h);
    682         }
    683         h = NLMSG_NEXT(h, len);
    684       }
    685       // Non-blocking drain: keep reading until EAGAIN.
    686       if ((size_t)len < sizeof(buf)) break;
    687     }
    688   }
    689   return 0;
    690 }