简体   繁体   English

在 fork() 之后关闭监听套接字

[英]Closing the listening socket after a fork()

A common server socket pattern on Linux/UNIX systems is to listen on a socket, accept a connection, and then fork() to process the connection. Linux/UNIX 系统上常见的服务器套接字模式是侦听套接字,接受连接,然后fork()处理连接。

So, it seems that after you accept() and fork() , once you're inside the child process, you will have inherited the listening file descriptor of the parent process.因此,在您accept()fork() ,一旦您进入子进程,您将继承父进程的侦听文件描述符。 I've read that at this point, you need to close the listening socket file descriptor from within the child process.我已经读到此时,您需要从子进程中关闭侦听套接字文件描述符。

My question is, why?我的问题是,为什么? Is this simply to reduce the reference count of the listening socket?这仅仅是为了减少监听套接字的引用计数吗? Or is it so that the child process itself will not be used by the OS as a candidate for routing incoming connections?或者是为了使操作系统不会将子进程本身用作路由传入连接的候选者? If it's the latter, I'm a bit confused for two reasons:如果是后者,我有点困惑,原因有两个:

(A) What tells the OS that a certain process is a candidate for accepting connections on a certain file descriptor? (A) 什么告诉操作系统某个进程是接受某个文件描述符上的连接的候选者? Is it the fact that the process has called accept() ?过程调用了accept()是事实吗? Or is it the fact that the process has called listen() ?或者是进程调用了listen()的事实?

(B) If it's the fact that the process has called listen() , don't we have a race condition here? (B) 如果是进程调用了listen()的事实,我们这里不是存在竞争条件吗? What if this happens:如果发生这种情况怎么办:

  1. Parent process listens on socket S.父进程侦听套接字 S。
  2. Incoming connection goes to Parent Process.传入连接进入父进程。
  3. Parent Process forks a child, child has a copy of socket S父进程fork一个子进程,子进程拥有socket S的副本
  4. BEFORE the child is able to call close(S) , a second incoming connection goes to Child Process.子进程能够调用close(S)第二个传入连接进入子进程。
  5. Child Process never calls accept() (because it's not supposed to), so the incoming connection gets dropped子进程从不调用accept() (因为它不应该调用),因此传入的连接被丢弃

What prevents the above condition from happening?是什么阻止了上述情况的发生? And more generally, why should a child process close the listening socket?更一般地说,为什么子进程应该关闭监听套接字?

Linux queues up pending connections. Linux 将挂起的连接排队。 A call to accept , from either the parent or child process, will poll that queue.从父进程或子进程调用accept将轮询该队列。

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.父级仍然会获取所有传入的连接,因为它是唯一一个调用accept ,但是如果父级退出,套接字仍然存在,因为它在子级上打开,即使子级从不使用它。

The incoming connection will be 'delivered' to which ever process is calling accept() .传入的连接将被“传递”到调用accept()进程。 After you forked before closing the file descriptor you could accept the connection in both processes.在关闭文件描述符之前分叉后,您可以在两个进程中接受连接。

So as long as you never accept any connections in the child thread and the parent is continuing to accept the connections everything would work fine.因此,只要您从不接受子线程中的任何连接并且父线程继续接受连接,一切都会正常工作。

But if you plan to never accept connections in your child process, why would you want to keep resources for the socket in this process?但是,如果您打算从不接受子进程中的连接,为什么要在此进程中为套接字保留资源?

The interesting question would be what happens if both processes call accept() on the socket.有趣的问题是如果两个进程都在套接字上调用accept()会发生什么。 I could not find definite information on this at the moment.我目前找不到这方面的确切信息。 What I could find is, that you can be sure, that every connection is only delivered to only one of these processes.我能发现的是,您可以确定,每个连接仅传送到这些进程中的一个。

A child process inherits all files descriptors from its parent.子进程从其父进程继承所有文件描述符。 A child process should close all listening sockets to avoid conflicts with its parent.子进程应关闭所有侦听套接字以避免与其父进程发生冲突。

除非调用 accept() ,否则子进程不会监听套接字,在这种情况下,传入连接可以转到任一进程。

In the socket() manual, a paragraph says:socket()手册中,有一段说:

SOCK_CLOEXEC
Set the close-on-exec ( FD_CLOEXEC ) flag on the new file descriptor.在新文件描述符上设置 close-on-exec ( FD_CLOEXEC ) 标志。 See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.请参阅open(2) O_CLOEXEC标志的说明,了解这可能有用的原因。

Unfortunately, that doesn't do anything when you call fork() , it's only for when you call execv() and other similar functions.不幸的是,这在您调用fork()时没有任何作用,仅在您调用execv()和其他类似函数时才起作用。 Anyway, reading the info in the open() function manual we see:无论如何,阅读open()函数手册中的信息,我们看到:

O_CLOEXEC (since Linux 2.6.23) O_CLOEXEC (自 Linux 2.6.23 起)
Enable the close-on-exec flag for the new file descriptor.为新文件描述符启用 close-on-exec 标志。 Specifying this flag permits a program to avoid additional fcntl(2) F_SETFD operations to set the FD_CLOEXEC flag.指定此标志允许程序避免额外的fcntl(2) F_SETFD操作来设置FD_CLOEXEC标志。

Note that the use of this flag is essential in some multithreaded programs, because using a separate fcntl(2) F_SETFD operation to set the FD_CLOEXEC flag does not suffice to avoid race conditions where one thread opens a file descriptor and attempts to set its close-on-exec flag using fcntl(2) at the same time as another thread does a fork(2) plus execve(2) .请注意,在某些多线程程序中使用此标志是必不可少的,因为使用单独的fcntl(2) F_SETFD操作来设置FD_CLOEXEC标志不足以避免一个线程打开文件描述符并尝试设置其FD_CLOEXEC竞争条件 -在另一个线程执行fork(2)execve(2)的同时使用fcntl(2) on-exec 标志。 Depending on the order of execution, the race may lead to the file descriptor returned by open() being unintentionally leaked to the program executed by the child process created by fork(2) .根据执行顺序,竞争可能会导致open()返回的文件描述符无意中泄露给由fork(2)创建的子进程执行的程序。 (This kind of race is in principle possible for any system call that creates a file descriptor whose close-on-exec flag should be set, and various other Linux system calls provide an equivalent of the O_CLOEXEC flag to deal with this problem.) (这种竞争原则上对于任何创建文件描述符的系统调用都是可能的,该文件描述符应该设置 close-on-exec 标志,并且各种其他 Linux 系统调用提供了等效的O_CLOEXEC标志来处理这个问题。)

Okay so what does all of that mean?好吧,这一切意味着什么?

The idea is very simple.这个想法很简单。 If you leave a file descriptor open when you call execve() , you give the child process access to that file descriptor and thus it may be given access to data that it should not have access to.如果在调用execve()时让文件描述符保持打开状态,则您授予子进程访问该文件描述符的权限,因此它可能会被授予访问它不应该访问的数据的权限。

When you create a service which fork() s and then executes code, that code often starts by dropping rights (ie the main apache2 service runs as root, but all the spawned fork() actually run as the httpd or www user—it is important for the main process to be root in order to open ports 80 and 443, any port under 1024, actually).当你创建一个fork() s 然后执行代码的服务时,该代码通常以删除权限开始(即主要的 apache2 服务以 root 身份运行,但所有衍生的fork()实际上以httpdwww用户身份运行——它是为了打开端口 80 和 443(实际上是 1024 下的任何端口),主进程必须是 root 用户,这一点很重要。 Now, if a hacker is somehow able to gain control of that child process, they at least won't have access to that file descriptor if closed very early on.现在,如果黑客能够以某种方式获得对该子进程的控制,那么如果很早就关闭,他们至少将无法访问该文件描述符。 This is much safer.这样安全得多。

On the other hand, my apache2 example works differently: it first open a socket and binds it to port 80, 443, etc. and then creates children with fork() and each child calls accept() (which by default blocks).另一方面,我的 apache2 示例的工作方式不同:它首先打开一个套接字并将其绑定到端口 80、443 等,然后使用fork()创建子级,每个子级调用accept() (默认情况下会阻塞)。 The first incoming connection will wake up one of the children by returning from the accept() call.第一个传入连接将通过从accept()调用返回来唤醒其中一个孩子。 So I guess that one is not that risky after all.所以我想这毕竟不是那么冒险。 It will even keep that connection open and call accept() again, up to the max.它甚至会保持该连接打开并再次调用accept() ,直到最大值。 defined in your settings (something like 100 by default, depends on the OS you use).在您的设置中定义(默认情况下类似于 100,取决于您使用的操作系统)。 After max.最大后accept() calls, that child process exits and the server creates a new instance. accept()调用,该子进程退出并且服务器创建一个新实例。 This is to make sure that the memory footprint doesn't grow too much.这是为了确保内存占用不会增长太多。

So in your case, it may not be that important.所以在你的情况下,它可能不那么重要。 However, if a hacker takes over your process, they could accept other connections and handle them with their canny version of your server... something to thing about.但是,如果黑客接管了您的进程,他们可以接受其他连接并使用您的服务器的精明版本来处理它们……一些事情。 If your service is internal (only runs on your Intranet), then the danger is lesser (although from what I read, most thieves in companies are employees working there...)如果您的服务是内部的(仅在您的 Intranet 上运行),那么危险就会较小(尽管从我读到的内容来看,公司中的大多数窃贼都是在那里工作的员工......)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM