ruby / xmlrpc

The Ruby standard library package 'xmlrpc'
Other
37 stars 26 forks source link

IPv6 #42

Closed Rotario closed 1 year ago

Rotario commented 1 year ago

Hi - I can't seem to get IPv6 working. Something changed when I updated my gems (not ruby) and now the URI Parsing isn't working with the bracket notation If I do

Net::HTTP.new('::', 8080).start
=> #<Net::HTTP :::8080 open=true>

The socket is opened and the connection works to an XMLRPC server If instead I do

XMLRPC::Client.new('[::]', '/RPC2', 8080).call('system.listMethods')
home/xxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/socket.rb:227:in `getaddrinfo': Failed to open TCP connection to [::]:8080 (getaddrinfo: Name or service not known) (SocketError)
/home/xxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/socket.rb:227:in `getaddrinfo': getaddrinfo: Name or service not known (SocketError)

I get this exception, which means the [::] IPv6 ip is being interpreted as a DNS hostname (the brackets must be trimmed)

If I monkey-patch XMLRPC::Client.net_http like this to trim the brackets manually

module XMLRPC
  class Client
    def net_http(host, port, proxy_host, proxy_port)
      Net::HTTP.new host.tr('[]', ''), port, proxy_host, proxy_port
    end
  end
end

Then the Net::HTTP instance is created correctly and the connection can be opened

XMLRPC::Client.new('[::]', '/path', 8080)
=> 
#<XMLRPC::Client:0x00007fd9e1098a68
 @auth=nil,
 @cookie=nil,
 @create=nil,
 @host="[::]",
 @http=#<Net::HTTP [::]:8080 open=false>,
 @http_header_extra=nil,
 @http_last_response=nil,
 @parser=nil,
 @password=nil,
 @path="/path",
 @port=8080,
 @proxy_host=nil,
 @proxy_port=nil,
 @timeout=30,
 @use_ssl=false,
 @user=nil>

But then when I try to call the client

XMLRPC::Client.new('[::]', '/path', 8080).call('system.listMethods')
/home/xxxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/uri/rfc3986_parser.rb:67:in `split': bad URI(is not URI?): "http://::/path" (URI::InvalidURIError)

I get an Invalid URI Error.

I can't seem to find the call point where a URI is built in call yet. Will try to monkey patch that to see if it works temporarily.

I have a feeling this is related to the age old bug here https://bugs.ruby-lang.org/issues/3788

kou commented 1 year ago

Why don't you use :: not [::] if the former work?

XMLRPC::Client.new('::', '/RPC2', 8080).call('system.listMethods')

(BTW, using :: for a client not a server is strange for me...)

XMLRPC::Client.new('[::]', '/path', 8080).call('system.listMethods')
/home/xxxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/uri/rfc3986_parser.rb:67:in `split': bad URI(is not URI?): "http://::/path" (URI::InvalidURIError)

Could you show full backtrace?

Rotario commented 1 year ago

Hi - yeah sorry [::] was a typo, [::1] is the correct IP This is all the backtrace I get

irb(main):005:0> XMLRPC::Client.new('[::1]', '/path', 8080).call('system.listMethods')
/home/xxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/socket.rb:227:in `getaddrinfo': Failed to open TCP connection to [::1]:8080 (getaddrinfo: Name or service not known) (SocketError)
/home/xxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/socket.rb:227:in `getaddrinfo': getaddrinfo: Name or service not known (SocketError)
herwinw commented 1 year ago

I feel like there are two things mixup up here. The bracketed IPv6 notation is used in case there is a port included, to distinguish the address and the port (Wikipedia). In case of XMLRPC::Client.new the parts are separated, so there is no need for these brackets.

XMLRPC::Client.new('::1', '/path', 8080).call('system.listMethods')

This works. (I tested it with nc -l ::1 8080 in a different console, I can see the request coming in, of course there is no reply). This is pretty much the same thing as @kou already said.

If you would instead use new_from_uri (or the more cryptically named new2), you would need the brackets:

XMLRPC::Client.new_from_uri('http://[::1]:8080/path').call('system.ListMethods')

Now this doesn't work due to a bug in this lib (I'll have a fix in a few minutes).

Rotario commented 1 year ago

thanks for your quick responses! If I try to run that client.new I get the following exception

 irb(main):002:0> XMLRPC::Client.new('::1', '/path', 8080).call('system.listMethods')
/home/xxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/uri/rfc3986_parser.rb:67:in `split': bad URI(is not URI?): "http://::1/path" (URI::InvalidURIError)

Also if I monkeypatch with the new_from_uri fix

irb(main):033:0> XMLRPC::Client.new2('http://[::1]:8080/RPC2').call('system.listMethods')
/home/xx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/socket.rb:227:in `getaddrinfo': Failed to open TCP connection to [::1]:8080 (getaddrinfo: Name or service not known) (SocketError)
/home/xxx/.rbenv/versions/3.1.0/lib/ruby/3.1.0/socket.rb:227:in `getaddrinfo': getaddrinfo: Name or service not known (SocketError)

Meaning I think [::1] is being interpreted as a DNS hostname, not an ipv6 literal

kou commented 1 year ago

Could you show full backtrace?

herwinw commented 1 year ago

And also: what versions of the relevant gems (net-http, uri, net-protocol, xmlrpc) are you running? The bug report starts with "Something changed when I updated my gems", which is not very concrete.

I tried this with a clean install of Ruby 3.1.0 via rbenv (which is like the only actual version I could distill from your backtraces), with only xmlrpc installed via rubygems, and XMLRPC::Client.new('::1', '/path', 8080).call('system.listMethods') works like intended. The new2/new_from_uri does not work, but that's expected since this is using the latest release without that fix. Upgrading the previously mentioned gems to the latest versions still works fine.

I would guess the combination of gems you've got is somehow broken, but with the limited amount of information in this case it's hard to tell for sure.

Rotario commented 1 year ago

Hi @herwinw and @kou thanks for your patience and sorry for the lack of attention!

I didn't have time to start digging through version numbers, but I updated xmlrpc to 0.3.3 and XMLRPC::Client.new worked thank you!

I'm actually using it in a rails app, and the issue actually looks like it comes from gem "sentry-ruby", "~> 5.11" weirdly.... it hijacks web requests with its own code which doesn't handle IPv6 correctly it seems. I'm going to have to try to work around this

"/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/uri/rfc3986_parser.rb:67:in `split'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/uri/rfc3986_parser.rb:72:in `parse'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/uri/common.rb:188:in `parse'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/sentry-ruby-5.11.0/lib/sentry/net/http.rb:80:in `extract_request_info'",                                                  
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/sentry-ruby-5.11.0/lib/sentry/net/http.rb:33:in `block in request'",                                                      
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/sentry-ruby-5.11.0/lib/sentry/hub.rb:102:in `with_child_span'",                                                           
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/sentry-ruby-5.11.0/lib/sentry-ruby.rb:456:in `with_child_span'",                                                          
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/sentry-ruby-5.11.0/lib/sentry/net/http.rb:32:in `request'",                                                               
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/net/http.rb:1484:in `request_post'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/xmlrpc-0.3.3/lib/xmlrpc/client.rb:498:in `do_rpc'",                                                                       
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/xmlrpc-0.3.3/lib/xmlrpc/client.rb:287:in `call2'",                                                                        
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/xmlrpc-0.3.3/lib/xmlrpc/client.rb:268:in `call'",
 "(irb):3:in `<main>'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/workspace.rb:119:in `eval'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/workspace.rb:119:in `evaluate'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/context.rb:476:in `evaluate'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:577:in `block (2 levels) in eval_input'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:770:in `signal_status'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:558:in `block in eval_input'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/ruby-lex.rb:268:in `block (2 levels) in each_top_level_statement'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/ruby-lex.rb:250:in `loop'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/ruby-lex.rb:250:in `block in each_top_level_statement'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/ruby-lex.rb:249:in `catch'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb/ruby-lex.rb:249:in `each_top_level_statement'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:557:in `eval_input'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:491:in `block in run'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:490:in `catch'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:490:in `run'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/3.1.0/irb.rb:419:in `start'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/railties-7.0.7.2/lib/rails/commands/console/console_command.rb:74:in `start'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/railties-7.0.7.2/lib/rails/commands/console/console_command.rb:19:in `start'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/railties-7.0.7.2/lib/rails/commands/console/console_command.rb:106:in `perform'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/thor-1.2.2/lib/thor/command.rb:27:in `run'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/thor-1.2.2/lib/thor/invocation.rb:127:in `invoke_command'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/thor-1.2.2/lib/thor.rb:392:in `dispatch'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/railties-7.0.7.2/lib/rails/command/base.rb:87:in `perform'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/railties-7.0.7.2/lib/rails/command.rb:48:in `invoke'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/railties-7.0.7.2/lib/rails/commands.rb:18:in `<main>'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'",
 "/home/rowan/.rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'",
 "bin/rails:4:in `<main>'"
Rotario commented 1 year ago

I've raised an issue with Sentry. Thanks for the new_from_uri fix though!

Rotario commented 1 year ago

Sorry - XMLRPC still seems inconsistent in some way. The Sentry code first checks the uri attribute in the underlying Net::HTTP instance then falls back to doing URI.parse since XMLRPC::Client doesn't set this.

If the xmlrpc client set the uri attribute this would work. I don't know if this is something for Sentry to change or for this to be changed.

Personally as a user it can be a little confusing when XMLRPC::Client.new(args) doesn't work but Net::HTTP.get(args) does for similar args

herwinw commented 1 year ago

Personally as a user it can be a little confusing when XMLRPC::Client.new(args) doesn't work but Net::HTTP.get(args) does for similar args

I guess sentry-ruby only hooks into some methods (I haven't looked too deep into it). I've tried the following script:

require 'sentry-ruby'

Sentry.init

http = Net::HTTP.new('::1', 8080)
http.request_post('/path', 'foo=1')

Which follows a code path similar to how xmlrpc/client works. I'm getting an error with the same backtrace as you were showing, without even using xmlrpc.

The error occurs in the following code of sentry-ruby:

def extract_request_info(req)
  uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{address}#{req.path}")
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s

It tries to construct a new URI object with the address, which is an IPv6 literal, so that breaks. It skips the port as well, so I guess it's broken in more than one way.

Given that I managed to reproduce your issue without using this gem, I would say this really is an issue in sentry-ruby and not in xmlrpc. Feel free to use my previous example in a bug report over there. I don't think there is much over here we can do to fix your issue.

herwinw commented 1 year ago

Upstream bug report: https://github.com/getsentry/sentry-ruby/issues/2163

kou commented 1 year ago

Can we close this?

Rotario commented 1 year ago

Yeah please! Your colleague is right it's an issue with sentry I'm using a workaround right now

On Tue, 7 Nov 2023, 20:44 Sutou Kouhei, @.***> wrote:

Can we close this?

— Reply to this email directly, view it on GitHub https://github.com/ruby/xmlrpc/issues/42#issuecomment-1800039608, or unsubscribe https://github.com/notifications/unsubscribe-auth/AHA6QA4DCIMBI744HHHGIWDYDKMS7AVCNFSM6AAAAAA63DKQRSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMBQGAZTSNRQHA . You are receiving this because you modified the open/close state.Message ID: @.***>