kernel-netlink: Implement configurable Netlink request retransmission

This commit is contained in:
Martin Willi
2014-11-21 10:55:45 +01:00
parent aa762bed7b
commit 15dc61757c
@@ -65,6 +65,16 @@ struct private_netlink_socket_t {
* Enum names for Netlink messages * Enum names for Netlink messages
*/ */
enum_name_t *names; enum_name_t *names;
/**
* Timeout for Netlink replies, in ms
*/
u_int timeout;
/**
* Number of times to repeat timed out queries
*/
u_int retries;
}; };
/** /**
@@ -117,13 +127,28 @@ static bool write_msg(private_netlink_socket_t *this, struct nlmsghdr *msg)
} }
/** /**
* Read a single Netlink message from socket * Read a single Netlink message from socket, return 0 on error, -1 on timeout
*/ */
static size_t read_msg(private_netlink_socket_t *this, static ssize_t read_msg(private_netlink_socket_t *this,
char buf[4096], size_t buflen, bool block) char buf[4096], size_t buflen, bool block)
{ {
ssize_t len; ssize_t len;
if (block)
{
fd_set set;
timeval_t tv = {};
FD_ZERO(&set);
FD_SET(this->socket, &set);
timeval_add_ms(&tv, this->timeout);
if (select(this->socket + 1, &set, NULL, NULL,
this->timeout ? &tv : NULL) <= 0)
{
return -1;
}
}
len = recv(this->socket, buf, buflen, block ? 0 : MSG_DONTWAIT); len = recv(this->socket, buf, buflen, block ? 0 : MSG_DONTWAIT);
if (len == buflen) if (len == buflen)
{ {
@@ -175,18 +200,22 @@ static bool queue(private_netlink_socket_t *this, struct nlmsghdr *buf)
} }
/** /**
* Read and queue response message, optionally blocking * Read and queue response message, optionally blocking, returns TRUE on timeout
*/ */
static void read_and_queue(private_netlink_socket_t *this, bool block) static bool read_and_queue(private_netlink_socket_t *this, bool block)
{ {
struct nlmsghdr *hdr; struct nlmsghdr *hdr;
union { union {
struct nlmsghdr hdr; struct nlmsghdr hdr;
char bytes[4096]; char bytes[4096];
} buf; } buf;
size_t len; ssize_t len;
len = read_msg(this, buf.bytes, sizeof(buf.bytes), block); len = read_msg(this, buf.bytes, sizeof(buf.bytes), block);
if (len == -1)
{
return TRUE;
}
if (len) if (len)
{ {
hdr = &buf.hdr; hdr = &buf.hdr;
@@ -199,6 +228,7 @@ static void read_and_queue(private_netlink_socket_t *this, bool block)
hdr = NLMSG_NEXT(hdr, len); hdr = NLMSG_NEXT(hdr, len);
} }
} }
return FALSE;
} }
CALLBACK(watch, bool, CALLBACK(watch, bool,
@@ -215,14 +245,12 @@ CALLBACK(watch, bool,
* Send a netlink request, try once * Send a netlink request, try once
*/ */
static status_t send_once(private_netlink_socket_t *this, struct nlmsghdr *in, static status_t send_once(private_netlink_socket_t *this, struct nlmsghdr *in,
struct nlmsghdr **out, size_t *out_len) uintptr_t seq, struct nlmsghdr **out, size_t *out_len)
{ {
struct nlmsghdr *hdr; struct nlmsghdr *hdr;
chunk_t result = {}; chunk_t result = {};
entry_t *entry; entry_t *entry;
uintptr_t seq;
seq = ref_get(&this->seq);
in->nlmsg_seq = seq; in->nlmsg_seq = seq;
in->nlmsg_pid = getpid(); in->nlmsg_pid = getpid();
@@ -249,18 +277,38 @@ static status_t send_once(private_netlink_socket_t *this, struct nlmsghdr *in,
{ {
if (lib->watcher->get_state(lib->watcher) == WATCHER_RUNNING) if (lib->watcher->get_state(lib->watcher) == WATCHER_RUNNING)
{ {
entry->condvar->wait(entry->condvar, this->mutex); if (this->timeout)
{
if (entry->condvar->timed_wait(entry->condvar, this->mutex,
this->timeout))
{
break;
}
}
else
{
entry->condvar->wait(entry->condvar, this->mutex);
}
} }
else else
{ /* During (de-)initialization, no watcher thread is active. { /* During (de-)initialization, no watcher thread is active.
* collect responses ourselves. */ * collect responses ourselves. */
read_and_queue(this, TRUE); if (read_and_queue(this, TRUE))
{
break;
}
} }
} }
this->entries->remove(this->entries, (void*)seq); this->entries->remove(this->entries, (void*)seq);
this->mutex->unlock(this->mutex); this->mutex->unlock(this->mutex);
if (!entry->complete)
{ /* timeout */
destroy_entry(entry);
return OUT_OF_RES;
}
while (array_remove(entry->hdrs, ARRAY_HEAD, &hdr)) while (array_remove(entry->hdrs, ARRAY_HEAD, &hdr))
{ {
if (this->names) if (this->names)
@@ -283,16 +331,31 @@ METHOD(netlink_socket_t, netlink_send, status_t,
private_netlink_socket_t *this, struct nlmsghdr *in, struct nlmsghdr **out, private_netlink_socket_t *this, struct nlmsghdr *in, struct nlmsghdr **out,
size_t *out_len) size_t *out_len)
{ {
while (TRUE) uintptr_t seq;
u_int try;
seq = ref_get(&this->seq);
for (try = 0; try <= this->retries; ++try)
{ {
struct nlmsghdr *hdr; struct nlmsghdr *hdr;
status_t status; status_t status;
size_t len; size_t len;
status = send_once(this, in, &hdr, &len); if (try > 0)
if (status != SUCCESS)
{ {
return status; DBG1(DBG_KNL, "retransmitting Netlink request (%u/%u)",
try, this->retries);
}
status = send_once(this, in, seq, &hdr, &len);
switch (status)
{
case SUCCESS:
break;
case OUT_OF_RES:
continue;
default:
return status;
} }
if (hdr->nlmsg_type == NLMSG_ERROR) if (hdr->nlmsg_type == NLMSG_ERROR)
{ {
@@ -302,6 +365,7 @@ METHOD(netlink_socket_t, netlink_send, status_t,
if (err->error == -EBUSY) if (err->error == -EBUSY)
{ {
free(hdr); free(hdr);
try--;
continue; continue;
} }
} }
@@ -309,6 +373,9 @@ METHOD(netlink_socket_t, netlink_send, status_t,
*out_len = len; *out_len = len;
return SUCCESS; return SUCCESS;
} }
DBG1(DBG_KNL, "Netlink request timed out after %u retransmits",
this->retries);
return OUT_OF_RES;
} }
METHOD(netlink_socket_t, netlink_send_ack, status_t, METHOD(netlink_socket_t, netlink_send_ack, status_t,
@@ -397,6 +464,10 @@ netlink_socket_t *netlink_socket_create(int protocol, enum_name_t *names)
.socket = socket(AF_NETLINK, SOCK_RAW, protocol), .socket = socket(AF_NETLINK, SOCK_RAW, protocol),
.entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
.names = names, .names = names,
.timeout = lib->settings->get_int(lib->settings,
"%s.plugins.kernel-netlink.timeout", 0, lib->ns),
.retries = lib->settings->get_int(lib->settings,
"%s.plugins.kernel-netlink.retries", 0, lib->ns),
); );
if (this->socket == -1) if (this->socket == -1)