linkd

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

rtnl.c (19093B)


      1 #define _GNU_SOURCE
      2 #include <arpa/inet.h>
      3 #include <errno.h>
      4 #include <linux/if_link.h>
      5 #include <linux/netlink.h>
      6 #include <linux/rtnetlink.h>
      7 #include <net/if.h>
      8 #include <poll.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <sys/socket.h>
     13 #include <unistd.h>
     14 
     15 #include "rxi/log.h"
     16 
     17 #include "rtnl.h"
     18 
     19 #define NL_BUFSZ 8192
     20 
     21 static int rtnl_open(void) {
     22   int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
     23   if (fd < 0) {
     24     log_error("rtnl: socket failed: %s", strerror(errno));
     25     return -1;
     26   }
     27   struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
     28   if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
     29     log_error("rtnl: bind failed: %s", strerror(errno));
     30     close(fd);
     31     return -1;
     32   }
     33   return fd;
     34 }
     35 
     36 static int rtnl_talk(struct nlmsghdr *nh) {
     37   int fd = rtnl_open();
     38   if (fd < 0) return -1;
     39 
     40   struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
     41   struct iovec iov = { .iov_base = nh, .iov_len = nh->nlmsg_len };
     42   struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = &iov, .msg_iovlen = 1 };
     43 
     44   if (sendmsg(fd, &msg, 0) < 0) {
     45     log_error("rtnl: sendmsg failed: %s", strerror(errno));
     46     close(fd);
     47     return -1;
     48   }
     49 
     50   // Wait for ACK (NLMSGERR with error 0) or NLMSG_DONE
     51   char buf[NL_BUFSZ];
     52   struct pollfd pfd = { .fd = fd, .events = POLLIN };
     53   if (poll(&pfd, 1, 5000) <= 0) {
     54     log_error("rtnl: poll timeout");
     55     close(fd);
     56     return -1;
     57   }
     58   ssize_t len = recv(fd, buf, sizeof(buf), 0);
     59   if (len < 0) {
     60     log_error("rtnl: recv failed: %s", strerror(errno));
     61     close(fd);
     62     return -1;
     63   }
     64   struct nlmsghdr *h = (struct nlmsghdr*)buf;
     65   while (NLMSG_OK(h, len)) {
     66     if (h->nlmsg_type == NLMSG_ERROR) {
     67       struct nlmsgerr *err = NLMSG_DATA(h);
     68       if (err->error == 0) {
     69         close(fd);
     70         return 0; // ACK
     71       } else {
     72         // EEXIST for addr add is not fatal for ifup idempotence -- treat as success
     73         if (err->error == -EEXIST) {
     74           log_debug("rtnl: NLMSGERR EEXIST (already exists, treating as success)");
     75           close(fd);
     76           return 0;
     77         }
     78         log_error("rtnl: NLMSGERR %s", strerror(-err->error));
     79         close(fd);
     80         return -1;
     81       }
     82     }
     83     h = NLMSG_NEXT(h, len);
     84   }
     85   close(fd);
     86   return 0;
     87 }
     88 
     89 static int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) {
     90   int len = RTA_LENGTH(alen);
     91   struct rtattr *rta;
     92   if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > (unsigned)maxlen) {
     93     log_error("rtnl: addattr_l overflow");
     94     return -1;
     95   }
     96   rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
     97   rta->rta_type = type;
     98   rta->rta_len = len;
     99   if (alen) memcpy(RTA_DATA(rta), data, alen);
    100   n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
    101   return 0;
    102 }
    103 
    104 // Returns the nest's byte offset for addattr_nested_end(), or -1 on overflow.
    105 static int addattr_nested_start(struct nlmsghdr *n, int maxlen, int type) {
    106   struct rtattr *rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
    107   if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(RTA_LENGTH(0)) > (unsigned)maxlen) {
    108     log_error("rtnl: addattr_nested_start overflow");
    109     return -1;
    110   }
    111   rta->rta_type = type;
    112   rta->rta_len = RTA_LENGTH(0);
    113   n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(sizeof(*rta));
    114   return (char*)rta - (char*)n;
    115 }
    116 
    117 static void addattr_nested_end(struct nlmsghdr *n, int start) {
    118   struct rtattr *rta = (struct rtattr*)((char*)n + start);
    119   rta->rta_len = (char*)n + NLMSG_ALIGN(n->nlmsg_len) - (char*)rta;
    120 }
    121 
    122 int rtnl_link_up(const char *ifname) {
    123   unsigned ifindex = if_nametoindex(ifname);
    124   if (!ifindex) {
    125     log_error("rtnl: link_up %s: if_nametoindex failed: %s", ifname, strerror(errno));
    126     return -1;
    127   }
    128   char buf[NL_BUFSZ];
    129   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    130   struct ifinfomsg *ifi;
    131   memset(buf, 0, sizeof(buf));
    132   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    133   nh->nlmsg_type = RTM_NEWLINK;
    134   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    135   nh->nlmsg_seq = 0;
    136   ifi = NLMSG_DATA(nh);
    137   ifi->ifi_family = AF_UNSPEC;
    138   ifi->ifi_index = ifindex;
    139   ifi->ifi_change = IFF_UP;
    140   ifi->ifi_flags = IFF_UP;
    141   return rtnl_talk(nh);
    142 }
    143 
    144 int rtnl_link_down(const char *ifname) {
    145   unsigned ifindex = if_nametoindex(ifname);
    146   if (!ifindex) {
    147     log_error("rtnl: link_down %s: if_nametoindex failed", ifname);
    148     return -1;
    149   }
    150   char buf[NL_BUFSZ];
    151   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    152   struct ifinfomsg *ifi;
    153   memset(buf, 0, sizeof(buf));
    154   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    155   nh->nlmsg_type = RTM_NEWLINK;
    156   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    157   ifi = NLMSG_DATA(nh);
    158   ifi->ifi_family = AF_UNSPEC;
    159   ifi->ifi_index = ifindex;
    160   ifi->ifi_change = IFF_UP;
    161   ifi->ifi_flags = 0;
    162   return rtnl_talk(nh);
    163 }
    164 
    165 int rtnl_link_set_mtu(const char *ifname, int mtu) {
    166   unsigned ifindex = if_nametoindex(ifname);
    167   if (!ifindex) return -1;
    168   char buf[NL_BUFSZ];
    169   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    170   struct ifinfomsg *ifi;
    171   memset(buf, 0, sizeof(buf));
    172   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    173   nh->nlmsg_type = RTM_NEWLINK;
    174   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    175   ifi = NLMSG_DATA(nh);
    176   ifi->ifi_family = AF_UNSPEC;
    177   ifi->ifi_index = ifindex;
    178   addattr_l(nh, sizeof(buf), IFLA_MTU, &mtu, sizeof(mtu));
    179   return rtnl_talk(nh);
    180 }
    181 
    182 int rtnl_link_set_hwaddr(const char *ifname, const char *hwaddr) {
    183   unsigned ifindex = if_nametoindex(ifname);
    184   if (!ifindex) return -1;
    185   unsigned char mac[6];
    186   if (sscanf(hwaddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) {
    187     log_error("rtnl: invalid hwaddr %s", hwaddr);
    188     return -1;
    189   }
    190   char buf[NL_BUFSZ];
    191   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    192   struct ifinfomsg *ifi;
    193   memset(buf, 0, sizeof(buf));
    194   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    195   nh->nlmsg_type = RTM_NEWLINK;
    196   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    197   ifi = NLMSG_DATA(nh);
    198   ifi->ifi_family = AF_UNSPEC;
    199   ifi->ifi_index = ifindex;
    200   addattr_l(nh, sizeof(buf), IFLA_ADDRESS, mac, 6);
    201   return rtnl_talk(nh);
    202 }
    203 
    204 // Parse "10.0.0.1/24" or "10.0.0.1" + netmask, returns family, addr, prefixlen
    205 static int parse_addr(const char *address, const char *netmask, int *family, unsigned char *addr, int *prefixlen) {
    206   char ip[128];
    207   char *slash = strchr(address, '/');
    208   if (slash) {
    209     size_t iplen = slash - address;
    210     if (iplen >= sizeof(ip)) return -1;
    211     memcpy(ip, address, iplen);
    212     ip[iplen] = '\0';
    213     *prefixlen = atoi(slash+1);
    214   } else {
    215     strncpy(ip, address, sizeof(ip)-1);
    216     ip[sizeof(ip)-1] = '\0';
    217     if (netmask) {
    218       // netmask like 255.255.255.0 -> prefixlen
    219       struct in_addr nm;
    220       if (inet_pton(AF_INET, netmask, &nm) != 1) return -1;
    221       uint32_t m = ntohl(nm.s_addr);
    222       int len = 0;
    223       while (m & 0x80000000) { len++; m <<= 1; }
    224       *prefixlen = len;
    225     } else {
    226       *prefixlen = -1;
    227     }
    228   }
    229   // Try v4 then v6
    230   struct in_addr a4;
    231   struct in6_addr a6;
    232   if (inet_pton(AF_INET, ip, &a4) == 1) {
    233     *family = AF_INET;
    234     memcpy(addr, &a4, 4);
    235     if (*prefixlen < 0) *prefixlen = 32;
    236     return 0;
    237   }
    238   if (inet_pton(AF_INET6, ip, &a6) == 1) {
    239     *family = AF_INET6;
    240     memcpy(addr, &a6, 16);
    241     if (*prefixlen < 0) *prefixlen = 128;
    242     return 0;
    243   }
    244   return -1;
    245 }
    246 
    247 int rtnl_addr_add(const char *ifname, const char *address, const char *netmask) {
    248   unsigned ifindex = if_nametoindex(ifname);
    249   if (!ifindex) {
    250     log_error("rtnl: addr_add %s: ifindex failed", ifname);
    251     return -1;
    252   }
    253   int family, prefixlen;
    254   unsigned char addr[16];
    255   if (parse_addr(address, netmask, &family, addr, &prefixlen) != 0) {
    256     log_error("rtnl: addr_add %s: parse %s failed", ifname, address);
    257     return -1;
    258   }
    259   char buf[NL_BUFSZ];
    260   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    261   struct ifaddrmsg *ifa;
    262   memset(buf, 0, sizeof(buf));
    263   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifa));
    264   nh->nlmsg_type = RTM_NEWADDR;
    265   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
    266   ifa = NLMSG_DATA(nh);
    267   ifa->ifa_family = family;
    268   ifa->ifa_prefixlen = prefixlen;
    269   ifa->ifa_flags = 0;
    270   ifa->ifa_scope = 0;
    271   ifa->ifa_index = ifindex;
    272   int alen = (family == AF_INET) ? 4 : 16;
    273   addattr_l(nh, sizeof(buf), IFA_LOCAL, addr, alen);
    274   addattr_l(nh, sizeof(buf), IFA_ADDRESS, addr, alen);
    275   int rc = rtnl_talk(nh);
    276   if (rc != 0) {
    277     // If EEXIST, treat as success (idempotent)
    278     log_debug("rtnl: addr_add %s %s/%d rc=%d", ifname, address, prefixlen, rc);
    279   }
    280   return rc;
    281 }
    282 
    283 int rtnl_addr_del(const char *ifname, const char *address, const char *netmask) {
    284   unsigned ifindex = if_nametoindex(ifname);
    285   if (!ifindex) return -1;
    286   int family, prefixlen;
    287   unsigned char addr[16];
    288   if (parse_addr(address, netmask, &family, addr, &prefixlen) != 0) return -1;
    289   char buf[NL_BUFSZ];
    290   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    291   struct ifaddrmsg *ifa;
    292   memset(buf, 0, sizeof(buf));
    293   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifa));
    294   nh->nlmsg_type = RTM_DELADDR;
    295   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    296   ifa = NLMSG_DATA(nh);
    297   ifa->ifa_family = family;
    298   ifa->ifa_prefixlen = prefixlen;
    299   ifa->ifa_index = ifindex;
    300   int alen = (family == AF_INET) ? 4 : 16;
    301   addattr_l(nh, sizeof(buf), IFA_LOCAL, addr, alen);
    302   return rtnl_talk(nh);
    303 }
    304 
    305 int rtnl_route_add_default(const char *gateway, const char *ifname) {
    306   int family;
    307   unsigned char gw[16];
    308   int prefixlen;
    309   // gateway may be without prefix, default to /32 or /128
    310   if (parse_addr(gateway, NULL, &family, gw, &prefixlen) != 0) return -1;
    311   // For default route, we ignore prefixlen and use 0
    312   char buf[NL_BUFSZ];
    313   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    314   struct rtmsg *rtm;
    315   memset(buf, 0, sizeof(buf));
    316   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*rtm));
    317   nh->nlmsg_type = RTM_NEWROUTE;
    318   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
    319   rtm = NLMSG_DATA(nh);
    320   rtm->rtm_family = family;
    321   rtm->rtm_dst_len = 0;
    322   rtm->rtm_src_len = 0;
    323   rtm->rtm_tos = 0;
    324   rtm->rtm_table = RT_TABLE_MAIN;
    325   rtm->rtm_protocol = RTPROT_BOOT;
    326   rtm->rtm_scope = RT_SCOPE_UNIVERSE;
    327   rtm->rtm_type = RTN_UNICAST;
    328   int alen = (family == AF_INET) ? 4 : 16;
    329   addattr_l(nh, sizeof(buf), RTA_GATEWAY, gw, alen);
    330   if (ifname) {
    331     unsigned ifindex = if_nametoindex(ifname);
    332     if (ifindex) addattr_l(nh, sizeof(buf), RTA_OIF, &ifindex, sizeof(ifindex));
    333   }
    334   return rtnl_talk(nh);
    335 }
    336 
    337 int rtnl_route_del_default(const char *gateway, const char *ifname) {
    338   int family;
    339   unsigned char gw[16];
    340   int prefixlen;
    341   if (parse_addr(gateway, NULL, &family, gw, &prefixlen) != 0) return -1;
    342   char buf[NL_BUFSZ];
    343   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    344   struct rtmsg *rtm;
    345   memset(buf, 0, sizeof(buf));
    346   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*rtm));
    347   nh->nlmsg_type = RTM_DELROUTE;
    348   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    349   rtm = NLMSG_DATA(nh);
    350   rtm->rtm_family = family;
    351   rtm->rtm_dst_len = 0;
    352   rtm->rtm_table = RT_TABLE_MAIN;
    353   int alen = (family == AF_INET) ? 4 : 16;
    354   addattr_l(nh, sizeof(buf), RTA_GATEWAY, gw, alen);
    355   if (ifname) {
    356     unsigned ifindex = if_nametoindex(ifname);
    357     if (ifindex) addattr_l(nh, sizeof(buf), RTA_OIF, &ifindex, sizeof(ifindex));
    358   }
    359   return rtnl_talk(nh);
    360 }
    361 
    362 int rtnl_vlan_create(const char *name, const char *rawdev, int vlan_id) {
    363   unsigned rawidx = if_nametoindex(rawdev);
    364   if (!rawidx) {
    365     log_error("rtnl: vlan %s: rawdev %s not found", name, rawdev);
    366     return -1;
    367   }
    368   // Check if already exists
    369   if (if_nametoindex(name) != 0) {
    370     log_info("rtnl: vlan %s already exists", name);
    371     return 0;
    372   }
    373   char buf[NL_BUFSZ];
    374   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    375   struct ifinfomsg *ifi;
    376   memset(buf, 0, sizeof(buf));
    377   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    378   nh->nlmsg_type = RTM_NEWLINK;
    379   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
    380   ifi = NLMSG_DATA(nh);
    381   ifi->ifi_family = AF_UNSPEC;
    382   addattr_l(nh, sizeof(buf), IFLA_IFNAME, name, strlen(name)+1);
    383   addattr_l(nh, sizeof(buf), IFLA_LINK, &rawidx, sizeof(rawidx));
    384   int nest = addattr_nested_start(nh, sizeof(buf), IFLA_LINKINFO);
    385   if (nest < 0) return -1;
    386   addattr_l(nh, sizeof(buf), IFLA_INFO_KIND, "vlan", 5);
    387   int nest2 = addattr_nested_start(nh, sizeof(buf), IFLA_INFO_DATA);
    388   if (nest2 < 0) return -1;
    389   addattr_l(nh, sizeof(buf), IFLA_VLAN_ID, &vlan_id, sizeof(vlan_id));
    390   addattr_nested_end(nh, nest2);
    391   addattr_nested_end(nh, nest);
    392   return rtnl_talk(nh);
    393 }
    394 
    395 int rtnl_bridge_create(const char *name) {
    396   if (if_nametoindex(name) != 0) {
    397     log_info("rtnl: bridge %s already exists", name);
    398     return 0;
    399   }
    400   char buf[NL_BUFSZ];
    401   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    402   struct ifinfomsg *ifi;
    403   memset(buf, 0, sizeof(buf));
    404   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    405   nh->nlmsg_type = RTM_NEWLINK;
    406   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
    407   ifi = NLMSG_DATA(nh);
    408   ifi->ifi_family = AF_UNSPEC;
    409   addattr_l(nh, sizeof(buf), IFLA_IFNAME, name, strlen(name)+1);
    410   int nest = addattr_nested_start(nh, sizeof(buf), IFLA_LINKINFO);
    411   if (nest < 0) return -1;
    412   addattr_l(nh, sizeof(buf), IFLA_INFO_KIND, "bridge", 7);
    413   addattr_nested_end(nh, nest);
    414   return rtnl_talk(nh);
    415 }
    416 
    417 // Create a VRF device bound to a routing table.
    418 //
    419 // A VRF is an L3 master device: enslaved interfaces have their routes looked
    420 // up in IFLA_VRF_TABLE instead of main. The table id is not decoration -- it
    421 // IS the separation, which is why it is mandatory here rather than defaulted.
    422 // Requires CONFIG_NET_VRF (shipped as a module, see the kernel templates).
    423 int rtnl_vrf_create(const char *name, uint32_t table) {
    424   if (!name || !table) return -1;
    425   if (if_nametoindex(name) != 0) {
    426     log_info("rtnl: vrf %s already exists", name);
    427     return 0;
    428   }
    429   char buf[NL_BUFSZ];
    430   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    431   struct ifinfomsg *ifi;
    432   memset(buf, 0, sizeof(buf));
    433   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    434   nh->nlmsg_type = RTM_NEWLINK;
    435   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
    436   ifi = NLMSG_DATA(nh);
    437   ifi->ifi_family = AF_UNSPEC;
    438   addattr_l(nh, sizeof(buf), IFLA_IFNAME, name, strlen(name)+1);
    439   int nest = addattr_nested_start(nh, sizeof(buf), IFLA_LINKINFO);
    440   if (nest < 0) return -1;
    441   addattr_l(nh, sizeof(buf), IFLA_INFO_KIND, "vrf", 4);
    442   int nest2 = addattr_nested_start(nh, sizeof(buf), IFLA_INFO_DATA);
    443   if (nest2 < 0) return -1;
    444   addattr_l(nh, sizeof(buf), IFLA_VRF_TABLE, &table, sizeof(table));
    445   addattr_nested_end(nh, nest2);
    446   addattr_nested_end(nh, nest);
    447   log_info("rtnl: vrf %s table %u", name, table);
    448   return rtnl_talk(nh);
    449 }
    450 
    451 // Enslave an interface to a VRF. Same IFLA_MASTER mechanism as bridge
    452 // enslavement -- the kernel distinguishes them by the master's kind.
    453 int rtnl_vrf_add_port(const char *vrf, const char *port) {
    454   unsigned vidx = if_nametoindex(vrf);
    455   unsigned pidx = if_nametoindex(port);
    456   if (!vidx || !pidx) {
    457     log_error("rtnl: vrf_add_port %s -> %s: ifindex not found (%u, %u)", port, vrf, pidx, vidx);
    458     return -1;
    459   }
    460   char buf[NL_BUFSZ];
    461   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    462   struct ifinfomsg *ifi;
    463   memset(buf, 0, sizeof(buf));
    464   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    465   nh->nlmsg_type = RTM_NEWLINK;
    466   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    467   ifi = NLMSG_DATA(nh);
    468   ifi->ifi_family = AF_UNSPEC;
    469   ifi->ifi_index = pidx;
    470   addattr_l(nh, sizeof(buf), IFLA_MASTER, &vidx, sizeof(vidx));
    471   log_info("rtnl: vrf %s enslave %s", vrf, port);
    472   return rtnl_talk(nh);
    473 }
    474 
    475 int rtnl_bridge_set_stp(const char *name, bool on) {
    476   unsigned ifindex = if_nametoindex(name);
    477   if (!ifindex) return -1;
    478   char buf[NL_BUFSZ];
    479   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    480   struct ifinfomsg *ifi;
    481   memset(buf, 0, sizeof(buf));
    482   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    483   nh->nlmsg_type = RTM_NEWLINK;
    484   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    485   ifi = NLMSG_DATA(nh);
    486   ifi->ifi_family = AF_UNSPEC;
    487   ifi->ifi_index = ifindex;
    488   int nest = addattr_nested_start(nh, sizeof(buf), IFLA_LINKINFO);
    489   if (nest < 0) return -1;
    490   addattr_l(nh, sizeof(buf), IFLA_INFO_KIND, "bridge", 7);
    491   int nest2 = addattr_nested_start(nh, sizeof(buf), IFLA_INFO_DATA);
    492   if (nest2 < 0) return -1;
    493   uint8_t stp = on ? 1 : 0;
    494   addattr_l(nh, sizeof(buf), IFLA_BR_STP_STATE, &stp, sizeof(stp));
    495   addattr_nested_end(nh, nest2);
    496   addattr_nested_end(nh, nest);
    497   return rtnl_talk(nh);
    498 }
    499 
    500 int rtnl_bridge_set_vlan_aware(const char *name, bool on) {
    501   unsigned ifindex = if_nametoindex(name);
    502   if (!ifindex) return -1;
    503   char buf[NL_BUFSZ];
    504   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    505   struct ifinfomsg *ifi;
    506   memset(buf, 0, sizeof(buf));
    507   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    508   nh->nlmsg_type = RTM_NEWLINK;
    509   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    510   ifi = NLMSG_DATA(nh);
    511   ifi->ifi_family = AF_UNSPEC;
    512   ifi->ifi_index = ifindex;
    513   int nest = addattr_nested_start(nh, sizeof(buf), IFLA_LINKINFO);
    514   if (nest < 0) return -1;
    515   addattr_l(nh, sizeof(buf), IFLA_INFO_KIND, "bridge", 7);
    516   int nest2 = addattr_nested_start(nh, sizeof(buf), IFLA_INFO_DATA);
    517   if (nest2 < 0) return -1;
    518   uint8_t vlan = on ? 1 : 0;
    519   addattr_l(nh, sizeof(buf), IFLA_BR_VLAN_FILTERING, &vlan, sizeof(vlan));
    520   addattr_nested_end(nh, nest2);
    521   addattr_nested_end(nh, nest);
    522   return rtnl_talk(nh);
    523 }
    524 
    525 int rtnl_bridge_add_port(const char *br, const char *port) {
    526   unsigned bridx = if_nametoindex(br);
    527   unsigned pidx = if_nametoindex(port);
    528   if (!bridx || !pidx) {
    529     log_error("rtnl: bridge_add_port %s -> %s: ifindex not found (%u, %u)", port, br, pidx, bridx);
    530     return -1;
    531   }
    532   char buf[NL_BUFSZ];
    533   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    534   struct ifinfomsg *ifi;
    535   memset(buf, 0, sizeof(buf));
    536   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    537   nh->nlmsg_type = RTM_NEWLINK;
    538   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    539   ifi = NLMSG_DATA(nh);
    540   ifi->ifi_family = AF_UNSPEC;
    541   ifi->ifi_index = pidx;
    542   addattr_l(nh, sizeof(buf), IFLA_MASTER, &bridx, sizeof(bridx));
    543   return rtnl_talk(nh);
    544 }
    545 
    546 int rtnl_bridge_del_port(const char *br, const char *port) {
    547   (void)br;
    548   unsigned pidx = if_nametoindex(port);
    549   if (!pidx) return -1;
    550   char buf[NL_BUFSZ];
    551   struct nlmsghdr *nh = (struct nlmsghdr*)buf;
    552   struct ifinfomsg *ifi;
    553   memset(buf, 0, sizeof(buf));
    554   nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
    555   nh->nlmsg_type = RTM_NEWLINK;
    556   nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    557   ifi = NLMSG_DATA(nh);
    558   ifi->ifi_family = AF_UNSPEC;
    559   ifi->ifi_index = pidx;
    560   // No master = 0 removes from bridge? Actually need to set master 0 and use NLM_F?
    561   // For del, we set master to 0 and use RTM_NEWLINK with master 0? The kernel handles master 0 as no bridge.
    562   // Alternative is to use RTM_DELLINK? No, enslavement is via master.
    563   // Setting master to 0 and sending NEWLINK should release.
    564   // But to be safe, we send with master 0 and hope.
    565   // If that doesn't work, we can try to use `ip link set dev port nomaster`
    566   uint32_t zero = 0;
    567   addattr_l(nh, sizeof(buf), IFLA_MASTER, &zero, sizeof(zero));
    568   return rtnl_talk(nh);
    569 }