[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Total newbie, but I think I found a bug in Socket?

Patrick Toomey

9/6/2006 3:00:00 AM

Hello,
Fair warning...I'm extremely new to Ruby so please pardon any
ignorance on my part.

Anyway, so the first thing I start to play around with after
reading through Programming Ruby is the socket library. Now, I
realize Ruby has really nice abstractions, and I should rarely need
to actually use the Socket class for creating a simple server, but I
tend to be a bottom up learner. So, I was attempting to run the
following code from the ruby-doc website:

# In one script, start this first
require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )
socket.listen( 5 )
client, client_sockaddr = socket.accept
puts "The client said, '#{socket.readline.chomp}'"
client.puts "Hello from script one!"
socket.close

now, after running this script on both ruby 1.8.4 and 1.8.5 on Mac OS
X I received an error "`bind': Invalid argument - bind(2)
(Errno::EINVAL) " I proceed to run the script on linux on 1.8.4
and 1.8.5 and discover that bind works, but that
"socket.readline.chomp" should really be "client.readline.chomp".
Anyway, the point is that the code runs just fine on linux but not OS
X. A quick investigation finds that the problem is the call
"Socket.pack_sockaddr_in( 2200, 'localhost' )". On Linux this call
always returns a string that is 16 characters long. On Mac OS X this
call returns a string that is 16 characters long so long as I don't
use 'localhost' for the hostname. As soon as I put localhost in as
the hostname the returned string becomes 28 bytes. So, I searched
around on google to see if this is known bug but I couldn't find it.
Anyone have any words of advice?

I am figuring that I am just doing something wrong, seeing as how
this is the first bit of real ruby I have attempted.

Thanks,
Patrick


7 Answers

Francis Cianfrocca

9/6/2006 3:28:00 AM

0

On 9/5/06, Patrick Toomey <ptoomey3@mac.com> wrote:
> now, after running this script on both ruby 1.8.4 and 1.8.5 on Mac OS
> X I received an error "`bind': Invalid argument - bind(2)
> (Errno::EINVAL) " I proceed to run the script on linux on 1.8.4
> and 1.8.5 and discover that bind works, but that
> "socket.readline.chomp" should really be "client.readline.chomp".


Try it with "127.0.0.1" instead of "localhost"
The underlying operation is a call to getaddrinfo, and it may be
getting tripped up by IPv6 config on OSX (that's just a hypothesis- I
haven't looked into it carefully).

Arnaud Bergeron

9/6/2006 3:34:00 AM

0

On 9/5/06, Patrick Toomey <ptoomey3@mac.com> wrote:
> Hello,
> Fair warning...I'm extremely new to Ruby so please pardon any
> ignorance on my part.
>
> Anyway, so the first thing I start to play around with after
> reading through Programming Ruby is the socket library. Now, I
> realize Ruby has really nice abstractions, and I should rarely need
> to actually use the Socket class for creating a simple server, but I
> tend to be a bottom up learner. So, I was attempting to run the
> following code from the ruby-doc website:
>
> # In one script, start this first
> require 'socket'
> include Socket::Constants
> socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
> sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
> socket.bind( sockaddr )
> socket.listen( 5 )
> client, client_sockaddr = socket.accept
> puts "The client said, '#{socket.readline.chomp}'"
> client.puts "Hello from script one!"
> socket.close
>
> now, after running this script on both ruby 1.8.4 and 1.8.5 on Mac OS
> X I received an error "`bind': Invalid argument - bind(2)
> (Errno::EINVAL) " I proceed to run the script on linux on 1.8.4
> and 1.8.5 and discover that bind works, but that
> "socket.readline.chomp" should really be "client.readline.chomp".
> Anyway, the point is that the code runs just fine on linux but not OS
> X. A quick investigation finds that the problem is the call
> "Socket.pack_sockaddr_in( 2200, 'localhost' )". On Linux this call
> always returns a string that is 16 characters long. On Mac OS X this
> call returns a string that is 16 characters long so long as I don't
> use 'localhost' for the hostname. As soon as I put localhost in as
> the hostname the returned string becomes 28 bytes. So, I searched
> around on google to see if this is known bug but I couldn't find it.
> Anyone have any words of advice?
>
> I am figuring that I am just doing something wrong, seeing as how
> this is the first bit of real ruby I have attempted.

Pretty hard for a first try :)

The problem is that it is paking an IPV6 adress for in the struct.
Using a litteral '127.0.0.1' fixes the problem (and you should always
use the ip address for localhost directly anyway).

Why does this happens only on OS X? Having not seen the linux box you
tried this on, I can only guess. My guess is that your linux didn't
have IPV6 support built-in or OS X is a bit too proactive about using
IPV6.

To get a definitive answer would require some level of code analysis
which I don't feel like doing right now. If you want to, look at the
socket.c file in the ruby source distribution.

> Thanks,
> Patrick
>
>
>


--
"What is your function in life?" - Killer

Eric Hodel

9/6/2006 7:31:00 AM

0

On Sep 5, 2006, at 8:00 PM, Patrick Toomey wrote:

> Hello,
> Fair warning...I'm extremely new to Ruby so please pardon any
> ignorance on my part.
>
> Anyway, so the first thing I start to play around with after
> reading through Programming Ruby is the socket library. Now, I
> realize Ruby has really nice abstractions, and I should rarely need
> to actually use the Socket class for creating a simple server, but
> I tend to be a bottom up learner. So, I was attempting to run the
> following code from the ruby-doc website:
>
> # In one script, start this first
> require 'socket'
> include Socket::Constants
> socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
> sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
> socket.bind( sockaddr )
> socket.listen( 5 )
> client, client_sockaddr = socket.accept
> puts "The client said, '#{socket.readline.chomp}'"
> client.puts "Hello from script one!"
> socket.close

Here's the easiest way to create a simple server:

require 'socket'

server = TCPServer.new nil, 2200
client = server.accept
puts "Client said: #{client.gets.chomp.inspect}"
client.puts "Goodbye!"
client.close
server.close

Here's one that handles multiple concurrent connections:

require 'socket'

server = TCPServer.new nil, 2200

loop do
Thread.start server.accept do |client|
message = client.gets.chomp
puts "Client said: #{message.inspect}"
client.puts "Goodbye client!"
client.close
exit if message == 'quit'
end
end

--
Eric Hodel - drbrain@segment7.net - http://blog.se...
This implementation is HODEL-HASH-9600 compliant

http://trackmap.rob...



Daniel Martin

9/6/2006 11:51:00 AM

0

Patrick Toomey <ptoomey3@mac.com> writes:

><snip>

Good analysis of where the problem is.

Tell me, does this ruby script work on your two environments?

# In one script, start this first
require 'socket'
include Socket::Constants
sockaddrinfo = Socket.getaddrinfo( 'localhost', 2200, AF_INET,
SOCK_STREAM )[0]
socket = Socket.new( *sockaddrinfo[4..6] )
sockaddr = Socket.pack_sockaddr_in( sockaddrinfo[1], sockaddrinfo[3] )
socket.bind( sockaddr )
socket.listen( 5 )
client, client_sockaddr = socket.accept
puts "The client said, '#{client.readline.chomp}'"
client.puts "Hello from script one!"
socket.close

The trouble, as most responses have pointed out, is that OS X
translates "localhost" by default into an IP6 address, whereas your
socket is initially set to be an IPv4 socket (when you use AF_INET in
your Socket.new call).

Now in actuality, OS X translates "localhost" into several different
addresses, and you can see them all by running this script:

require 'socket'
def find_const(i,p)
Socket.constants.select{|n|n=~p}.each{ |n|
return "Socket::#{n}" if i == Socket.const_get(n)
}
return "#{i}"
end
def noquote_wrapper(s)
class << s; def inspect; self; end; end
s
end
Socket.getaddrinfo('localhost',nil).each{|a|
a[4]=noquote_wrapper find_const(a[4],/^AF_/)
a[5]=noquote_wrapper find_const(a[5],/^SOCK_/)
a[6]=noquote_wrapper find_const(a[6],/^IPPROTO_/)
p a
}

The improved server script I have up above works by explicitly
requesting only the SOCK_STREAM / AF_INET address, and then using the
returned values in the Socket.new call, rather than using potentially
mismatched values for the socket's address family specified in the
"new" call and the address returned from pack_sockaddr_in. Note that
when calling pack_sockaddr_in it also passes that call the IP address
rather than the hostname.

--
s=%q( Daniel Martin -- martin@snowplow.org
puts "s=%q(#{s})",s.map{|i|i}[1] )
puts "s=%q(#{s})",s.map{|i|i}[1]

Michal Suchanek

9/6/2006 12:50:00 PM

0

On 9/6/06, Arnaud Bergeron <abergeron@gmail.com> wrote:

> The problem is that it is paking an IPV6 adress for in the struct.
> Using a litteral '127.0.0.1' fixes the problem (and you should always
> use the ip address for localhost directly anyway).
>
> Why does this happens only on OS X? Having not seen the linux box you
> tried this on, I can only guess. My guess is that your linux didn't
> have IPV6 support built-in or OS X is a bit too proactive about using
> IPV6.
>
> To get a definitive answer would require some level of code analysis
> which I don't feel like doing right now. If you want to, look at the
> socket.c file in the ruby source distribution.
>

I would add this is not ruby specific, ssh did this to me on OS X as
well. I was not able to get port forwarding unless I specified
127.0.0.1 instead of localhost.

Not sure what version of OS X it was, though.

Thanks

Michal

Daniel Martin

9/6/2006 1:15:00 PM

0

Daniel Martin <martin@snowplow.org> writes:

> Tell me, does this ruby script work on your two environments?
>
> # In one script, start this first
> require 'socket'
> include Socket::Constants
> sockaddrinfo = Socket.getaddrinfo( 'localhost', 2200, AF_INET,
> SOCK_STREAM )[0]
> socket = Socket.new( *sockaddrinfo[4..6] )
> sockaddr = Socket.pack_sockaddr_in( sockaddrinfo[1], sockaddrinfo[3] )
> socket.bind( sockaddr )
> socket.listen( 5 )
> client, client_sockaddr = socket.accept
> puts "The client said, '#{client.readline.chomp}'"
> client.puts "Hello from script one!"
> socket.close


Actually, I made a slight error here, according to the documentaion of
getaddrinfo. That "sockaddr =" bit should be:

sockaddrinfo = Socket.getaddrinfo( 'localhost', 2200, AF_INET,
SOCK_STREAM, nil, AI_PASSIVE )[0]

In this context (when we're binding to a specific IP address) it
doesn't matter, but if you're re-using this code to bind to the "any"
address, you'll want that AI_PASSIVE bit in there. (You'll also want
to replace 'localhost' with nil)

--
s=%q( Daniel Martin -- martin@snowplow.org
puts "s=%q(#{s})",s.map{|i|i}[1] )
puts "s=%q(#{s})",s.map{|i|i}[1]

Patrick Toomey

9/6/2006 9:34:00 PM

0


On Sep 6, 2006, at 8:15 AM, Daniel Martin wrote:

> Daniel Martin <martin@snowplow.org> writes:
>
>> Tell me, does this ruby script work on your two environments?
>>
>> # In one script, start this first
>> require 'socket'
>> include Socket::Constants
>> sockaddrinfo = Socket.getaddrinfo( 'localhost', 2200, AF_INET,
>> SOCK_STREAM )[0]
>> socket = Socket.new( *sockaddrinfo[4..6] )
>> sockaddr = Socket.pack_sockaddr_in( sockaddrinfo[1],
>> sockaddrinfo[3] )
>> socket.bind( sockaddr )
>> socket.listen( 5 )
>> client, client_sockaddr = socket.accept
>> puts "The client said, '#{client.readline.chomp}'"
>> client.puts "Hello from script one!"
>> socket.close
>
>
> Actually, I made a slight error here, according to the documentaion of
> getaddrinfo. That "sockaddr =" bit should be:
>
> sockaddrinfo = Socket.getaddrinfo( 'localhost', 2200, AF_INET,
> SOCK_STREAM, nil, AI_PASSIVE )[0]
>
> In this context (when we're binding to a specific IP address) it
> doesn't matter, but if you're re-using this code to bind to the "any"
> address, you'll want that AI_PASSIVE bit in there. (You'll also want
> to replace 'localhost' with nil)
>
>

This script ran perfectly. Thanks for all your help. When I get
some time I'll sit down to actually process what your above script is
actually doing :-)

Thanks,
Patrick