]> git.ipfire.org Git - thirdparty/public-inbox.git/commitdiff
daemon: check connections WIP
authorEric Wong <e@80x24.org>
Tue, 28 Jan 2025 08:10:06 +0000 (08:10 +0000)
committerEric Wong <e@80x24.org>
Tue, 28 Jan 2025 08:22:43 +0000 (08:22 +0000)
MANIFEST
devel/sysdefs-list
lib/PublicInbox/Daemon.pm
lib/PublicInbox/HTTPD.pm
lib/PublicInbox/Syscall.pm
t/daemon.t [new file with mode: 0644]

index 30adab809e7bbf67a27fa8082b263303dafa02b7..0a0f8a1d28cf919a4ffea8a4b54b2f4b4b7a856a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -448,6 +448,7 @@ t/config.t
 t/config_limiter.t
 t/content_hash.t
 t/convert-compact.t
+t/daemon.t
 t/data-gen/.gitignore
 t/data/0001.patch
 t/data/attached-mbox-with-utf8.eml
index 1345e0ba24a718d8c636a0441c2350e8dbfb9013..f5c3d8342443280f4217022abc6875fba34074f8 100755 (executable)
@@ -24,6 +24,14 @@ my $x = "$tmp/sysdefs";
 open my $fh, '>', $f or die "open $f $!";
 print $fh $str or die "print $f $!";
 close $fh or die "close $f $!";
+for (qw(sys/ioctl sys/filio)) {
+       my $cfg_name = $_;
+       my $cpp_name = uc $_;
+       $cfg_name =~ tr!/!!d;
+       $cpp_name =~ tr!/!_!;
+       ($Config{"i_$cfg_name"} // '') eq 'define' and
+               push @cflags, "-DHAVE_${cpp_name}_H";
+}
 system($cc, '-o', $x, $f, @cflags) == 0 or die "$cc failed \$?=$?";
 print STDERR '# %Config',
        (map { " $_=$Config{$_}" } qw(ptrsize sizesize lseeksize)), "\n";
@@ -37,7 +45,12 @@ __DATA__
 #include <stddef.h>
 #include <sys/socket.h>
 #include <sys/syscall.h>
-#include <sys/ioctl.h>
+#ifdef HAVE_SYS_IOCTL_H
+#      include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+#      include <sys/filio.h>
+#endif
 #ifdef __linux__
 #      include <linux/fs.h>
 #      include <sys/epoll.h>
@@ -144,6 +157,7 @@ int main(void)
 #endif /* Linux, any other OSes with stable syscalls? */
 
        D(SIGWINCH);
+       X(FIONREAD);
        MAYBE D(SO_ACCEPTFILTER);
        MAYBE D(_SC_NPROCESSORS_ONLN);
        MAYBE D(_SC_AVPHYS_PAGES);
index 57f01d2cb1729d6437d8151a87f8be76c78491a2..b1f50562ccba01753aa2527c79b0203665bdaee4 100644 (file)
@@ -11,6 +11,7 @@ use Getopt::Long qw(:config gnu_getopt no_ignore_case auto_abbrev);
 use IO::Handle; # ->autoflush
 use IO::Socket;
 use File::Spec;
+use IO::Poll qw(POLLIN POLLHUP);
 use POSIX qw(WNOHANG :signal_h F_SETFD);
 use Socket qw(IPPROTO_TCP SOL_SOCKET);
 STDOUT->autoflush(1);
@@ -754,4 +755,32 @@ sub write_pid ($) {
        do_chown($path);
 }
 
+sub stream_hup ($) {
+       my $ev = POLLIN | POLLHUP;
+       my $n = IO::Poll::_poll(0, fileno($_[0]) // return, $ev) or return;
+       return 1 if $ev & POLLHUP;
+
+       # n.b. POLLHUP isn't reliably detected, so check FIONREAD on POLLIN
+       if (defined(PublicInbox::Syscall::FIONREAD) && ($ev & POLLIN)) {
+               ioctl($_[0], PublicInbox::Syscall::FIONREAD, $n = "") //
+                       return;
+               return 1 if unpack('i', $n) == 0;
+       }
+       undef;
+}
+
+if (PublicInbox::Syscall->can('TCP_ESTABLISHED')) {
+       eval <<EOM;
+sub tcp_hup (\$) {
+       my \$buf = getsockopt(\$_[0], Socket::IPPROTO_TCP, Socket::TCP_INFO)
+               or return;
+       unpack('C', \$buf) != PublicInbox::Syscall::TCP_ESTABLISHED
+}
+EOM
+       warn "E: $@" if $@;
+}
+
+no warnings 'once';
+*tcp_hup = \&stream_hup if !__PACKAGE__->can('tcp_hup');
+
 1;
index 6a6347d8bdc5a396b0d440a07f096be02fba4d27..38b2e4f9a662ce2380af6ba75228e7a4f111c0a8 100644 (file)
@@ -45,6 +45,8 @@ sub env_for ($$$) {
                'pi-httpd.async' => 1,
                'pi-httpd.app' => $self->{app},
                'pi-httpd.warn_cb' => $self->{warn_cb},
+               'pi-httpd.ckhup' => $port ? \&PublicInbox::Daemon::tcp_hup :
+                               \&PublicInbox::Daemon::stream_hup,
        }
 }
 
index e5e3340fa11123e52c38d48135330196a6bf6154..c2e1888167ad1d861376262c95cf9c4fa54f3e14 100644 (file)
@@ -304,6 +304,8 @@ BEGIN {
        if ($^O eq 'linux') {
                %CONST = (
                        MSG_MORE => 0x8000,
+                       FIONREAD => 0x541b,
+                       TCP_ESTABLISHED => 1,
                        TMPL_cmsg_len => TMPL_size_t,
                        # cmsg_len, cmsg_level, cmsg_type
                        SIZEOF_cmsghdr => SIZEOF_int * 2 + SIZEOF_size_t,
@@ -318,6 +320,7 @@ BEGIN {
        } elsif ($^O =~ /\A(?:freebsd|openbsd|netbsd|dragonfly)\z/) {
                %CONST = (
                        TMPL_cmsg_len => 'L', # socklen_t
+                       FIONREAD => 0x4004667f,
                        SIZEOF_cmsghdr => SIZEOF_int * 3,
                        CMSG_DATA_off => SIZEOF_ptr == 8 ? '@16' : '',
                        TMPL_msghdr => 'PL' . # msg_name, msg_namelen
@@ -327,7 +330,11 @@ BEGIN {
                                TMPL_size_t. # msg_controllen
                                'i', # msg_flags
 
-               )
+               );
+               # *BSD uses `TCPS_ESTABLISHED', not `TCP_ESTABLISHED'
+               # dragonfly uses TCPS_ESTABLISHED==5, but it lacks TCP_INFO,
+               # so leave it unset on dfly
+               $CONST{TCP_ESTABLISHED} = 4 if $^O ne 'dragonfly';
        }
        $CONST{CMSG_ALIGN_size} = SIZEOF_size_t;
        $CONST{SIZEOF_cmsghdr} //= 0;
@@ -335,6 +342,7 @@ BEGIN {
        $CONST{CMSG_DATA_off} //= undef;
        $CONST{TMPL_msghdr} //= undef;
        $CONST{MSG_MORE} //= 0;
+       $CONST{FIONREAD} //= undef;
 }
 
 # SFD_CLOEXEC is arch-dependent, so IN_CLOEXEC may be, too
diff --git a/t/daemon.t b/t/daemon.t
new file mode 100644 (file)
index 0000000..46793bf
--- /dev/null
@@ -0,0 +1,55 @@
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use v5.12;
+use autodie;
+use PublicInbox::TestCommon;
+use Socket qw(SOCK_STREAM);
+require_ok 'PublicInbox::Daemon';
+use PublicInbox::IO qw(poll_in);
+
+# $fn is stream_hup or tcp_hup
+my $ck_hup = sub {
+       my ($s, $connect, $fn, $type) = @_;
+       my $c = $connect->();
+       poll_in $s; # needed on *BSD
+       my $addr = accept(my $acc, $s);
+       close $c;
+       my $ck = PublicInbox::Daemon->can($fn);
+       poll_in($acc) if $^O ne 'linux'; # netbsd needs this, at least...
+       ok $ck->($acc), "$fn detected close ($type)";
+       $c = $connect->();
+       syswrite $c, 'hi';
+       poll_in $s; # needed on *BSD
+       $addr = accept($acc, $s);
+       ok !$ck->($acc), "$fn false when still established ($type)";
+};
+
+if (1) {
+       my $tmpdir = tmpdir;
+       my $l = "$tmpdir/named.sock";
+       my $s = IO::Socket::UNIX->new(Listen => 5, Local => $l,
+                               Type => SOCK_STREAM)
+               or xbail "bind+listen($l): $!";
+       my $connect = sub {
+               IO::Socket::UNIX->new(Peer => $l, Type => SOCK_STREAM) or
+                       xbail "connect($l): $!";
+       };
+       $ck_hup->($s, $connect, 'stream_hup', 'UNIX');
+}
+
+{
+       my $s = tcp_server;
+       my $tcp_conn = sub { tcp_connect($s) };
+       $ck_hup->($s, $tcp_conn, 'stream_hup', 'TCP');
+       $ck_hup->($s, $tcp_conn, 'tcp_hup', 'TCP');
+}
+
+SKIP: {
+       $^O =~ /\A(?:linux|freebsd|netbsd)\z/ or
+               skip "no TCP_INFO support $^O", 1;
+       isnt \&PublicInbox::Daemon::stream_hup,
+               \&PublicInbox::Daemon::tcp_hup,
+               "stream_hup and tcp_hup are different on \$^O=$^O";
+}
+
+done_testing;