UDPSocket broadcast to 127.0.0.255

(Joel VanderWerf) #1

Is my network stack screwy, or is there something strange about
UDPSockets in ruby when broadcasting to 127.0.0.255?

irb(main):001:0> require 'socket'
=> true
irb(main):002:0> s = UDPSocket.open
=> #<UDPSocket:0xb7d5f9cc>
irb(main):003:0> s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST,
true)
=> 0
irb(main):004:0> s.connect '127.0.0.255', 12345
=> 0
irb(main):005:0> s.send "foo", 0
=> 3
irb(main):006:0> s.send "foo", 0
Errno::ECONNREFUSED: Connection refused - send(2)
        from (irb):6:in `send'
        from (irb):6
irb(main):007:0> s.send "foo", 0
=> 3
irb(main):008:0> s.send "foo", 0
Errno::ECONNREFUSED: Connection refused - send(2)
        from (irb):8:in `send'
        from (irb):8
irb(main):009:0> s.send "foo", 0
=> 3
irb(main):010:0> s.send "foo", 0
Errno::ECONNREFUSED: Connection refused - send(2)
        from (irb):10:in `send'
        from (irb):10
irb(main):011:0> s.send "foo", 0
=> 3
irb(main):012:0> s.send "foo", 0
Errno::ECONNREFUSED: Connection refused - send(2)
        from (irb):12:in `send'
        from (irb):12

and it just keeps on alternating, more or less the same as that...

There's no problem when I broadcast on a *real* network, like 192.168.1.255.

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Daniel Brockman) #2

Joel,

Is my network stack screwy, or is there something strange
about UDPSockets in ruby when broadcasting to 127.0.0.255?

I don't think it's supposed to be possible to send broadcasts
to loopback interfaces, going by what ifconfig tells me.
But if it were, I'd think they should go to 127.255.255.255.
That's the obvious choice of a broadcast address here.

Any address can function as the broadcast address for the
network that it's part of, and any address ending in 255
that isn't the broadcast address is just a normal address.
So I think 127.0.0.255 is just another loopback address.

[...]

it just keeps on alternating

Try connecting to another address, say 127.0.0.1, on a port
where noone is listening, and send a bunch of packets.
I think you will see the same alternating behavior,
supporting the claim that 127.0.0.255 is just another
address on which noone is listening.

I can't explain the alternating per se, however, so I'll
leave that to someone who's more familiar with UDP.

There's no problem when I broadcast on a *real* network,
like 192.168.1.255.

That's probably because that's the real broadcast address
for that network (see ifconfig).

Linux seems to treat 127.255.255.255 as the broadcast
address even though ifconfig says there is no loopback
broadcast address, so you should be able to use that.

I hope this helps,

···

--
Daniel Brockman <daniel@brockman.se>

(Joel VanderWerf) #3

Daniel Brockman wrote:

Joel,

Is my network stack screwy, or is there something strange
about UDPSockets in ruby when broadcasting to 127.0.0.255?

I don't think it's supposed to be possible to send broadcasts
to loopback interfaces, going by what ifconfig tells me.
But if it were, I'd think they should go to 127.255.255.255.
That's the obvious choice of a broadcast address here.

Any address can function as the broadcast address for the
network that it's part of, and any address ending in 255
that isn't the broadcast address is just a normal address.
So I think 127.0.0.255 is just another loopback address.

Ah, you're right, of course. The mask for lo is 255.0.0.0, so of course
the broadcast addr has to be 127.255.255.255. Funny that it sorta worked
with 127.0.0.255, though.

Thanks!

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Joel VanderWerf) #4

Ok, a new problem. The following program outputs "received: foo". But if
I uncomment the s.connect line in the fork code, it hangs in #recv. Why
should connecting a UDPSocket to _another_ broadcast port interfere with
receiving? (I see the same behavior with 192.168.1.255 broadcasts after
turning off my firewall.)

require 'socket'

broadcast_addr = '127.255.255.255'

fork do
  s = UDPSocket.open
  s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
  s.bind(broadcast_addr, 44891)
  #s.connect(broadcast_addr, 44890)
  puts "received: #{s.recv(100)}"
end

sleep 0.1

s = UDPSocket.open
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
#s.bind(broadcast_addr, 44890) # this has no effect either way
s.connect(broadcast_addr, 44891)
s.send "foo", 0

Process.wait

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Daniel Brockman) #5

Joel VanderWerf <vjoel@path.berkeley.edu> writes:

Why should connecting a UDPSocket to _another_ broadcast
port interfere with receiving?

CONNECT(2) Linux Programmers Manual CONNECT(2)

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr
       *serv_addr, socklen_t addrlen);

DESCRIPTION
       The file descriptor sockfd must refer to a socket.
       If the socket is of type SOCK_DGRAM then the
       serv_addr address is the address to which datagrams
       are sent by default, and the only address from which
       datagrams are received.

       [...]

···

--
Daniel Brockman <daniel@brockman.se>

(Joel VanderWerf) #6

Joel VanderWerf wrote:

Ok, a new problem. The following program outputs "received: foo". But if
I uncomment the s.connect line in the fork code, it hangs in #recv. Why
should connecting a UDPSocket to _another_ broadcast port interfere with
receiving? (I see the same behavior with 192.168.1.255 broadcasts after
turning off my firewall.)

require 'socket'

broadcast_addr = '127.255.255.255'

fork do
  s = UDPSocket.open
  s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
  s.bind(broadcast_addr, 44891)
  #s.connect(broadcast_addr, 44890)
  puts "received: #{s.recv(100)}"
end

sleep 0.1

s = UDPSocket.open
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
#s.bind(broadcast_addr, 44890) # this has no effect either way

                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Wrong. Binding this socket lets the packet go through, when the
s.connect line is also enabled, but only if I use a non-broadcast
address, like 127.0.0.1.

I guess #connect just doesn't mix well with broadcast UDP?

Well, fortunately, I don't need to use the combination of (broadcast,
UDP, connect) in my application code. I was just using it in a unit
test, but it's not necessary.

···

s.connect(broadcast_addr, 44891)
s.send "foo", 0

Process.wait

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Joel VanderWerf) #7

Daniel Brockman wrote:

Joel VanderWerf <vjoel@path.berkeley.edu> writes:

Why should connecting a UDPSocket to _another_ broadcast
port interfere with receiving?

CONNECT(2) Linux Programmers Manual CONNECT(2)

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr
       *serv_addr, socklen_t addrlen);

DESCRIPTION
       The file descriptor sockfd must refer to a socket.
       If the socket is of type SOCK_DGRAM then the
       serv_addr address is the address to which datagrams
       are sent by default, and the only address from which
       datagrams are received.

       [...]

Hm, so linux connect() implies bind() on UDP sockets?

Then is ruby doing something funny on top of the linux socket API? Why
does the following code work? (It sets up two sockets that connect to
the port each other is listening on, which should not be possible, if I
understand man connect.)

require 'socket'

Socket.do_not_reverse_lookup = true

addr = '127.0.0.1'

socks = (0..1).map {UDPSocket.open()}
socks.each {|s| s.bind(addr, 0)}
ports = socks.map {|s| s.addr[1]}

socks.each_with_index {|s,i| s.connect(addr, ports[1-i])}
puts socks.map{|s| [s.addr, s.peeraddr].inspect}

s0, s1 = socks

t0 = Thread.new do
  puts "s1 received: #{s1.recv(100)}"
end
sleep 0.1

s0.send "foo", 0

t0.join

t1 = Thread.new do
  puts "s0 received: #{s0.recv(100)}"
end
sleep 0.1

s1.send "bar", 0

t1.join

__END__

output:

[["AF_INET", 33223, "127.0.0.1", "127.0.0.1"], ["AF_INET", 33224,
"127.0.0.1", "127.0.0.1"]]
[["AF_INET", 33224, "127.0.0.1", "127.0.0.1"], ["AF_INET", 33223,
"127.0.0.1", "127.0.0.1"]]
s1 received: foo
s0 received: bar

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407