From: Eric Wong Date: Tue, 28 Jan 2025 08:10:06 +0000 (+0000) Subject: daemon: check connections WIP X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=10394e508575fd7cc78d0255d39b04c833efcdbb;p=thirdparty%2Fpublic-inbox.git daemon: check connections WIP --- diff --git a/MANIFEST b/MANIFEST index 30adab809..0a0f8a1d2 100644 --- 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 diff --git a/devel/sysdefs-list b/devel/sysdefs-list index 1345e0ba2..f5c3d8342 100755 --- a/devel/sysdefs-list +++ b/devel/sysdefs-list @@ -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 #include #include -#include +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_SYS_FILIO_H +# include +#endif #ifdef __linux__ # include # include @@ -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); diff --git a/lib/PublicInbox/Daemon.pm b/lib/PublicInbox/Daemon.pm index 57f01d2cb..b1f50562c 100644 --- a/lib/PublicInbox/Daemon.pm +++ b/lib/PublicInbox/Daemon.pm @@ -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 <can('tcp_hup'); + 1; diff --git a/lib/PublicInbox/HTTPD.pm b/lib/PublicInbox/HTTPD.pm index 6a6347d8b..38b2e4f9a 100644 --- a/lib/PublicInbox/HTTPD.pm +++ b/lib/PublicInbox/HTTPD.pm @@ -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, } } diff --git a/lib/PublicInbox/Syscall.pm b/lib/PublicInbox/Syscall.pm index e5e3340fa..c2e188816 100644 --- a/lib/PublicInbox/Syscall.pm +++ b/lib/PublicInbox/Syscall.pm @@ -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 index 000000000..46793bfa6 --- /dev/null +++ b/t/daemon.t @@ -0,0 +1,55 @@ +# Copyright (C) all contributors +# License: AGPL-3.0+ +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;