plugins: netlink: redesign to handle ENOBUFS with kernel resync

This is a major redesign of the netlink plugin to be able to handle
ENOBUFS¹ properly.  Pending verification, this change replaces the patch
to increase socket buffer size, which in real life turned out to be
insufficient.

When nl_callback() calls recv() and it fails with ENOBUFS, we consider
our cache of the kernel state invalid and thus:

  1. deassert all net/ conditions
  2. open a new (temporary) netlink socket
  3. send RTM_GETLINK  and re-assert all interfaces using nl_link()
  4. send RTM_GETROUTE and re-assert all routes with nl_route()

Like before, the kernel will not send us a RTM_DELROUTE when it removes
the default route, so we still have to track this ourselves.  This patch
also refactors that functionality to only resync routes when the ifindex
associated previosly with the default route goes down or is removed.

The previous change that added nl_default() to recheck, has been dropped
to instead reuse the standard nl_route() callback.
___
¹ see netlink(7) for details.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2021-05-07 13:06:47 +02:00
parent add55cfc2a
commit 910bb13711
3 changed files with 199 additions and 103 deletions
+142 -92
View File
@@ -30,8 +30,13 @@
#include "cond.h"
#include "helpers.h"
#include "plugin.h"
#include "service.h"
static int ifdown = 0;
#define NL_BUFSZ 8192
static int nl_defidx;
static int nl_ifdown;
static char *nl_buf;
static int nlmsg_validate(struct nlmsghdr *nh, size_t len)
@@ -109,41 +114,14 @@ static void nl_route(struct nlmsghdr *nlmsg, ssize_t len)
_d("Got gw %s dst/len %s/%d ifindex %d", gaddr, daddr, plen, idx);
if ((!dst && !plen) && (gw || idx)) {
if (nlmsg->nlmsg_type == RTM_DELROUTE)
if (nlmsg->nlmsg_type == RTM_DELROUTE) {
cond_clear("net/route/default");
else
nl_defidx = 0;
} else {
cond_set("net/route/default");
}
}
static int nl_default(struct nlmsghdr *nlh, struct in_addr *dst, struct in_addr *gw)
{
struct in_addr nil = { 0 };
struct rtattr *a;
struct rtmsg *r;
int len;
r = (struct rtmsg *)NLMSG_DATA(nlh);
if ((r->rtm_family != AF_INET) || (r->rtm_table != RT_TABLE_MAIN))
return -1;
len = RTM_PAYLOAD(nlh);
for (a = RTM_RTA(r); RTA_OK(a, len); a = RTA_NEXT(a, len)) {
switch (a->rta_type) {
case RTA_GATEWAY:
memcpy(gw, RTA_DATA(a), sizeof(*gw));
break;
case RTA_DST:
memcpy(dst, RTA_DATA(a), sizeof(*dst));
break;
nl_defidx = idx;
}
}
if (!memcmp(dst, &nil, sizeof(nil)) && memcmp(gw, &nil, sizeof(nil)))
return 0;
return 1;
}
static void net_cond_set(char *ifname, char *cond, int set)
@@ -177,6 +155,19 @@ static int validate_ifname(const char *ifname)
return 0;
}
/*
* Check if this interface was associated with the default route
* previously, or if it's been removed. If so, trigger a recheck
* of the system default route.
*/
static void nl_check_default(char *ifname)
{
int idx = (int)if_nametoindex(ifname);
if ((nl_defidx > 0 && nl_defidx == idx) || (idx == 0 && errno == ENODEV))
nl_ifdown = 1;
}
static void nl_link(struct nlmsghdr *nlmsg, ssize_t len)
{
char ifname[IFNAMSIZ + 1];
@@ -218,7 +209,7 @@ static void nl_link(struct nlmsghdr *nlmsg, ssize_t len)
net_cond_set(ifname, "up", i->ifi_flags & IFF_UP);
net_cond_set(ifname, "running", i->ifi_flags & IFF_RUNNING);
if (!(i->ifi_flags & IFF_UP) || !(i->ifi_flags & IFF_RUNNING))
ifdown = 1;
nl_check_default(ifname);
break;
case RTM_DELLINK:
@@ -227,7 +218,7 @@ static void nl_link(struct nlmsghdr *nlmsg, ssize_t len)
net_cond_set(ifname, "exist", 0);
net_cond_set(ifname, "up", 0);
net_cond_set(ifname, "running", 0);
ifdown = 1;
nl_check_default(ifname);
break;
case RTM_NEWADDR:
@@ -245,76 +236,128 @@ static void nl_link(struct nlmsghdr *nlmsg, ssize_t len)
}
}
static void nl_callback(void *arg, int sd, int events)
static int nl_parse(struct nlmsghdr *nh, ssize_t len)
{
static char buf[4096];
struct nlmsghdr *nh;
ssize_t len;
for (; !nlmsg_validate(nh, len); nh = NLMSG_NEXT(nh, len)) {
// _d("netlink message, type %d ...", nh->nlmsg_type);
switch (nh->nlmsg_type) {
case RTM_NEWROUTE:
case RTM_DELROUTE:
nl_route(nh, len);
break;
memset(buf, 0, sizeof(buf));
len = recv(sd, buf, sizeof(buf), 0);
if (len < 0) {
if (errno != EINTR) /* Signal */
_pe("recv()");
case RTM_NEWLINK:
case RTM_DELLINK:
nl_link(nh, len);
break;
default:
_w("unhandled netlink message, type %d", nh->nlmsg_type);
break;
}
}
return 0;
}
static int nl_resync_act(int sd, unsigned int seq, int type)
{
struct nlmsghdr *nh;
nh = (struct nlmsghdr *)nl_buf;
nh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nh->nlmsg_type = type;
nh->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
nh->nlmsg_seq = seq;
nh->nlmsg_pid = 1;
if (send(sd, nh, nh->nlmsg_len, 0) < 0)
return 1;
return nl_parse(nh, recv(sd, nl_buf, NL_BUFSZ, 0));
}
static void nl_resync_routes(int sd, unsigned int seq)
{
if (nl_resync_act(sd, seq, RTM_GETROUTE))
_pe("Failed netlink route request");
}
static void nl_resync_ifaces(int sd, unsigned int seq)
{
if (nl_resync_act(sd, seq, RTM_GETLINK))
_pe("Failed netlink link request");
}
/*
* We've potentially lost netlink events, let's resync with kernel.
*/
static void nl_resync(int all)
{
unsigned int seq = 0;
int sd;
sd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (sd < 0) {
_pe("netlink socket");
return;
}
/* check for interface changes -> loss of default route */
ifdown = 0;
if (all) {
/* this doesn't update condtions, and thus does not stop services */
cond_deassert("net/");
for (nh = (struct nlmsghdr *)buf; !nlmsg_validate(nh, len); nh = NLMSG_NEXT(nh, len)) {
//_d("Well formed netlink message received. type %d ...", nh->nlmsg_type);
if (nh->nlmsg_type == RTM_NEWROUTE || nh->nlmsg_type == RTM_DELROUTE)
nl_route(nh, len);
else
nl_link(nh, len);
nl_resync_ifaces(sd, seq++);
nl_resync_routes(sd, seq++);
/* delayed update after we've corrected things */
service_step_all(SVC_TYPE_ANY);
} else
nl_resync_routes(sd, seq++);
close(sd);
}
static void nl_callback(void *arg, int sd, int events)
{
ssize_t len;
len = recv(sd, nl_buf, NL_BUFSZ, 0);
if (len < 0) {
switch (errno) {
case EINTR: /* Signal */
break;
case ENOBUFS: /* netlink(7) */
_w("busy system, resynchronizing with kernel.");
nl_resync(1);
break;
default:
_pe("recv()");
break;
}
return;
}
nl_parse((struct nlmsghdr *)nl_buf, len);
/*
* Linux doesn't send route changes, when interfaces go down, so
* Linux doesn't send route changes when interfaces go down, so
* we need to check ourselves, e.g. for loss of default route.
*/
if (ifdown) {
unsigned int seq = 0;
int found = 0;
sd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (sd < 0) {
_pe("netlink socket");
return;
if (nl_ifdown) {
_d("interface down, checking default route.");
if (nl_defidx > 0) {
nl_defidx = 0;
nl_resync(0);
if (nl_defidx <= 0) {
cond_clear("net/route/default");
nl_defidx = 0;
}
}
memset(buf, 0, sizeof(buf));
nh = (struct nlmsghdr *)buf;
nh->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nh->nlmsg_type = RTM_GETROUTE;
nh->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
nh->nlmsg_seq = seq++;
nh->nlmsg_pid = 1;
if (send(sd, nh, nh->nlmsg_len, 0) < 0) {
_pe("Failed netlink route request");
close(sd);
return;
}
len = recv(sd, buf, sizeof(buf), 0);
close(sd);
for (; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
struct in_addr dst = { 0 };
struct in_addr gw = { 0 };
if (nl_default(nh, &dst, &gw))
continue;
_d("default via %s\n", inet_ntoa(gw));
found = 1;
}
/* We already get notification when adding default rt */
if (!found)
cond_clear("net/route/default");
nl_ifdown = 0;
}
}
@@ -355,7 +398,7 @@ PLUGIN_INIT(plugin_init)
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_LINK; // | RTMGRP_NOTIFY | RTMGRP_IPV4_IFADDR;
sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_LINK;
sa.nl_pid = getpid();
if (bind(sd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
@@ -364,6 +407,13 @@ PLUGIN_INIT(plugin_init)
return;
}
nl_buf = malloc(NL_BUFSZ);
if (!nl_buf) {
_pe("malloc()");
close(sd);
return;
}
plugin.io.fd = sd;
plugin_register(&plugin);
}
+51 -11
View File
@@ -178,13 +178,22 @@ void cond_update(const char *name)
}
}
void cond_set(const char *name)
int cond_set_noupdate(const char *name)
{
_d("%s", name);
if (string_compare(name, "nop"))
return;
return 1;
if (!cond_set_path(cond_path(name), COND_ON))
return 1;
return 0;
}
void cond_set(const char *name)
{
_d("%s", name);
if (cond_set_noupdate(name))
return;
cond_update(name);
@@ -208,13 +217,22 @@ void cond_set_oneshot(const char *name)
cond_update(name);
}
void cond_clear(const char *name)
int cond_clear_noupdate(const char *name)
{
_d("%s", name);
if (string_compare(name, "nop"))
return;
return 1;
if (!cond_set_path(cond_path(name), COND_OFF))
return 1;
return 0;
}
void cond_clear(const char *name)
{
_d("%s", name);
if (cond_clear_noupdate(name))
return;
cond_update(name);
@@ -227,7 +245,7 @@ void cond_reload(void)
cond_bump_reconf();
}
static int reassert(const char *fpath, const struct stat *sb, int tflg, struct FTW *ftw)
static int do_assert(const char *fpath, const struct stat *sb, int tflg, struct FTW *ftw, int set)
{
char *nm;
@@ -239,25 +257,47 @@ static int reassert(const char *fpath, const struct stat *sb, int tflg, struct F
nm = strstr((char *)fpath, COND_BASE);
if (!nm) {
_e("Incorrect condtion path %s, cannot reassert", fpath);
_e("Incorrect condtion path %s, cannot %sassert", fpath, set ? "re" : "de");
return 1;
}
nm += strlen(COND_BASE);
_d("Reasserting %s => %s", fpath, nm);
cond_set(nm);
_d("%sasserting %s => %s", set ? "Re" : "De", fpath, nm);
if (set)
cond_set(nm);
else
cond_clear_noupdate(nm); /* important, see netlink plugin! */
return 0;
}
static int reassert(const char *fpath, const struct stat *sb, int tflg, struct FTW *ftw)
{
return do_assert(fpath, sb, tflg, ftw, 1);
}
static int deassert(const char *fpath, const struct stat *sb, int tflg, struct FTW *ftw)
{
return do_assert(fpath, sb, tflg, ftw, 0);
}
/*
* Used only by netlink plugin atm.
* type: is a one of pid/, net/, etc.
*/
void cond_reassert(const char *type)
void cond_reassert(const char *pat)
{
_d("%s", type);
nftw(cond_path(type), reassert, 20, FTW_DEPTH);
_d("%s", pat);
nftw(cond_path(pat), reassert, 20, FTW_DEPTH);
}
/*
* Used only by netlink plugin atm.
*/
void cond_deassert(const char *pat)
{
_d("%s", pat);
nftw(cond_path(pat), deassert, 20, FTW_DEPTH);
}
void cond_init(void)
+6
View File
@@ -34,7 +34,13 @@ void cond_set (const char *name);
void cond_set_oneshot (const char *name);
void cond_clear (const char *name);
void cond_reload (void);
int cond_set_noupdate (const char *name);
int cond_clear_noupdate(const char *name);
void cond_reassert (const char *pat);
void cond_deassert (const char *pat);
void cond_init (void);
#endif /* FINIT_COND_H_ */