Prevent Fork() from Copying Sockets

Prevent fork() from copying sockets

No, you can't force all file descriptors to be closed on exec. You will need to loop over all unwanted file descriptors in the child after the fork() and close them. Unfortunately, there isn't an easy, portable, way to do that - the usual approach is to use getrlimit() to get the current value of RLIMIT_NOFILE and loop from 3 to that number, trying close() on each candidate.

If you are happy to be Linux-only, you can read the /proc/self/fd/ directory to determine the open file descriptors and close them (except 0, 1 and 2 - which should either be left alone or reopened to /dev/null).

Linux C socket how to prevent 2 forked process from accepting the same connection when using epoll?

You are creating multiple epoll instances and registering for edge-triggered events on the listening socket in each one. Naturally, you will get an event from each one when a new connection becomes available to accept. However, only one process can successfully accept each connection. As observed in comments, two different children might accept connections that are assigned the same file descriptor number in the respective processes, but that doesn't mean they refer to the same socket.

You have several options, but prominent among them are:

  • use a single epoll instance, shared by all the processes. You can get this automatically by having the parent create it before forking off any of the children. In this case, only one child will receive each edge-triggered event. Of course, if the children intend to register for events that should be received only by them, then this isn't going to work very well.

  • just accept (no pun intended) that multiple processes will receive events when a connection becomes available, and deal with it. That appears to be what you're doing now (by ignoring EAGAIN and EWOULDBLOCK errors from accept()), and I see no particular reason why you shouldn't keep doing it.

Avoid variable duplication using fork() in C

fork does not duplicate variables but the entire address space (by definition of fork) of the invoking process.

You might want to use some shared memory, but then you should care about synchronization. Read shm_overview(7) and sem_overview(7) (you could share some memory using mmap(2) but you need to synchronize anyway).

Maybe you don't need to fork, but you just want to have several threads sharing the same address space. Read pthreads(7) and a good pthread tutorial. Then you'll also should care about synchronization, probably using mutexes.

You could also (and instead) use some other form of IPC. Read Advanced Linux Programming, consider using pipe(7)-s and have some event loop around a multiplexing syscall like poll(2)

In a server/client scenario, you might have some request and protocol to query (from inside clients) some state variable (inside the server).

PS. The main issue is not sharing data, it is synchronization and deadlock avoidance.

unix socket creation then fork(), messaging gets mixed up

I did some additional research and there is another way to do this, I learned about pthread_atfork() which sets up functions to be called by the parent and the child upon fork.. i actually used this mechanism to stop and start the network functionality on the child, and it worked!

https://linux.die.net/man/3/pthread_atfork

Closing the listening socket after a fork()

Linux queues up pending connections. A call to accept, from either the parent or child process, will poll that queue.

Not closing the socket in the child process is a resource leak, but not much else. The parent will still grab all the incoming connections, because it's the only one that calls accept, but if the parent exits, the socket will still exist because it's open on the child, even if the child never uses it.

How to correctly use socket close for fork?

When a process forks, file descriptors are duplicated in the child process. However, these file descriptors are distinct from each other. Closing a file descriptor in the child doesn't affect the corresponding file descriptor in the parent, and vice versa.

In your case, since the child process needs the accepted socket new_sockfd and the parent process continues to use the listening socket sockfd, the child should close(sockfd) (in your if block; this doesn't affect the parent) and the parent should close(new_sockfd) (in your else block; this doesn't affect the child). The fact that the parent and child are running at the same time doesn't affect this.

Purpose of using fork() with select() in c sockets

fork() spawns a child process; it isn’t required for network programming, but some network programs do it as a way to allow the code that handles a particular client to execute in parallel with the code (in the parent-process) that is accepting incoming TCP connections. Other approaches include spawning a thread to handle the client, or using asynchronous I/O, or using select() and non-blocking I/O to handle the client in the same/single thread.

If you call fork() and don’t want your child processes to become zombies, you will need to call waitpid() on the child process at some point; otherwise the child process’s return-value will remain uncollected and you’ll have a zombie. However, you don’t have to call waitpid() right away; and if you pass in the WNOHANG argument you can avoid having waitpid() block; instead, with WNOHANG waitpid() will return 0 immediately if the child process is still alive, and you can try calling it again later.

Having zombie processes is a problem, because if you keep generating them, eventually the processes-table will fill up with zombies and then you won’t be able to spawn any more processes.

Is this the correct way to close socket descriptors on fork?

Converting the comment stream into an answer.

TL; DR

Yes. The description in the question looks correct and the reasoning is sound.

At some point, your parent process should wait for child processes that have died to prevent the accumulation of zombies (but it shouldn't block until a child dies). Using waitpid() in a loop with the WNOHANG argument is probably appropriate, in the portion of the loop where the parent closes the new_socket_fd. This could leave one or more zombies around until the next incoming request is made. If that's a problem, you could ignore SIGCHLD (so zombies are never created), or you could arrange a periodic wake-up during which the parent process checks for zombies.

Discussion

babon asked

Quick question - so when / where does the parent process close socket_fd?

The parent closes the socket_fd when it exits the loop or has been told to stop listening on the socket. There's no real provision for that in the code shown, so it will be closed when the parent process is killed (or a fork failure occurs). The whole point is that the listening socket can be used for many connections — you don't want to close it in the parent until you've finished listening.

Matteo noted

In this case since it's an endless loop, never. The server will always be listening for up to N connections defined in listen(socket_fd, N).

Note that the N parameter in the listen() call is the number of outstanding connections that can be queued for the listening process. That is, the number of connection requests that have not yet returned a value via the accept() call. It is not a limit on the number of connections that can be active concurrently after the accept() has accepted the connection.

Ajay Brahmakshatriya asked

Before the child closes the socket_fd, is the bound port mapped to both the PIDs? If there is a incoming packet, whose queue will it be put into?

The incoming packet is associated with the socket's 'open file description' (or equivalent — as distinct from 'file descriptor' or 'socket descriptor'). It is available to either parent or child, whichever reads it first. Similarly, incoming connection requests are queued on the socket_fd; they could be accepted by either parent or child. However, the family has agreed who does what so they don't get in each other's way.

Matteo commented

To the parent's one I assume.

Ajay responded

If that is the case, the same should also happen for the packets for the new_socket_fd since both have it open. This means that the child won't be able to read the packets until the parent closes it. This can lead to race conditions.

This is based on a misunderstanding. The packet is available to both processes via the file descriptor. When a process closes the file descriptor, it can no longer access the information sent to connection (or send data on that connection, of course). Until then, it is a lottery who sees what unless the participant processes agree on which one reads the data and which one listens for connections

Matteo responded

But file descriptor shouldn't interfere between parent and child; that's why closing socket_fd on the child side doesn't stop the parent from listening.

babon commented

Agreed. But I think you should close socket_fd after the while loop. In case tomorrow you change the loop to break for some condition you run the risk of keeping an open socket for no reason.

This is good practice, but the loop doesn't exit (it is a while (1) loop, and the failure mode does a return out of the loop — it could close the socket before doing that return). And if the program exits, then the system closes the socket, so it isn't crucial, for all it is good housekeeping to close what you open.

Ajay notes

Both the file descriptors in the parent and child are different. So closing one should not affect the other. But both the copies have the same (src-ip, src-port, dest-ip, dest-port) 4-tuple and so where would a packet with such a header go?

The descriptors are different, but the socket connection they refer to is the same. The packet is available to any process that reads it — parent or child.

Matteo responded

In my example accept_client creates the sockaddr struct for the client, so the 4-tuple goes to child's new_socket_fd.

This isn't quite accurate. First, accept_client() is called before there is a child; the new_socket_fd is in the parent (only) when that function completes. Second, after the fork(), both processes have access to the new_socket_fd, and either could read the data sent by the client process. However, the program is designed so that the server goes back to listening for more connection requests while the child processes the incoming connection on new_socket_fd — a sensible division of labour.

Note that it would be permissible to have the parent process the request and the child continue listening. However, this is contrary to convention. It would mean that the 'daemon' process listening changes PID on each connection request, making it hard to determine which process is currently listening on the socket. The conventional approach used in the code is that the daemon process remains the same over long periods of time, making it sensible to record the PID for later process control (killing the daemon when it isn't needed any more).

How is socket connection being handled in a forked process

  1. First, accept() the incoming connection. The accepting process now has a handle to the listening socket, and the newly accepted socket.
  2. Fork and:
    • In the child:
      1. Close the listening socket.
      2. Do stuff with the accepted socket.
    • In the parent:
      1. Close the accepted socket.
      2. Resume the accept loop.

The various socket resources will be reclaimed when all references to the handle are closed. If a process terminates, all its handles are closed implicitly. Therefore if a child closes the handle it inherits to the listening socket, the only handle remaining to that socket exists in the parent. Then the listening socket will be reclaimed when the parent terminates, or closes this handle explicitly.



Related Topics



Leave a reply



Submit