Page MenuHomeFreeBSD

UNIX-socket bind(): distinguish between alive listening socket and random garbage
Needs ReviewPublic

Authored by firk_cantconnect.ru on Mar 14 2022, 10:58 PM.
Referenced Files
Unknown Object (File)
Mon, Nov 18, 11:31 PM
Unknown Object (File)
Mon, Nov 18, 11:10 PM
Unknown Object (File)
Sat, Nov 9, 7:12 PM
Unknown Object (File)
Sun, Oct 27, 10:51 AM
Unknown Object (File)
Oct 11 2024, 9:20 AM
Unknown Object (File)
Oct 11 2024, 7:10 AM
Unknown Object (File)
Oct 1 2024, 3:55 AM
Unknown Object (File)
Sep 27 2024, 10:20 PM
Subscribers

Details

Reviewers
glebius
Summary

The bind()/bindat() syscall for AF_UNIX returns EADDRINUSE when some file already exist at the specified path. EADDRINUSE should mean "this address already bound to someone", and this is not always true here. There may be just some file (not a socket) or a dead socket (pending non-unlinked vnode from already closed, previously bound socket). While the first case usually considered as an application error, the second usually means "we need to remove this stale entry and try again", but to do this we should first check if the socket is really dead.

There is a hacky way to do this: try to connect to it, and see what happens, but:

  1. it is an extra unneeded system call,
  2. is the socket is not dead, the listening application will receive spurious connection request, which is not always desirable,
  3. there is a problem to distinguish between ECONNREFUSED from a dead socket and ECONNREFUSED from full-backlog; we can try to connect with a wrong protocol (SOCK_STREAM vs SOCK_DGRAM etc) to get another errno, but anyway it is a hack

So I made a patch to return EEXIST instead of EADDRINUSE for a stale dead socket. As discussed in bugzilla, also added EFTYPE for non-socket file. As this may cause some incompatibilities, I added sysctl to optionally enable this feature.

PR: 262172

Test Plan
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>

static int do_listen(char const * path) {
  int lfd;
  struct sockaddr_un sa;
  bzero(&sa, sizeof(sa));
  sa.sun_family = AF_UNIX;
  strlcpy(sa.sun_path,path,sizeof(sa.sun_path));
  if((lfd = socket(PF_UNIX, SOCK_STREAM, 0))<0) { fprintf(stderr, "socket() error %d (%s)\n", errno, strerror(errno)); return -1; }
  if(bind(lfd, (struct sockaddr*)&sa, sizeof(sa))<0) { fprintf(stderr, "bind() error %d (%s)\n", errno, strerror(errno)); return -1; }
  if(listen(lfd, 1)<0) { fprintf(stderr, "listen() error %d (%s)\n", errno, strerror(errno)); return -1; }
  fprintf(stderr, "success\n");
  return lfd;
}

int main(void) {
  int fd;
  unlink("testsock-1");
  unlink("testsock-2");
  close(open("testsock-2",O_CREAT|O_WRONLY,0644));
  fprintf(stderr,"listening testsock-1: "); fd = do_listen("testsock-1");
  fprintf(stderr,"listening testsock-1 again: "); do_listen("testsock-1");
  fprintf(stderr,"closing first lsock and listening testsock-1 again: "); close(fd); do_listen("testsock-1");
  fprintf(stderr,"listening testsock-2 over a file: "); do_listen("testsock-2");
  return 0;  
}

Unpatched system: one success and three "Address already in use"
Patched system with enabled sysctl switch: success, "Address already in use", "File exists" and "Inappropriate file type or format"

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped