33

Suppose the listening socket passed to accept has non-default options set on it with setsockopt. Are these options (some or all of them?) inherited by the resulting file descriptors for accepted connections?

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 3
    When in doubt, test. I know it's not an definitive answer but looking at the other answers such a thing might not exist (and on the top of that, implementations change, sometimes accidentally, sometimes as the unsoppable march of progress). – Gil Oct 12 '12 at 16:55
  • 4
    "Test" is a good answer if you're writing a program for a particular target that's fully under your control, like an embedded system where you pick the hardware it runs on, the kernel/library/software version, etc. It's not a very useful if your goal is writing portable applications, however. And even on embedded systems, having used the "test" method of answering questions like this has a major cost: it means when you find you need to upgrade the software, you have to worry about whether the results you obtained before are still true, and if not, you may be stuck with old software... – R.. GitHub STOP HELPING ICE Oct 12 '12 at 17:43
  • This happens all the time IRL (in real life). Welcome to the world of software development. GCC changes its way to compile things, Linux changes its APIs and the location/availability of libraries, Windows pushes new APIs to innovate, etc. That's the "unstoppable march of progress"... – Gil Oct 14 '12 at 07:53
  • @Gil: I know I can test it. I have opened this bounty to find out whether relying on such a test is a good idea. – che Oct 14 '12 at 13:02
  • 5
    @Gil: That's why you write to well-specified behavior, not random things you found by testing the current implementation. – R.. GitHub STOP HELPING ICE Oct 14 '12 at 15:41
  • 2
    @There are several options which cannot be tested as such, for instance SO_BINDTODEVICE socket option is only available for setsockopt and not for getsockopt. Hence one can only try setting this option and hope your socket is bound to that interface. You would only receive confirmation once you actually get a packet, and dig deep in rcvmsg with IP_PACKETINFO to determine which interface received the packet. I have been through with this exercise in a real product. – fkl Oct 16 '12 at 05:49
  • 2
    this little bit "This option, like many others, will be inherited by the socket returned by accept(2), if it was set on the listening socket." under TCP_USER_TIMEOUT from http://man7.org/linux/man-pages/man7/tcp.7.html would seem to indicate that some are and some aren't – rmanna Apr 22 '16 at 06:55
  • Re: "This option, like many others, will be inherited by the socket returned by accept(2), if it was set on the listening socket." Neither the POSIX spec nor the accept(2) man page indicates this is the case, that any option requiring setsockopt() to enable on the listening socket will be inherited by the socket returned from accept(). If this is the case a non-conformance bug report needs to be filed with the kernel team. Such is allowed for accept4() but not accept(). – M. Ziegast Mar 16 '20 at 19:41
  • @M.Ziegast: As long as `TCP_USER_TIMEOUT` is not specified by the standard, I think any use of it (at least arguably) puts the behavior outside the scope of the standard. – R.. GitHub STOP HELPING ICE Mar 16 '20 at 22:55

4 Answers4

14

Several of the socket options are handled at lower levels of the system. While most of the socket options could be set using the setsockopt. Reference:man setsockopt And since you are mentioning only POSIX on any Linux, in general, as your scope. The accept() (Reference: man accept) does have a certain amount of discretion on what socket options should be inherited and what options to reject from the listening fd.

accept() does not modify the original socket passed to it as argument. The new socket returned by accept() does not inherit file status flags such as O_NONBLOCK,O_ASYNC from the listening socket.

So, instead of relying on the inheritance or non-inheritance of the listening socket properties(which is bound to vary across implementations and licenses), the accepted socket should be explicitly set with the desired socket options.(Best practice)

man pages and the implementation codes in your machine would be the most relevant specification for the accept() behavior.There's no common or standard specification existing across multiple variants of Linux.

askmish
  • 6,464
  • 23
  • 42
5

No, they're not necessarily inherited. Try this sample, which sets the receive buffer size (SO_RCVBUF) on the initial socket to a non-default value and then compares the result with the inherited socket. Run this code, which listens on TCP port 12345, and then connect to it from any other program.

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>

void die(const char *f)
{
  printf("%s: %s\n", f, strerror(errno));
  exit(1);
}

int main(void)
{
  int s = socket(AF_INET, SOCK_STREAM, 0);
  if(s < 0)
    die("socket");

  int rcvbuf;
  socklen_t optlen = sizeof(rcvbuf);
  if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) < 0)
    die("getsockopt (1)");
  printf("initial rcvbuf: %d\n", rcvbuf);
  rcvbuf *= 2;
  if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
    die("setsockopt");
  printf("set rcvbuf to %d\n", rcvbuf);

  struct sockaddr_in sin;
  memset(&sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(12345);
  sin.sin_addr.s_addr = INADDR_ANY;
  if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    die("bind");

  if(listen(s, 10) < 0)
    die("listen");

  struct sockaddr_in client_addr;
  socklen_t addr_len = sizeof(client_addr);
  int s2 = accept(s, (struct sockaddr *)&client_addr, &addr_len);
  if(s2 < 0)
    die("accept");
  printf("accepted connection\n");
  optlen = sizeof(rcvbuf);
  if(getsockopt(s2, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) < 0)
    die("getsockopt (2)");

  printf("new rcvbuf: %d\n", rcvbuf);

  return 0;
}

Result on a machine running Linux 3.0.0-21-generic:

initial rcvbuf: 87380
set rcvbuf to 174760
accepted connection
new rcvbuf: 262142
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • 2
    I don't think this is the case (but i can be wrong, here is the reference). "SO_RCVBUF Sets or gets the maximum socket receive buffer in bytes. The kernel doubles this value (to allow space for bookkeeping overhead) when it is set using setsockopt(2), and this doubled value is returned by getsockopt(2). The default value is set by the /proc/sys/net/core/rmem_default file, and the maximum allowed value is set by the /proc/sys/net/core/rmem_max file. The minimum (doubled) value for this option is 256". http://linux.die.net/man/7/socket. – fkl Oct 16 '12 at 05:22
  • @fayyazkl: Oh interesting, I guess that explains why the new rcvbuf is different from both the original, default value and the modified value. I modified this to do another `getsockopt` immediately after the `setsockopt` on the original socket, and it surprisingly returned a different value, which then got inherited into the new socket. However, just because it happened to be inherited in this case, that doesn't imply that all options are necessarily inherited or that that behavior can be relied upon. – Adam Rosenfield Oct 16 '12 at 19:53
  • Certainly agreed. I pointed out for e.g. SO_BINDTODEVICE (under question comments) on which only set is allowed. So you can't even test using getsock to know if it was set or for that matter the bind stays with same interface after the accept. I am sure even if many options are inherited safely, there are others with different behaviors – fkl Oct 16 '12 at 20:38
3

Socket options is the place where things go that don't fit elsewhere. So, it's expected for different socket options to have different inheriting behaviour. Whether to inherit or not a socket option is decided on a case by case basis.

ninjalj
  • 42,493
  • 9
  • 106
  • 148
1

The answer is No for POSIX conforming implementations, as I read it.

From the POSIX-2017 spec for accept():

The accept() function shall extract the first connection on the queue of pending connections, create a new socket with the same socket type protocol and address family as the specified socket, and allocate a new file descriptor for that socket.

Note it is explicitly a "new socket", not a "full or partial copy of the socket being unqueued", so should have no options different from the default for that socket type and address family. While the copy behavior may be desirable, this is left as an extension interface a platform may have. I haven't seen that any platform does implement one, however, so it could be added to the standard. It is therefore on the application to use getsockopt()/setsockopt() to copy any attributes, that differ from the defaults, from the queue socket to the returned socket, not the responsibility of the interface, before any use of that socket to send or receive data.

M. Ziegast
  • 165
  • 4
  • Additional note, I consider this as the more reliable behavior that should be the standard. What applies to a connection used for listening will frequently differ from both the defaults and what optimizes a data session being set up, so this allows applications to just set what is optimal for the session, not worry about undoing any options set for listening. – M. Ziegast Mar 16 '20 at 18:44