Skip to contentSkip to conversation

iproute uses too small of a receive buffer

PATCH Applied Ben Greear 5 participants 17 messages Oct 27, 2009 – Nov 10, 2009

Subsystems (3)
lore
Share & tools
lore

Ben Greear

author
#0 lore raw

I have a very busy system with a bunch of xorp router processes (mis)configured.

This thing is rapidly making route changes for whatever reason.

The 'ip monitor route' command was failing:

[root@i7-dqc-1 ]# ip monitor route
netlink receive error No buffer space available (105) Dump terminated

It is only using a 32k rcv buffer, and it seems the OS was overdriving it.

Please consider making the rcv buffer larger, perhaps something like this (inline is white-space damaged...attachment should apply if deemed useful.):

lib/libnetlink.c +1 -1
index b68e2fd..95a7d1d 100644
@@ -38,7 +38,7 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
 {
        socklen_t addr_len;
        int sndbuf = 32768;
-       int rcvbuf = 32768;
+       int rcvbuf = 3276800;
 
        memset(rth, 0, sizeof(*rth));
 

Thanks,
Ben

Signature · 2 lines
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com
lib/libnetlink.c +1 -1
index b68e2fd..95a7d1d 100644
@@ -38,7 +38,7 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
 {
 	socklen_t addr_len;
 	int sndbuf = 32768;
-	int rcvbuf = 32768;
+	int rcvbuf = 3276800;
 
 	memset(rth, 0, sizeof(*rth));
 
#1 lore raw
Quoted 34 lines · On Tue, 27 Oct 2009 16:16:52 -0700 Ben Greear <[email protected]> wrote:

I have a very busy system with a bunch of xorp router processes (mis)configured.

This thing is rapidly making route changes for whatever reason.

The 'ip monitor route' command was failing:

[root@i7-dqc-1 ]# ip monitor route
netlink receive error No buffer space available (105) Dump terminated

It is only using a 32k rcv buffer, and it seems the OS was overdriving it.

Please consider making the rcv buffer larger, perhaps something like this (inline is white-space damaged...attachment should apply if deemed useful.):

lib/libnetlink.c +1 -1
index b68e2fd..95a7d1d 100644
@@ -38,7 +38,7 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
  {
         socklen_t addr_len;
         int sndbuf = 32768;
-       int rcvbuf = 32768;
+       int rcvbuf = 3276800;
 
         memset(rth, 0, sizeof(*rth));
 
Quoted 3 lines

Thanks,
Ben

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Why not have it continue after one error.

author
#2 lore raw
Quoted 37 lines · Stephen Hemminger
Quoted 34 lines · On Tue, 27 Oct 2009 16:16:52 -0700 Ben Greear<[email protected]> wrote:

I have a very busy system with a bunch of xorp router processes (mis)configured.

This thing is rapidly making route changes for whatever reason.

The 'ip monitor route' command was failing:

[root@i7-dqc-1 ]# ip monitor route
netlink receive error No buffer space available (105) Dump terminated

It is only using a 32k rcv buffer, and it seems the OS was overdriving it.

Please consider making the rcv buffer larger, perhaps something like this (inline is white-space damaged...attachment should apply if deemed useful.):

lib/libnetlink.c +1 -1
index b68e2fd..95a7d1d 100644
@@ -38,7 +38,7 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
   {
          socklen_t addr_len;
          int sndbuf = 32768;
-       int rcvbuf = 32768;
+       int rcvbuf = 3276800;
 
          memset(rth, 0, sizeof(*rth));
 
Quoted 8 lines
Quoted 3 lines

Thanks,
Ben

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Why not have it continue after one error.

Probably the right way is to give a cmd-line arg to set the buffer size and also continue if the error is ENOBUFs (but print some error out so users know they have issues). I can make the attempt if that sounds good to you.

Thanks,
Ben

Signature · 2 lines
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com
#3 lore raw
Quoted 5 lines · Ben Greear

Probably the right way is to give a cmd-line arg to set the buffer size and also continue if the error is ENOBUFs (but print some error out so users know they have issues). I can make the attempt if that sounds good to you.

Real fix is to realloc buffer at receive time, no need for user setting.

In my testings I saw it reaching 1 Mbyte write(2, "REALLOC buflen 8192\n"..., 20) = 20 write(2, "REALLOC buflen 16384\n"..., 21) = 21 write(2, "REALLOC buflen 32768\n"..., 21) = 21 write(2, "REALLOC buflen 65536\n"..., 21) = 21 write(2, "REALLOC buflen 131072\n"..., 22) = 22 write(2, "REALLOC buflen 262144\n"..., 22) = 22 write(2, "REALLOC buflen 524288\n"..., 22) = 22

[iproute2] realloc buffer in rtnl_listen

# ip monitor route
netlink receive error No buffer space available (105)
Dump terminated 

---

lib/libnetlink.c +16 -2
index b68e2fd..134ce7f 100644
@@ -392,8 +392,14 @@ int rtnl_listen(struct rtnl_handle *rtnl,
 		.msg_iov = &iov,
 		.msg_iovlen = 1,
 	};
-	char   buf[8192];
+	char   *buf;
+	size_t buflen = 8192;
 
+	buf = malloc(buflen);
+	if (buf == NULL) {
+		fprintf(stderr, "netlink could not alloc %lu bytes\n", buflen);
+		return -1;
+	}
 	memset(&nladdr, 0, sizeof(nladdr));
 	nladdr.nl_family = AF_NETLINK;
 	nladdr.nl_pid = 0;
@@ -401,12 +407,20 @@ int rtnl_listen(struct rtnl_handle *rtnl,
 
 	iov.iov_base = buf;
 	while (1) {
-		iov.iov_len = sizeof(buf);
+		iov.iov_len = buflen;
 		status = recvmsg(rtnl->fd, &msg, 0);
 
 		if (status < 0) {
 			if (errno == EINTR || errno == EAGAIN)
 				continue;
+			if (errno == ENOBUFS) {
+				buf = realloc(buf, buflen * 2);
+				if (buf) {
+					buflen *= 2;
+					iov.iov_base = buf;
+					continue;
+				}
+			}
 			fprintf(stderr, "netlink receive error %s (%d)\n",
 				strerror(errno), errno);
 			return -1;
#4 lore raw
Quoted 8 lines · Eric Dumazet
Quoted 4 lines · Ben Greear

Probably the right way is to give a cmd-line arg to set the buffer size and also continue if the error is ENOBUFs (but print some error out so users know they have issues). I can make the attempt if that sounds good to you.

Real fix is to realloc buffer at receive time, no need for user setting.

Then, another problem is that some information can be dropped at kernel level when socket rcvbuf is full (ip monitor too slow to read its socket)

Thats hard to fix because you need to tweak /proc/sys/net/core/rmem_max

#5 lore raw
In reply to Eric Dumazet
Quoted 18 lines · Eric Dumazet
Quoted 4 lines · Ben Greear

Probably the right way is to give a cmd-line arg to set the buffer size and also continue if the error is ENOBUFs (but print some error out so users know they have issues). I can make the attempt if that sounds good to you.

Real fix is to realloc buffer at receive time, no need for user setting.

In my testings I saw it reaching 1 Mbyte write(2, "REALLOC buflen 8192\n"..., 20) = 20 write(2, "REALLOC buflen 16384\n"..., 21) = 21 write(2, "REALLOC buflen 32768\n"..., 21) = 21 write(2, "REALLOC buflen 65536\n"..., 21) = 21 write(2, "REALLOC buflen 131072\n"..., 22) = 22 write(2, "REALLOC buflen 262144\n"..., 22) = 22 write(2, "REALLOC buflen 524288\n"..., 22) = 22

Quoted 8 lines

[iproute2] realloc buffer in rtnl_listen

# ip monitor route
netlink receive error No buffer space available (105)
Dump terminated 

Oops, this was wrong, Ben was right, sorry...

ENOBUFS errors is a flag to actually report to user that some information was dropped, not that user supplied buffer at recv() time is not big enough.

I was surprised that buffer could reach 1Mbytes, while RCVBUF was 32768 or so.

#6 lore raw
In reply to Stephen Hemminger
Quoted 4 lines · Stephen Hemminger

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Please note we do not allocate a big buffer, only allow more small skbs to be queued on socket receive queue.

If memory is not available, skb allocation will eventually fail and be reported as well, embedded or not.

I vote for allowing 1024*1024 bytes instead of 32768, and eventually user should be warned that it is capped by /proc/sys/net/core/rmem_max

Quoted 1 line

Why not have it continue after one error.

Yes, but caller of 'ip monitor' just restart it anyway

maintainer
#7 lore raw

From: Eric Dumazet <[email protected]>
Date: Wed, 28 Oct 2009 08:52:57 +0100

Quoted 9 lines
Quoted 4 lines · Stephen Hemminger

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Please note we do not allocate a big buffer, only allow more small skbs to be queued on socket receive queue.

Quoted 6 lines

If memory is not available, skb allocation will eventually fail and be reported as well, embedded or not.

I vote for allowing 1024*1024 bytes instead of 32768, and eventually user should be warned that it is capped by /proc/sys/net/core/rmem_max

This discussion constantly reminds me of:

/*
* skb should fit one page. This choice is good for headerless malloc.
* But we should limit to 8K so that userspace does not have to
* use enormous buffer sizes on recvmsg() calls just to avoid
* MSG_TRUNC when PAGE_SIZE is very large.
*/
#if PAGE_SIZE < 8192UL
#define NLMSG_GOODSIZE SKB_WITH_OVERHEAD(PAGE_SIZE)
#else
#define NLMSG_GOODSIZE SKB_WITH_OVERHEAD(8192UL)
#endif

#define NLMSG_DEFAULT_SIZE (NLMSG_GOODSIZE - NLMSG_HDRLEN)

#8 lore raw
In reply to Eric Dumazet
Quoted 8 lines · Eric Dumazet
Quoted 3 lines · Stephen Hemminger

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Please note we do not allocate a big buffer, only allow more small skbs to be queued on socket receive queue.

Quoted 6 lines

If memory is not available, skb allocation will eventually fail and be reported as well, embedded or not.

I vote for allowing 1024*1024 bytes instead of 32768, and eventually user should be warned that it is capped by /proc/sys/net/core/rmem_max

How about this? It will double the receive queue limit on ENOBUFS up to 1024 * 1024b, then bail out with the normal error message on further ENOBUFS.

lib/libnetlink.c +8 -1
index b68e2fd..e4fda40 100644
@@ -25,6 +25,8 @@
 
 #include "libnetlink.h"
 
+static int rcvbuf = 32768;
+
 void rtnl_close(struct rtnl_handle *rth)
 {
 	if (rth->fd >= 0) {
@@ -38,7 +40,6 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
 {
 	socklen_t addr_len;
 	int sndbuf = 32768;
-	int rcvbuf = 32768;
 
 	memset(rth, 0, sizeof(*rth));
 
@@ -407,6 +409,12 @@ int rtnl_listen(struct rtnl_handle *rtnl,
 		if (status < 0) {
 			if (errno == EINTR || errno == EAGAIN)
 				continue;
+			if (errno == ENOBUFS && rcvbuf < 1024 * 1024) {
+				rcvbuf *= 2;
+				if (setsockopt(rtnl->fd, SOL_SOCKET, SO_RCVBUF,
+					       &rcvbuf, sizeof(rcvbuf)) == 0)
+					continue;
+			}
 			fprintf(stderr, "netlink receive error %s (%d)\n",
 				strerror(errno), errno);
 			return -1;
author
#9 lore raw
Quoted 16 lines · Patrick McHardy
Quoted 8 lines · Eric Dumazet
Quoted 3 lines · Stephen Hemminger

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Please note we do not allocate a big buffer, only allow more small skbs to be queued on socket receive queue.

Quoted 6 lines

If memory is not available, skb allocation will eventually fail and be reported as well, embedded or not.

I vote for allowing 1024*1024 bytes instead of 32768, and eventually user should be warned that it is capped by /proc/sys/net/core/rmem_max

Quoted 5 lines

How about this? It will double the receive queue limit on ENOBUFS up to 1024 * 1024b, then bail out with the normal error message on further ENOBUFS.

First: This still pretty much guarantees that messages will be lost when the program starts (when messages are coming in too large of chunks for small buffers) If you are debugging something tricky, having lost messages will be
very annoying!

Second: Why bail on ENOBUFS at all? I don't see how it helps the user since they will probably just have to start it again, and will miss more messages than keeping going would have.

And, even 1MB may not be enough for some scenarios. So, probably best to let users over-ride the initial setting on cmd-line. If not, then use a large value to start with.

Thanks,
Ben

Signature · 2 lines
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com
#10 lore raw
Quoted 21 lines · Ben Greear
Quoted 20 lines · Patrick McHardy
Quoted 8 lines · Eric Dumazet
Quoted 3 lines · Stephen Hemminger

Just having larger buffer isn't guarantee of success. Allocating a huge buffer is not going to work on embedded.

Please note we do not allocate a big buffer, only allow more small skbs to be queued on socket receive queue.

Quoted 6 lines

If memory is not available, skb allocation will eventually fail and be reported as well, embedded or not.

I vote for allowing 1024*1024 bytes instead of 32768, and eventually user should be warned that it is capped by /proc/sys/net/core/rmem_max

How about this? It will double the receive queue limit on ENOBUFS up to 1024 * 1024b, then bail out with the normal error message on further ENOBUFS.

Quoted 7 lines
Quoted 1 line

First: This still pretty much guarantees that messages will be lost when the program starts (when messages are coming in too large of chunks for small buffers)
If you are debugging something tricky, having lost messages will be very annoying!

Yeah, on second thought the probing also doesn't make too much sense since the memory is only used when its really needed anyways. And its capped by rmem_max.

Quoted 3 lines

Second: Why bail on ENOBUFS at all? I don't see how it helps the user since they will probably just have to start it again, and will miss more messages than keeping going would have.

Agreed.

Quoted 3 lines

And, even 1MB may not be enough for some scenarios. So, probably best to let users over-ride the initial setting on cmd-line. If not, then use a large value to start with.

How about this? It uses 1MB as receive buf limit by default (without increasing /proc/sys/net/core/rmem_max it will be limited by less however) and allows to specify the size manually using "-rcvbuf X" (-r is already used, so you need to specify at least -rc).

Additionally rtnl_listen() continues on ENOBUFS after printing the error message.

include/libnetlink.h +2 -0
index 0e02468..61da15b 100644
@@ -17,6 +17,8 @@ struct rtnl_handle
 	__u32			dump;
 };
 
+extern int rcvbuf;
+
 extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions);
 extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, int protocol);
 extern void rtnl_close(struct rtnl_handle *rth);
ip/ip.c +15 -1
index 2bd54b2..b4c076a 100644
@@ -50,7 +50,8 @@ static void usage(void)
 "                   tunnel | maddr | mroute | monitor | xfrm }\n"
 "       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n"
 "                    -f[amily] { inet | inet6 | ipx | dnet | link } |\n"
-"                    -o[neline] | -t[imestamp] | -b[atch] [filename] }\n");
+"                    -o[neline] | -t[imestamp] | -b[atch] [filename] |\n"
+"                    -rc[vbuf] [size]}\n");
 	exit(-1);
 }
 
@@ -213,6 +214,19 @@ int main(int argc, char **argv)
 			if (argc <= 1)
 				usage();
 			batch_file = argv[1];
+		} else if (matches(opt, "-rcvbuf") == 0) {
+			unsigned int size;
+
+			argc--;
+			argv++;
+			if (argc <= 1)
+				usage();
+			if (get_unsigned(&size, argv[1], 0)) {
+				fprintf(stderr, "Invalid rcvbuf size '%s'\n",
+					argv[1]);
+				exit(-1);
+			}
+			rcvbuf = size;
 		} else if (matches(opt, "-help") == 0) {
 			usage();
 		} else {
lib/libnetlink.c +4 -1
index b68e2fd..5c716ab 100644
@@ -25,6 +25,8 @@
 
 #include "libnetlink.h"
 
+int rcvbuf = 1024 * 1024;
+
 void rtnl_close(struct rtnl_handle *rth)
 {
 	if (rth->fd >= 0) {
@@ -38,7 +40,6 @@ int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions,
 {
 	socklen_t addr_len;
 	int sndbuf = 32768;
-	int rcvbuf = 32768;
 
 	memset(rth, 0, sizeof(*rth));
 
@@ -409,6 +410,8 @@ int rtnl_listen(struct rtnl_handle *rtnl,
 				continue;
 			fprintf(stderr, "netlink receive error %s (%d)\n",
 				strerror(errno), errno);
+			if (errno == ENOBUFS)
+				continue;
 			return -1;
 		}
 		if (status == 0) {
author
#11 lore raw
Quoted 11 lines · Patrick McHardy
Quoted 3 lines

And, even 1MB may not be enough for some scenarios. So, probably best to let users over-ride the initial setting on cmd-line. If not, then use a large value to start with.

How about this? It uses 1MB as receive buf limit by default (without increasing /proc/sys/net/core/rmem_max it will be limited by less however) and allows to specify the size manually using "-rcvbuf X" (-r is already used, so you need to specify at least -rc).

Additionally rtnl_listen() continues on ENOBUFS after printing the error message.

Looks good..except:

If rmem_max is smaller than 1M, will that cause setsocktopt to fail and thus fail early out of rtnl_open_byproto?

Maybe we should only print errors but not return in that method when setsockopt fails?

In another project, I ended up trying ever smaller values until one worked in order to get near what the user wanted even if rmem_max was configured smaller. Not sure if that is worth doing here or not.

Thanks,
Ben

Signature · 2 lines
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com
#12 lore raw
Quoted 12 lines · Ben Greear
Quoted 10 lines · Patrick McHardy
Quoted 4 lines

And, even 1MB may not be enough for some scenarios. So, probably best to
let users over-ride the initial setting on cmd-line. If not, then use a large value to start with.

How about this? It uses 1MB as receive buf limit by default (without increasing /proc/sys/net/core/rmem_max it will be limited by less however) and allows to specify the size manually using "-rcvbuf X" (-r is already used, so you need to specify at least -rc).

Quoted 7 lines
Quoted 2 lines

Additionally rtnl_listen() continues on ENOBUFS after printing the error message.

Looks good..except:

If rmem_max is smaller than 1M, will that cause setsocktopt to fail and thus fail early out of rtnl_open_byproto?

No, the kernel takes the value as a hint and only uses the maximum allowable value:

case SO_RCVBUF:
/* Don't error on this BSD doesn't and if you think
about it this is right. Otherwise apps have to play 'guess the biggest size' games. RCVBUF/SNDBUF are treated in BSD as hints */

if (val > sysctl_rmem_max)
val = sysctl_rmem_max;

Quoted 6 lines

Maybe we should only print errors but not return in that method when setsockopt fails?

In another project, I ended up trying ever smaller values until one worked in order to get near what the user wanted even if rmem_max was configured smaller. Not sure if that is worth doing here or not.

I think it should be fine this way.

author
#13 lore raw
Quoted 16 lines · Patrick McHardy
Quoted 15 lines · Ben Greear
Quoted 12 lines · Patrick McHardy
Quoted 4 lines

And, even 1MB may not be enough for some scenarios. So, probably best to
let users over-ride the initial setting on cmd-line. If not, then use a large value to start with.

How about this? It uses 1MB as receive buf limit by default (without increasing /proc/sys/net/core/rmem_max it will be limited by less however) and allows to specify the size manually using "-rcvbuf X" (-r is already used, so you need to specify at least -rc).

Additionally rtnl_listen() continues on ENOBUFS after printing the error message.

Quoted 7 lines
Quoted 4 lines

Looks good..except:

If rmem_max is smaller than 1M, will that cause setsocktopt to fail and thus fail early out of rtnl_open_byproto?

No, the kernel takes the value as a hint and only uses the maximum allowable value:

Sweet. No complaints from me then.

Thanks,
Ben

Signature · 2 lines
Ben Greear <[email protected]>
Candela Technologies Inc  http://www.candelatech.com
#14 lore raw
In reply to Patrick McHardy
Quoted 9 lines

How about this? It uses 1MB as receive buf limit by default (without increasing /proc/sys/net/core/rmem_max it will be limited by less however) and allows to specify the size manually using "-rcvbuf X" (-r is already used, so you need to specify at least -rc).

Additionally rtnl_listen() continues on ENOBUFS after printing the error message.

Applied, seems like the best workaround

#15 lore raw
In reply to Ben Greear
Quoted 8 lines · Ben Greear

Second: Why bail on ENOBUFS at all? I don't see how it helps the user since they will probably just have to start it again, and will miss more messages than keeping going would have.

And, even 1MB may not be enough for some scenarios. So, probably best to let users over-ride the initial setting on cmd-line. If not, then use a large value to start with.

In this case, just dont call setsockopt() at all in "ip" and let system use the standard/default value (/proc/sys/net/core/rmem_default) that an admin can change if he wants to handle one million devices :)