Ownership of file descriptors passed via domain sockets

I have a project that needs to create a TUN device on OSX. As some of the steps require root privileges, I separated it out as follows:

  1. A small C program that takes the TUN parameters and a domain-socket address as command-line arguments, creates the device, configures it and then sends its file descriptor to the provided address, via the domain socket.
  2. This executable is spawned from erlang as a port and the same process receives the file descriptor through a domain socket via socket:recvmsg/2 and parsing the ancillary data,
  3. A new socket is opened via socket:open/2 with this file descriptor.

This all works and I can drive the TUN device as expected. But - I noticed that closing the socket (step 3) does not destroy the TUN device, as it should when the last descriptor to it is closed.

It seems the descriptor transmitted through the domain socket is still alive in the fd table of the beam.smp process, but I can’t see a way to close it after creating the socket around it. I thought the dup option of socket:open/2 might offer some control, but it’s actually symmetrical in the sense that if dup is false, the underlying socket fd is not closed on socket:close/1.

In the absence of a way to close an arbitrary file descriptor, one way to support this would be to relax the dup option to allow it to be set to takeover as well as true/false, in which case it would use the fd passed but also close it.

For what it’s worth, the native mechanism used by OSX to create TUN devices means that I can redesign this and avoid this ‘leak’ by creating the fd first in the erlang process then passing it to the native privileged executable to finish the creation and configuration. But I’d be interested to what others have to say about how Erlang might more generally deal with file-descriptors injected into the vm context this way?

1 Like