像主机文件一样覆盖特定域的DNS,但不使用主机文件

时间:2017-04-27 20:40:02

标签: perl dns lwp lwp-useragent

我需要从同一服务器向特定域发出一系列并行化Web请求,但要控制这些请求实际转到的IP地址。最初,我想出了一个方案,我会特别请求我想要的IP,然后在请求上手动设置Host: www.example.com标头,并使用一系列处理程序来确保发出的重定向遵循相同的模式。

这似乎工作了一段时间,但最近我遇到了重定向到HTTPS的问题。握手将失败,请求依次。 我尝试过以各种方式禁用SSL验证,包括:

local $ENV{ PERL_LWP_SSL_VERIFY_HOSTNAME } = 0;
local $ENV{ HTTPS_DEBUG }                  = 1;

$ua->ssl_opts(
    SSL_ca_file => Mozilla::CA::SSL_ca_file(),
    verify_hostname => 0,
    SSL_verify_mode => 0x00,
);

IO::Socket::SSL::set_ctx_defaults(
    SSL_verifycn_scheme => 'www',
    SSL_verify_mode => 0,
);        

我也尝试使用LWP::UserAgent::DNS::Hosts来解决问题,但它仍然存在。

< edit>我应该注意,为什么关闭SSL的对等验证无法解决问题的原因可能是因为某些原因请求这种方式实际上导致握手失败,而不是验证失败。点< /编辑>

有一件事是在/etc/hosts中创建一个条目,将域指向适当的IP,但这是不实际的,因为我可能需要同时运行数十或数百个测试相同的域名。

有没有办法模拟向/etc/hosts添加条目的功能,该条目不涉及专门请求IP并覆盖Host: ... HTTP标头?

编辑:SSL调试信息

DEBUG: .../IO/Socket/SSL.pm:1914: new ctx 140288835318480
DEBUG: .../IO/Socket/SSL.pm:402: socket not yet connected
DEBUG: .../IO/Socket/SSL.pm:404: socket connected
DEBUG: .../IO/Socket/SSL.pm:422: ssl handshake not started
DEBUG: .../IO/Socket/SSL.pm:455: not using SNI because hostname is unknown
DEBUG: .../IO/Socket/SSL.pm:478: set socket to non-blocking to enforce timeout=180
DEBUG: .../IO/Socket/SSL.pm:491: Net::SSLeay::connect -> -1
DEBUG: .../IO/Socket/SSL.pm:501: ssl handshake in progress
DEBUG: .../IO/Socket/SSL.pm:511: waiting for fd to become ready: SSL wants a read first
DEBUG: .../IO/Socket/SSL.pm:531: socket ready, retrying connect
DEBUG: .../IO/Socket/SSL.pm:491: Net::SSLeay::connect -> -1
DEBUG: .../IO/Socket/SSL.pm:1388: SSL connect attempt failed with unknown error

DEBUG: .../IO/Socket/SSL.pm:497: fatal SSL error: SSL connect attempt failed with unknown error error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
DEBUG: .../IO/Socket/SSL.pm:1948: free ctx 140288835318480 open=140288835318480
DEBUG: .../IO/Socket/SSL.pm:1953: free ctx 140288835318480 callback
DEBUG: .../IO/Socket/SSL.pm:1956: OK free ctx 140288835318480

在我得到的回复中:

  

无法连接到redacted.org:443

     

SSL connect attempt failed with unknown error error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure at /System/Library/Perl/Extras/5.18/LWP/Protocol/http.pm line 51.

它在我们的服务器上同样失败(使用旧版本的Perl,我不会在这里透露,因为它似乎无关紧要)。

服务器最初使用301重定向到HTTPS站点来响应非HTTPS请求。然后发生故障。我将发布复制代码,删除我的请求的具体细节,但任何将非HTTPS流量重定向到HTTPS的网站都应该足够。

use IO::Socket::SSL qw/ debug4 /;
use LWP::UserAgent;
use LWP::UserAgent::DNS::Hosts;
use HTTP::Request;
use Mozilla::CA;
use Data::Dumper;

LWP::UserAgent::DNS::Hosts->register_hosts(
    'recacted.org' => '127.0.0.1', # no I am not redirecting to loopback in reality, this is anonymized
    'www.redacted.org' => '127.0.0.1',
);

LWP::UserAgent::DNS::Hosts->enable_override;

my $ua = LWP::UserAgent->new;
$ua->ssl_opts( SSL_ca_file => Mozilla::CA::SSL_ca_file() );

my $request = HTTP::Request->new(GET => 'http://redacted.org/');

my $response = $ua->request($request);

print $response->content; #Dumper ( $response->is_success ? $response->headers : $response );

同样,这不是生产代码,只是足以重现问题的代码。它似乎与SSL验证无关,而且无法协商请求,可能是因为LWP::UserAgent::DNS::Hosts正在完成我正在做的事情:将请求目标更改为所需的IP,以及然后手动编写Host: ...标题。为什么这会导致SSL握手失败,我不知道。

在我的本地机器上调试

openssl version -a: 1.0.2j 26 Sep 2016
IO::Socket::SSL->VERSION == 1.966
Net::SSLeay->VERSION == 1.72

在我们的服务器上

openssl version -a: 1.0.1t 3 May 2016
IO::Socket::SSL->VERSION == 1.76
Net::SSLeay->VERSION == 1.48

1 个答案:

答案 0 :(得分:2)

鉴于它适用于明确的/etc/hosts文件,但不能仅仅替换PeerAddr或使用LWP::UserAgent::DNS::Hosts,这看起来像是SNI扩展的问题。此TLS扩展用于向TLS服务器提供所请求的主机名(类似于HTTP Host标头),以便它可以选择适当的证书。如果缺少此SNI扩展,某些服务器将返回默认证书,而其他服务器会抛出错误,如本例所示。

此修复是使用SSL_hostname中的ssl_opts提供主机名。这样的修复可能也有助于LWP::UserAgent::DNS::Hosts,即LWP/Protocol/https/hosts.pm

12    if (my $peer_addr = LWP::UserAgent::DNS::Hosts->_registered_peer_addr($host)) {
13        push @opts, (
14            PeerAddr          => $peer_addr,
15            Host              => $host,
16            SSL_verifycn_name => $host,
NEW           SSL_hostname      => $host,   # for SNI
17        );
18    }