]> Sergey Matveev's repositories - public-inbox.git/commitdiff
daemon: use FreeBSD accept filters on non-NNTP
authorEric Wong <e@80x24.org>
Mon, 24 Jun 2019 02:52:54 +0000 (02:52 +0000)
committerEric Wong <e@80x24.org>
Mon, 24 Jun 2019 05:26:27 +0000 (05:26 +0000)
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
t/httpd-corner.t
t/httpd.t
t/nntpd-tls.t

index 8b59b65fc13f7cde1e624b4a3582e0041e13fb2d..cf011a2020b560d544766021435b822d4e32ff21 100644 (file)
@@ -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 ($) {
index 13befcf1decf5c7651ad2591d7cdb29daaf91bd9..1cfc2565c09fd3c3aa7056cd50473ffef5f77ce8 100644 (file)
@@ -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();
 
index 8c2a3173a03686187e362ed5392ab7a6c279c5ba..e085c4b9f502b2bbdae5828ff379ddb332fcbfd9 100644 (file)
--- 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();
 
index ef683cab64a0b3b7e4b5fb8950276f2136bfca2e..427d370fc4a69a87b7e7f747cbcc5f21bccd1119 100644 (file)
@@ -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);