From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 183F71F4B9 for ; Mon, 24 Jun 2019 02:59:25 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 53/57] daemon: use FreeBSD accept filters on non-NNTP Date: Mon, 24 Jun 2019 02:52:54 +0000 Message-Id: <20190624025258.25592-54-e@80x24.org> In-Reply-To: <20190624025258.25592-1-e@80x24.org> References: <20190624025258.25592-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: Similar to TCP_DEFER_ACCEPT on Linux, FreeBSD has a 'dataready' accept filter which we can use to reduce wakeups when doing TLS negotiation or plain HTTP. There's also a 'httpready' which we can use for plain HTTP connections. --- lib/PublicInbox/Daemon.pm | 23 +++++++++++++++-------- t/httpd-corner.t | 21 ++++++++++++++++++--- t/httpd.t | 10 ++++++++++ t/nntpd-tls.t | 14 +++++++++++++- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/lib/PublicInbox/Daemon.pm b/lib/PublicInbox/Daemon.pm index 8b59b65f..cf011a20 100644 --- a/lib/PublicInbox/Daemon.pm +++ b/lib/PublicInbox/Daemon.pm @@ -8,7 +8,8 @@ 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 Socket qw(IPPROTO_TCP SOL_SOCKET); +sub SO_ACCEPTFILTER () { 0x1000 } use Cwd qw/abs_path/; STDOUT->autoflush(1); STDERR->autoflush(1); @@ -553,20 +554,25 @@ sub tls_start_cb ($$) { } } -sub defer_accept ($) { +sub defer_accept ($$) { + my ($s, $af_name) = @_; + return unless defined $af_name; 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); + } elsif ($^O eq 'freebsd') { + my $x = getsockopt($s, SOL_SOCKET, SO_ACCEPTFILTER); + return if defined $x; # don't change if set + my $accf_arg = pack('a16a240', $af_name, ''); + setsockopt($s, SOL_SOCKET, SO_ACCEPTFILTER, $accf_arg); } - # TODO FreeBSD accf_http / accf_data } -sub daemon_loop ($$$) { - my ($refresh, $post_accept, $nntpd) = @_; +sub daemon_loop ($$$$) { + my ($refresh, $post_accept, $nntpd, $af_default) = @_; PublicInbox::EvCleanup::enable(); # early for $refresh my %post_accept; while (my ($k, $v) = each %tls_opt) { @@ -599,7 +605,7 @@ sub daemon_loop ($$$) { # NNTPS, HTTPS, HTTP, and POP3S are client-first traffic # NNTP and POP3 are server-first - defer_accept($_) if $tls_cb || !$nntpd; + defer_accept($_, $tls_cb ? 'dataready' : $af_default); # this calls epoll_create: PublicInbox::Listener->new($_, $tls_cb || $post_accept) @@ -612,8 +618,9 @@ sub daemon_loop ($$$) { sub run ($$$;$) { my ($default, $refresh, $post_accept, $nntpd) = @_; daemon_prepare($default); + my $af_default = $default =~ /:8080\z/ ? 'httpready' : undef; daemonize(); - daemon_loop($refresh, $post_accept, $nntpd); + daemon_loop($refresh, $post_accept, $nntpd, $af_default); } sub do_chown ($) { diff --git a/t/httpd-corner.t b/t/httpd-corner.t index 13befcf1..1cfc2565 100644 --- a/t/httpd-corner.t +++ b/t/httpd-corner.t @@ -18,7 +18,7 @@ use File::Temp qw/tempdir/; use IO::Socket; use IO::Socket::UNIX; use Fcntl qw(:seek); -use Socket qw(IPPROTO_TCP TCP_NODELAY); +use Socket qw(IPPROTO_TCP TCP_NODELAY SOL_SOCKET); use POSIX qw(mkfifo); require './t/common.perl'; my $tmpdir = tempdir('httpd-corner-XXXXXX', TMPDIR => 1, CLEANUP => 1); @@ -36,7 +36,10 @@ my %opts = ( Listen => 1024, ); my $sock = IO::Socket::INET->new(%opts); -my $defer_accept_val; + +# Make sure we don't clobber socket options set by systemd or similar +# using socket activation: +my ($defer_accept_val, $accf_arg); 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()); @@ -45,6 +48,11 @@ if ($^O eq 'linux') { if ($defer_accept_val <= 0) { die "unexpected TCP_DEFER_ACCEPT value: $defer_accept_val"; } +} elsif ($^O eq 'freebsd' && system('kldstat -m accf_data >/dev/null') == 0) { + require PublicInbox::Daemon; + my $var = PublicInbox::Daemon::SO_ACCEPTFILTER(); + $accf_arg = pack('a16a240', 'dataready', ''); + setsockopt($sock, SOL_SOCKET, $var, $accf_arg) or die "setsockopt: $!"; } my $upath = "$tmpdir/s"; @@ -100,7 +108,7 @@ my $spawn_httpd = sub { is(scalar(grep(/CLOSE FAIL/, @$after)), 1, 'body->close not called'); } -{ +SKIP: { my $conn = conn_for($sock, 'excessive header'); $SIG{PIPE} = 'IGNORE'; $conn->write("GET /callback HTTP/1.0\r\n"); @@ -515,6 +523,13 @@ SKIP: { is(unpack('i', $x), $defer_accept_val, 'TCP_DEFER_ACCEPT unchanged if previously set'); }; +SKIP: { + skip 'SO_ACCEPTFILTER is FreeBSD-only', 1 if $^O ne 'freebsd'; + skip 'accf_data not loaded: kldload accf_data' if !defined $accf_arg; + my $var = PublicInbox::Daemon::SO_ACCEPTFILTER(); + defined(my $x = getsockopt($sock, SOL_SOCKET, $var)) or die; + is($x, $accf_arg, 'SO_ACCEPTFILTER unchanged if previously set'); +}; done_testing(); diff --git a/t/httpd.t b/t/httpd.t index 8c2a3173..e085c4b9 100644 --- a/t/httpd.t +++ b/t/httpd.t @@ -106,6 +106,16 @@ SKIP: { defined(my $x = getsockopt($sock, IPPROTO_TCP, $var)) or die; ok(unpack('i', $x) > 0, 'TCP_DEFER_ACCEPT set'); }; +SKIP: { + skip 'SO_ACCEPTFILTER is FreeBSD-only', 1 if $^O ne 'freebsd'; + if (system('kldstat -m accf_http >/dev/null') != 0) { + skip 'accf_http not loaded: kldload accf_http', 1; + } + require PublicInbox::Daemon; + my $var = PublicInbox::Daemon::SO_ACCEPTFILTER(); + my $x = getsockopt($sock, SOL_SOCKET, $var); + like($x, qr/\Ahttpready\0+\z/, 'got httpready accf for HTTP'); +}; done_testing(); diff --git a/t/nntpd-tls.t b/t/nntpd-tls.t index ef683cab..427d370f 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 IPPROTO_TCP); +use Socket qw(SOCK_STREAM IPPROTO_TCP SOL_SOCKET); # 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)) { @@ -190,6 +190,18 @@ for my $args ( defined($x = getsockopt($starttls, IPPROTO_TCP, $var)) or die; is(unpack('i', $x), 0, 'TCP_DEFER_ACCEPT is 0 on plain NNTP'); }; + SKIP: { + skip 'SO_ACCEPTFILTER is FreeBSD-only', 2 if $^O ne 'freebsd'; + if (system('kldstat -m accf_data >/dev/null')) { + skip 'accf_data not loaded? kldload accf_data', 2; + } + require PublicInbox::Daemon; + my $var = PublicInbox::Daemon::SO_ACCEPTFILTER(); + my $x = getsockopt($nntps, SOL_SOCKET, $var); + like($x, qr/\Adataready\0+\z/, 'got dataready accf for NNTPS'); + $x = getsockopt($starttls, IPPROTO_TCP, $var); + is($x, undef, 'no BSD accept filter for plain NNTP'); + }; $c = undef; kill('TERM', $pid); -- EW