Bug#1025769: HTTP::Server::Simple vs. support for Unix domain sockets
Ivan Shmakov
ivan at siamics.net
Thu Dec 8 20:07:38 GMT 2022
Package: libhttp-server-simple-perl
Version: 0.52-2
Severity: minor
[Please do not Cc: me, as I’m “on the list,” so to say, and
I try to reserve my inbox for private communication only.
I’d have set up Mail-Followup-To:, but there doesn’t seem
to be a way to make it point to the report being filed.]
In true multiuser setups, it’s much easier to control access
to Unix domain sockets than to INET6 and INET ones: for the
first, chmod(1) and chacl(1) can be used, while the second
is likely to require configuring nftables or iptables as root.
Attempting to implement Unix domain socket support by
subclassing HTTP::Server::Simple uncovered several issues
with the latter, which I’m going to describe in this report.
(Feel free to clone it and address them separately as you
see fit.)
As an aside, note that both Apache mod_proxy (as of 2.4.7)
and Lighttpd mod_proxy provide support for passing HTTP
traffic to a Unix domain socket; consider, e. g.:
## Lighttpd
$HTTP["url"] =~ "^/(example)(/|$)" {
proxy.server = ("" => (("host" => "/where/is/%1.socket")))
}
## From http://httpd.apache.org/docs/2.4/mod/mod_proxy.html
<FilesMatch "\.php$">
# Unix sockets require 2.4.7 or later
SetHandler "proxy:unix:/path/to/app.sock|fcgi://localhost/"
</FilesMatch>
Making HTTP requests via a Unix socket is supported by
curl(1) as well, e. g.:
$ curl --unix-socket ~/my.server.socket http://-/foo
The first issue is that while most of the HTTP::Server::Simple
functionality is implemented as separate methods, easy to
override in a subclass, calls to sockaddr_in6 and sockaddr_in
are hardcoded into _process_request:
421 my $remote_sockaddr = getpeername( $self->stdio_handle );
422 my $family = sockaddr_family($remote_sockaddr);
423
424 my ( $iport, $iaddr ) = $remote_sockaddr
425 ? ( ($family == AF_INET6) ? sockaddr_in6($remote_sockaddr)
426 : sockaddr_in($remote_sockaddr) )
427 : (undef,undef);
I believe that this code should be split off into a separate
method (or at least the sockaddr_in call should be explicitly
guarded as sockaddr_in6 already is), like:
sub peer_identify
{
my ($self, $peer) = @_;
my $remote_sockaddr = getpeername( $self->stdio_handle );
my $family = sockaddr_family($remote_sockaddr);
my ( $iport, $iaddr )
= ( ($family == AF_INET6) ? sockaddr_in6 ($remote_sockaddr)
($family == AF_INET) ? sockaddr_in ($remote_sockaddr)
: (undef, undef) );
...
return ("peername" => ..., "peeraddr" => ..., "peerport" => ...);
}
This is critical to my UNIX subclass [1], as the (currently
unguarded) call to sockaddr_in on a Unix name results in an
exception, which I worked-around by overriding
Socket::unpack_sockaddr_in with a function that returns undef
if the original function throws an exception on the value
passed while unpack_sockaddr_un doesn’t (see [1].)
[1] http://am-1.org/~ivan/src/iroca-2022/lib/HTTP/Server/Simple/UNIX.pm
[2] http://am-1.org/~ivan/src/iroca-2022/test/53-un-serv.perl
Another issue is that the documentation suggests that
peername and peeraddr may be different values (presumably
the former would be an IP resolved into a hostname via rDNS,
while the latter is the address in the numeric form):
563 ITEM/METHOD Set to Example
…
570 port Received Port 80, 8080
571 peername Remote name "200.2.4.5", "foo.com"
572 peeraddr Remote address "200.2.4.5", "::1"
573 peerport Remote port 42424
574 localname Local interface "localhost", "myhost.com"
This is not the case the way _process_request is currently
implemented:
447 my ( $file, $query_string )
448 = ( $request_uri =~ /([^?]*)(?:\?(.*))?/s ); # split at ?
449
450 $self->setup(
…
458 peername => $peeraddr,
459 peeraddr => $peeraddr,
460 peerport => $iport,
461 );
And before that, $peeraddr is obtained from Socket::getnameinfo:
429 my $loopback = ($family == AF_INET6) ? "::1" : "127.0.0.1";
430 my $peeraddr = $loopback;
431 if ($iaddr) {
432 my ($host_err,$addr, undef) = Socket::getnameinfo($remote_sockaddr,Socket::NI_NUMERICHOST);
433 warn ($host_err) if $host_err;
434 $peeraddr = $addr || $loopback;
435 }
AIUI, the end result is that /both/ peername and peeraddr will
be the numeric IP address (thanks to the Socket::NI_NUMERICHOST
flag used.)
This behavior (which involves no rDNS lookups) I find
preferable, but I think it should at least be documented
(and better still, configurable. Then again, should
peer_identify be a separate method, it would be trivial for
the module user to override it as they see fit.)
The third issue was uncovered by the test suite [2] I wrote
for my subclass; consider:
383 sub restart {
384 my $self = shift;
385
386 close HTTPDaemon;
…
397 # Server simple
398 # do the exec. if $0 is not executable, try running it with $^X.
399 exec {$0}( ( ( -x $0 ) ? () : ($^X) ), $0, @ARGV );
400 }
The indirect object to exec is the program to be executed, and
hence should be $^X when $0 is not executable (i. e., -x $0
is false), like:
exec { (-x $0) ? $0 : $^X }( ( ( -x $0 ) ? () : ($^X) ), $0, @ARGV );
As is, running the server with $ perl myscript.perl (where
myscript.perl lacks the applicable +x bit) and sending SIGHUP
to the process results in its termination, not restart.
As currently implemented, the module relies on (internal
and undocumented) HTTPDaemon filehandle being the listening
socket. Naturally, in my subclass I have to rely on that
inside knowledge in the substitute setup_listener method.
It would’ve been cleaner were a FileHandle reference be used
instead; such as by replacing HTTPDaemon throughout the code
with $self->{'_listener_handle'} and providing a (documented)
interface to access it, like:
sub listener_handle {
my $self = shift;
$self->{'_listener_handle'} = shift if (@_);
return $self->{'_listener_handle'};
}
Lastly, the package’s absolute dependency on libcgi-pm-perl
is arguably at odds with Policy 7.2:
http://debian.org//doc/debian-policy/ch-relationships.html
Depends
This declares an absolute dependency. […]
The Depends field should be used if the depended-on package
is required for the depending package to provide a significant
amount of functionality.
So far as I can tell, it’s perfectly possible to subclass
and use HTTP::Server::Simple without libcgi-pm-perl, except
perhaps with Net::Server (I’m not familiar with the latter
and won’t claim understanding of what the run method does
in the case net_server is supplied.)
For instance, the aforementioned test suite [2] completes
successfully (aside of the SIGHUP issue above) even though
I’ve circumvented the dependency by creating an otherwise
empty Provides: libcgi-pm-perl package [3] with nope.sh [4]:
$ fakeroot -- nope libcgi-pm-perl
[3] http://am-1.org/~ivan/dist/no-libcgi-pm-perl_0.1_all.deb
[4] http://am-1.org/~ivan/src/misc-utils-is-2020/nope.sh
The Recommends: relation seems like a better fit in this case:
Recommends
This declares a strong, but not absolute, dependency.
The Recommends field should list packages that would be found
together with this one in all but unusual installations.
--
FSF associate member #7257 http://am-1.org/~ivan/
More information about the pkg-perl-maintainers
mailing list