Fix support for HTTP proxies (#11245)
* Disable incorrect check for hidden services in Socket Hidden services can only be accessed with an HTTP proxy, in which case the host seen by the Socket class will be the proxy, not the target host. Hidden services are already filtered in `Request#initialize`. * Use our Socket class to connect to HTTP proxies Avoid the timeout logic being bypassed * Add support for IP addresses in Request::Socket * Refactor a bit, no need to keep the DNS resolver around
This commit is contained in:
parent
23aeef52cc
commit
58276715be
|
@ -30,7 +30,8 @@ class Request
|
||||||
@verb = verb
|
@verb = verb
|
||||||
@url = Addressable::URI.parse(url).normalize
|
@url = Addressable::URI.parse(url).normalize
|
||||||
@http_client = options.delete(:http_client)
|
@http_client = options.delete(:http_client)
|
||||||
@options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket })
|
@options = options.merge(socket_class: use_proxy? ? ProxySocket : Socket)
|
||||||
|
@options = @options.merge(Rails.configuration.x.http_client_proxy) if use_proxy?
|
||||||
@headers = {}
|
@headers = {}
|
||||||
|
|
||||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service?
|
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service?
|
||||||
|
@ -177,47 +178,49 @@ class Request
|
||||||
class Socket < TCPSocket
|
class Socket < TCPSocket
|
||||||
class << self
|
class << self
|
||||||
def open(host, *args)
|
def open(host, *args)
|
||||||
return super(host, *args) if thru_hidden_service?(host)
|
|
||||||
|
|
||||||
outer_e = nil
|
outer_e = nil
|
||||||
port = args.first
|
port = args.first
|
||||||
|
|
||||||
Resolv::DNS.open do |dns|
|
addresses = []
|
||||||
dns.timeouts = 5
|
begin
|
||||||
|
addresses = [IPAddr.new(host)]
|
||||||
|
rescue IPAddr::InvalidAddressError
|
||||||
|
Resolv::DNS.open do |dns|
|
||||||
|
dns.timeouts = 5
|
||||||
|
addresses = dns.getaddresses(host).take(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
addresses = dns.getaddresses(host).take(2)
|
addresses.each do |address|
|
||||||
|
begin
|
||||||
|
check_private_address(address)
|
||||||
|
|
||||||
|
sock = ::Socket.new(address.is_a?(Resolv::IPv6) ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
|
||||||
|
sockaddr = ::Socket.pack_sockaddr_in(port, address.to_s)
|
||||||
|
|
||||||
|
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
||||||
|
|
||||||
addresses.each do |address|
|
|
||||||
begin
|
begin
|
||||||
raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
|
sock.connect_nonblock(sockaddr)
|
||||||
|
rescue IO::WaitWritable
|
||||||
sock = ::Socket.new(address.is_a?(Resolv::IPv6) ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
|
if IO.select(nil, [sock], nil, Request::TIMEOUT[:connect])
|
||||||
sockaddr = ::Socket.pack_sockaddr_in(port, address.to_s)
|
begin
|
||||||
|
sock.connect_nonblock(sockaddr)
|
||||||
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
rescue Errno::EISCONN
|
||||||
|
# Yippee!
|
||||||
begin
|
rescue
|
||||||
sock.connect_nonblock(sockaddr)
|
|
||||||
rescue IO::WaitWritable
|
|
||||||
if IO.select(nil, [sock], nil, Request::TIMEOUT[:connect])
|
|
||||||
begin
|
|
||||||
sock.connect_nonblock(sockaddr)
|
|
||||||
rescue Errno::EISCONN
|
|
||||||
# Yippee!
|
|
||||||
rescue
|
|
||||||
sock.close
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
else
|
|
||||||
sock.close
|
sock.close
|
||||||
raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
|
raise
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
sock.close
|
||||||
|
raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
|
||||||
end
|
end
|
||||||
|
|
||||||
return sock
|
|
||||||
rescue => e
|
|
||||||
outer_e = e
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return sock
|
||||||
|
rescue => e
|
||||||
|
outer_e = e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -230,11 +233,21 @@ class Request
|
||||||
|
|
||||||
alias new open
|
alias new open
|
||||||
|
|
||||||
def thru_hidden_service?(host)
|
def check_private_address(address)
|
||||||
Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(host)
|
raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private_constant :ClientLimit, :Socket
|
class ProxySocket < Socket
|
||||||
|
class << self
|
||||||
|
def check_private_address(_address)
|
||||||
|
# Accept connections to private addresses as HTTP proxies will usually
|
||||||
|
# be on local addresses
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private_constant :ClientLimit, :Socket, :ProxySocket
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue