Skip Menu |

This queue is for tickets about the Perl-Dist-Strawberry CPAN distribution.

Report information
The Basics
Id: 105603
Status: resolved
Priority: 0/
Queue: Perl-Dist-Strawberry

People
Owner: Nobody in particular
Requestors: rjray [...] blackperl.com
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: (no value)



Subject: Strawberry does not always resolve 'localhost' correctly (may be in LWP)
I recently got a bug report for my RPC-XML module, in which the user was trying to build/install on Windows with Strawberry Perl 5.20.2. After much poking and prodding, I finally realized that when I created a daemon with HTTP::Daemon, it was taking 'localhost' and resolving it to 127.0.0.1. That is, I would create the object as: $server = HTTP::Daemon->new(LocalHost => 'localhost', LocalPort => 9000); and then printing $server->url would give me: http://127.0.0.1:9000/ The problem I was having came when creating a client to connect to that server. The client did NOT resolve localhost to 127.0.0.1, and instead failed to connect. I created the client with: $client = LWP::UserAgent->new(POST => 'http://localhost:9000'); If, instead, I created the client as: $client = LWP::UserAgent->new(POST => $server->url); it would work fine. The attached file shows this. It is stripped down from what it would be if I were using RPC::XML classes instead, but it shows the problem. The series of lines from 28-30, and again from 38-40, create request objects. As configured in this file, you will see the failure. If you comment-out lines 30 and 40, and uncomment 28 and 38, the script will run without error. (If you instead uncomment lines 29 and 39, you'll see a different bug that I'll be reporting in a different RT ticket.) I cannot tell if this problem is due to something in the HTTP::Request library vs. HTTP::Daemon, or if it traces back to different networking syscalls being made in the different cases. I have not tried this on the 5.22.0.1 build yet. Randy -- Randy J. Ray rjray@blackperl.com randy.j.ray@gmail.com
Subject: socket-fail.pl
#!/usr/bin/env perl use strict; use warnings; use LWP::UserAgent; use HTTP::Daemon; use HTTP::Request; use HTTP::Response; use HTTP::Status; my $server = HTTP::Daemon->new( ReuseAddr => 1, LocalHost => 'localhost', LocalPort => 9000, Listen => 5 ); my $url = $server->url; die "Failed to create server object\n" if (! $server); my $child = fork; if (! defined $child) { die "fork() failed: $!\n"; } elsif ($child) { # Parent my $UA = LWP::UserAgent->new; #my $req = HTTP::Request->new(HEAD => $url); #my $req = HTTP::Request->new(HEAD => $server->url); my $req = HTTP::Request->new(HEAD => 'http://localhost:9000'); print ">>> Sending HEAD request\n"; my $res = $UA->request($req); print ">>> Headers from HEAD request:\n\t"; printf "%d %s\n\t", $res->code, $res->message; print $res->headers_as_string("\n\t"), "\n"; sleep 1; printf ">>> Creating POST request to %s\n", $url; #$req = HTTP::Request->new(POST => $url); #$req = HTTP::Request->new(POST => $server->url); $req = HTTP::Request->new(POST => 'http://localhost:9000'); print ">>> Sending POST request\n"; $res = $UA->request($req); print ">>> Full message from POST request:\n\t"; print $res->as_string("\n\t"), "\n"; } else { # Child $server->timeout(1); while (1) { my $conn = $server->accept; printf "<<< Accept: %s\n", ($conn || '(none)'); next if (! $conn); my $req; while ($conn and $req = $conn->get_request('headers only')) { my $res = HTTP::Response->new; $res->code(RC_OK); $res->message('OK'); $res->header(Accept => 'text/xml'); $res->content_type('text/xml'); if ($req->method eq 'HEAD') { print "<<< Sending HEAD response\n"; $conn->send_response($res); } elsif ($req->method eq 'POST') { print "<<< Sending POST response\n"; $res->content('Success'); $conn->send_response($res); } } my $eval_return = eval { local $SIG{PIPE} = sub { die "<<< server_loop: Caught SIGPIPE\n"; }; $conn->close; 1; }; if ((! $eval_return) && $@) { warn "<<< Cannot close connection: $@\n"; } undef $conn; } } kill 'KILL', $child; exit;
I forgot to mention this: I also tried my code and this attached example code with ActiveState Perl, also version 5.20.2. AS Perl did not exhibit any of the problems that Strawberry did; both the client and server did the proper resolving of localhost. Randy -- Randy J. Ray rjray@blackperl.com randy.j.ray@gmail.com
Subject: Re: [rt.cpan.org #105603] Strawberry does not always resolve 'localhost' correctly (may be in LWP)
Date: Thu, 02 Jul 2015 10:29:33 +0200
To: bug-Perl-Dist-Strawberry [...] rt.cpan.org
From: kmx <kmx [...] cpan.org>
Hi Randy, the localhost issue you are describing appears on a MS Windows box with both IPv4 + IPv6 (unfortunately even if you do not have IPv6 connectivity your Windows box has very likely IPv6 "somehow" configured). The thing is that with both IPv4 and IPv6 the resolution of name 'localhost' is in most cases '::1' (IPv6 loopback) not '127.0.0.1' The trouble in your case is IMO an inconsistency: - daemon somehow resolves localhost to 127.0.0.1 - client in some other way resolves localhost to ::1 - and they are obviously not able to connect Anyway I am not sure what exactly causes this situation but for example HTTP::Server::Simple (not sure if it is involved in your case) uses: sub lookup_localhost { my $self = shift; my $local_sockaddr = getsockname( $self->stdio_handle ); my ( undef, $localiaddr ) = sockaddr_in($local_sockaddr); $self->host( gethostbyaddr( $localiaddr, AF_INET ) || "localhost"); $self->{'local_addr'} = inet_ntoa($localiaddr) || "127.0.0.1"; } -- kmx
That's interesting, and it might explain why (on rare occasion) I've seen this on non-MS platforms as well. But it doesn't explain why ActiveState Perl works correctly. Presumably, AS is using the same system-level libs to do name resolution? I will try to see if I can distill this down further. I lack the tools and the know-how to debug a threaded application, so I'll try to split this into two smaller pieces. Randy -- Randy J. Ray rjray@blackperl.com randy.j.ray@gmail.com
I can confirm that IPv6 is at least part of the issue here. I modified the script to let me run the client and server separately, and thus I could step through the debugger without threading/forking issues. Here is what I have found. from running both the client side and server side through the debugger: * The path of execution through LWP::UserAgent leads to IO::Socket::IP being used for the actual creation of the socket. The execution path for HTTP::Daemon does not. * IO::Socket::IP uses getaddrinfo() (inside the _io_socket_ip__configure method), which returns two records for "localhost": one for IPv6 and one for IPv4, in that order. * HTTP::Daemon, by way of IO:Socket::INET::configure(), simply passes the hostname to inet_aton() to get the encoded address. In HTTP::Daemon, it only ever tries to bind to the IPv4 address. In LWP::UserAgent, by way of IO::Socket::IP, it tries (in order) IPv6 and IPv4. However, why the IPv4 bind fails is a mystery. From my reading of the code, when IPv6 failed the IPv4 address should have been successful. So, there appear to be two issues here, neither of which are in Strawberry Perl after all: 1. HTTP::Daemon, unlike LWP::UserAgent, is not using IO::Socket::IP as a means of transparently handling IPv6 as well as IPv4. 2. IO::Socket::IP is not correctly binding the IPv4 socket for an address after the IPv6 bind fails. I will pursue these two issues with those distributions. You can mark this ticket closed, since it isn't a Strawberry issue after all. Randy -- Randy J. Ray rjray@blackperl.com randy.j.ray@gmail.com