lib/PublicInbox/Daemon.pm | 26 ++++++++++++++++++++++---- t/httpd-corner.t | 19 +++++++++++++++++++ t/httpd.t | 8 ++++++++ t/nntpd-tls.t | 11 ++++++++++- diff --git a/lib/PublicInbox/Daemon.pm b/lib/PublicInbox/Daemon.pm index c4481555d43e66a7065b21e39bce4648a5780c85..8b59b65fc13f7cde1e624b4a3582e0041e13fb2d 100644 --- a/lib/PublicInbox/Daemon.pm +++ b/lib/PublicInbox/Daemon.pm @@ -8,6 +8,7 @@ use warnings; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/; use IO::Handle; use IO::Socket; +use Socket qw(IPPROTO_TCP); use Cwd qw/abs_path/; STDOUT->autoflush(1); STDERR->autoflush(1); @@ -552,6 +553,18 @@ $orig_post_accept->($ssl, $addr, $srv); } } +sub defer_accept ($) { + if ($^O eq 'linux') { + my ($s) = @_; + my $x = getsockopt($s, IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT()); + return unless defined $x; # may be Unix socket + my $sec = unpack('i', $x); + return if $sec > 0; # systemd users may set a higher value + setsockopt($s, IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT(), 1); + } + # TODO FreeBSD accf_http / accf_data +} + sub daemon_loop ($$$) { my ($refresh, $post_accept, $nntpd) = @_; PublicInbox::EvCleanup::enable(); # early for $refresh @@ -581,10 +594,15 @@ $SIG{USR1} = *reopen_logs; $SIG{HUP} = $refresh; $SIG{CHLD} = 'DEFAULT'; $SIG{$_} = 'IGNORE' for qw(USR2 TTIN TTOU WINCH); - # this calls epoll_create: - @listeners = map { - PublicInbox::Listener->new($_, - $post_accept{sockname($_)} || $post_accept) + @listeners = map {; + my $tls_cb = $post_accept{sockname($_)}; + + # NNTPS, HTTPS, HTTP, and POP3S are client-first traffic + # NNTP and POP3 are server-first + defer_accept($_) if $tls_cb || !$nntpd; + + # this calls epoll_create: + PublicInbox::Listener->new($_, $tls_cb || $post_accept) } @listeners; PublicInbox::DS->EventLoop; $parent_pipe = undef; diff --git a/t/httpd-corner.t b/t/httpd-corner.t index c1dc77dba9a3a5b89fa8420e9c66af962fa19e82..13befcf1decf5c7651ad2591d7cdb29daaf91bd9 100644 --- a/t/httpd-corner.t +++ b/t/httpd-corner.t @@ -36,6 +36,17 @@ Type => SOCK_STREAM, Listen => 1024, ); my $sock = IO::Socket::INET->new(%opts); +my $defer_accept_val; +if ($^O eq 'linux') { + setsockopt($sock, IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT(), 5) or die; + my $x = getsockopt($sock, IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT()); + defined $x or die "getsockopt: $!"; + $defer_accept_val = unpack('i', $x); + if ($defer_accept_val <= 0) { + die "unexpected TCP_DEFER_ACCEPT value: $defer_accept_val"; + } +} + my $upath = "$tmpdir/s"; my $unix = IO::Socket::UNIX->new( Listen => 1024, @@ -496,6 +507,14 @@ ($head, $body) = split(/\r\n\r\n/, $buf, 2); like($head, qr/\r\nContent-Length: 40\r\n/s, 'got expected length'); is($body, sha1_hex(''), 'read expected body #2'); } + +SKIP: { + skip 'TCP_DEFER_ACCEPT is Linux-only', 1 if $^O ne 'linux'; + my $var = Socket::TCP_DEFER_ACCEPT(); + defined(my $x = getsockopt($sock, IPPROTO_TCP, $var)) or die; + is(unpack('i', $x), $defer_accept_val, + 'TCP_DEFER_ACCEPT unchanged if previously set'); +}; done_testing(); diff --git a/t/httpd.t b/t/httpd.t index c061031cc662f7b63d6d9c4fffb2caebf2fdc926..8c2a3173a03686187e362ed5392ab7a6c279c5ba 100644 --- a/t/httpd.t +++ b/t/httpd.t @@ -10,6 +10,7 @@ plan skip_all => "$mod missing for httpd.t" if $@; } use File::Temp qw/tempdir/; use IO::Socket::INET; +use Socket qw(IPPROTO_TCP); require './t/common.perl'; # FIXME: too much setup @@ -98,6 +99,13 @@ is(system('git', "--git-dir=$tmpdir/clone.git", qw(fsck --no-verbose)), 0, 'fsck on cloned directory successful'); } + +SKIP: { + skip 'TCP_DEFER_ACCEPT is Linux-only', 1 if $^O ne 'linux'; + my $var = Socket::TCP_DEFER_ACCEPT(); + defined(my $x = getsockopt($sock, IPPROTO_TCP, $var)) or die; + ok(unpack('i', $x) > 0, 'TCP_DEFER_ACCEPT set'); +}; done_testing(); diff --git a/t/nntpd-tls.t b/t/nntpd-tls.t index e8fb63b46fc80a204f774d1718b6158249687eba..ef683cab64a0b3b7e4b5fb8950276f2136bfca2e 100644 --- a/t/nntpd-tls.t +++ b/t/nntpd-tls.t @@ -4,7 +4,7 @@ use strict; use warnings; use Test::More; use File::Temp qw(tempdir); -use Socket qw(SOCK_STREAM); +use Socket qw(SOCK_STREAM IPPROTO_TCP); # IO::Poll and Net::NNTP are part of the standard library, but # distros may split them off... foreach my $mod (qw(DBD::SQLite IO::Socket::SSL Net::NNTP IO::Poll)) { @@ -181,6 +181,15 @@ is(syswrite($slow, "QUIT\r\n"), 6, 'slow wrote QUIT'); ok(sysread($slow, my $end, 4096) > 0, 'got EOF'); is(sysread($slow, my $eof, 4096), 0, 'got EOF'); $slow = undef; + + SKIP: { + skip 'TCP_DEFER_ACCEPT is Linux-only', 2 if $^O ne 'linux'; + my $var = Socket::TCP_DEFER_ACCEPT(); + defined(my $x = getsockopt($nntps, IPPROTO_TCP, $var)) or die; + ok(unpack('i', $x) > 0, 'TCP_DEFER_ACCEPT set on NNTPS'); + defined($x = getsockopt($starttls, IPPROTO_TCP, $var)) or die; + is(unpack('i', $x), 0, 'TCP_DEFER_ACCEPT is 0 on plain NNTP'); + }; $c = undef; kill('TERM', $pid);