linkd

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

netlink.c (21398B)


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