Anyway, I originally posted this question at comp.lang.perl.modules
but I'm not getting any responses and I'm not sure it's a modules
question anyway - maybe more a socket/Linux problem.
Original post here (includes code):
http://groups.google.com/group/comp.lang.perl.modules/msg/8ce530096fe7fdac?
In summary, I'm attempting to use Net::DHCP::Packet and
IO::Socket::INET to generate, transmit and receive DHCP messages
(discover, offer, request, ack). The problem is that I'm not able to
use the IO::Socket recv method to receive a broadcasted DHCP offer
packet. My script blocks indefinitely though I see the offer arrive
via Wireshark.
I'm following the example from the Net::DHCP::Packet package and, in
Googling, no one else mentions issues with this. Therefore I assume
that the error is on my part - probably a simple error - but I don't
know enough to diagnose it.
As I mentioned, I'm using Linux with eth0 set up with a static IP
(10.0.0.1). I was not able to set the interface to 0.0.0.0 as I'd
assumed I should in order to perform DHCP transactions.
I'm open to any and all suggestions, tips, etc.
Thanks in advance,
-jp
In that message, you wrote:
|
| #open socket for packet tx & rx
| my $socket = IO::Socket::INET->new( Proto => 'udp',
| Broadcast => 1,
| LocalAddr => '10.0.0.1',
| LocalPort => 68,
| PeerAddr => '255.255.255.255',
| PeerPort => 67,
| ) || die "Unable to create socket: $@\n";
|
| my $discover = Net::DHCP::Packet->new(
| Xid => int rand(0xFFFFFFFF),
| Chaddr => 00011aabbccd,
| DHO_DHCP_MESSAGE_TYPE() => DHCPDISCOVER(),
| DHO_VENDOR_CLASS_IDENTIFIER() => 'MyVendorClassID',
| DHO_DHCP_PARAMETER_REQUEST_LIST() => '1 2 6 12 15 28 67',
| );
RFC 2131 says "If the broadcast bit is not set and 'giaddr' is zero and
'ciaddr' is zero, then the server unicasts DHCPOFFER and DHCPACK
messages to the client's hardware address and 'yiaddr' address.". That
applies here, so the packet will not be broadcast, it will be sent as a
unicast Ethernet frame to your claimed hardware address
(00:01:1a:ab:bc:cd) with an IP packet addressed to your claimed IP addr
(0.0.0.0, since you didn't set yiaddr) inside.
Even if this packet gets to you (are you using your real hardware
address?) you can't read such a packet from an IP socket under Linux.
You need to put the interface in promiscuous mode (which requires root)
and use a PF_PACKET socket with an appropriate filter to get hold of the
packet. See e.g. setup_packet_filters, open_socket and get_packet in
http://git.marples.name/?p=dhcpcd/.git;a=blob;f=socket.c (make sure you
read the #ifdef __linux__ version).
Alternatively, you could try setting Flags => 1, which asks for the
reply to be broadcast (for the sake of clients whose IP stacks can't
cope with confusingly-addressed packets), in which case an ordinary UDP
socket should be able to pick it up.
Ben
Hi Ben,
Thanks for the reply.
The DHCP Offer I'm receiving is definitely addressed to destination IP
255.255.255.255:68 (perhaps my DHCP server is non-compliant?). I did
try setting Flags => 1 as one of my tests and it didn't make a
difference but I'll repeat it to be sure.
The Linux stuff is interesting. I'm not using my true hardware
address because the goal of the test is to check the DHCP behavior for
various hardware classes. Perhaps that's the problem. I'll go over
what you suggested and give it a try in the morning.
I wasn't able to set eth0 to 0.0.0.0 using ifconfig but you're right,
yiaddr is 0.0.0.0 within the discover payload.
-jp
Well, after Ben mentioned promiscuous mode I did some more Googling
and it turns out Abigail had problems with the same issue in 2004
(http://www.perlmonks.org/index.pl?node_id=325248).
So I feel a lot better if it can happen to him too ;-)
Abigail was on RH; I'm on Fedora. So now to figure out what was
blocked and how to unblock it. iptables? I'll check tomorrow but I
thought I tried with the thing wide open at least once...
-jp
> Well, after Ben mentioned promiscuous mode I did some more Googling
> and it turns out Abigail had problems with the same issue in 2004
> (http://www.perlmonks.org/index.pl?node_id=325248).
>
> So I feel a lot better if it can happen to him too ;-)
>
> Abigail was on RH; I'm on Fedora. So now to figure out what was
> blocked and how to unblock it. iptables? I'll check tomorrow but I
> thought I tried with the thing wide open at least once...
Still no luck. Have tried with Flags => 1 in the IO::Socket::INET
constructor. The DHCPOFFER is broadcasted regardless (Ethernet
destination MAC = ff:ff:ff:ff:ff:ff; destination IP = 255.255.255.255;
destination udp port = 68) but my socket never receives it.
Have run with iptables completely flushed. Tried running the same
script on FreeBSD with the same results.
Does Abigail still hang out here? It sounds like he had an identical
issue...
Last thing to do: trying to set interface as promiscuous. Can I do it
from Perl? I tried ifconfig eth0 promisc before running the script,
but identical results.
Thanks,
-jp
The Flags => 1 was in the Net::DHCP::Packet constructor.
> The DHCPOFFER is broadcasted regardless (Ethernet
> destination MAC = ff:ff:ff:ff:ff:ff; destination IP = 255.255.255.255;
> destination udp port = 68) but my socket never receives it.
That's... odd. Can you recieve other broadcast datagrams?
Have you tried using a PF_PACKET socket?
Ben
Right, my bad. I think I tried that too, but I'll repeat to be sure.
> > The DHCPOFFER is broadcasted regardless (Ethernet
> > destination MAC = ff:ff:ff:ff:ff:ff; destination IP = 255.255.255.255;
> > destination udp port = 68) but my socket never receives it.
>
> That's... odd. Can you recieve other broadcast datagrams?
>
> Have you tried using a PF_PACKET socket?
I thought it was odd too, but I'm continuing to research (so
frustrated! ;-)) and I think what I'm finding is that it's not that
odd...
Here's a post from Solaris' bug tracker: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4191980.
The originator had a similar problem, but with Java. The response
was:
"The Solaris (& Linux for that matter) behavior is
that to be able to receive broadcasts a UDP socket has to be bound to
the
wildcard address (aka anylocal). This has been the defacto standard
behavior
since the days of BSD 4.3. Win32 behaviors differs from that. So the
only way
to be guaranteed to receive the broadcasts on any platform is to bind
to
the wildcard address."
So I tried changing my IO::Socket::INET constructor to use INADDR_ANY,
but no luck. The constructor seems to ignore PeerAddr => '0.0.0.0';
This page (http://wiki.forum.nokia.com/index.php/
How_to_use_udp_broadcast) shows how to set up a Python socket for UDP
broadcast.
So now my question is, how do I set up a Perl socket similarly? Can I
still use IO::Socket::INET to set PF_INET (is that the same as
PF_PACKET?) or do I have to use Socket instead?
As I'm sure everyone can tell, I'm out of my depth, but learning...
slowly.
Thanks Ben,
-jp
OK. I didn't know that, but it makes some sense.
> So I tried changing my IO::Socket::INET constructor to use INADDR_ANY,
> but no luck. The constructor seems to ignore PeerAddr => '0.0.0.0';
No, you need LocalAddr => '0.0.0.0'. It's the address of *this* socket
you're setting, not the address you're talking to. For UDP sockets,
PeerAddr sets the default destination for sends.
> This page (http://wiki.forum.nokia.com/index.php/
> How_to_use_udp_broadcast) shows how to set up a Python socket for UDP
> broadcast.
Whoa... that page is about PyS60, otherwise known as 'Python for itty
bitty Symbian phones with rather weird network stacks' :). However, the
code they give is basically the same as on any other platform; here
(FreeBSD) I can send and recieve broadcast datagrams with
#!/usr/bin/perl -l
use warnings;
use strict;
use IO::Socket::INET;
use Data::Dump qw/dump/;
my $port = 8091;
if ($ARGV[0] eq 'recv') {
my $SCK = IO::Socket::INET->new(
Type => SOCK_DGRAM,
Proto => 'udp',
LocalAddr => '0.0.0.0',
LocalPort => $port,
Broadcast => 1,
) or die "socket: $@";
$SCK->recv(my $buf, 512, 0)
or die "recv: $@ $!";
print dump $buf;
}
else {
my $SCK = IO::Socket::INET->new(
Type => SOCK_DGRAM,
Proto => 'udp',
PeerAddr => $ARGV[1],
PeerPort => $port,
Broadcast => 1,
) or die "socket: $!";
my $buf = 'Hello world!';
$SCK->send($buf)
or die "send: $!";
}
__END__
though I find I have to send to 192.168.1.255 (the local net broadcast
addr) rather than 255.255.255.255, which ought to be the same. I don't
know why that is.
> So now my question is, how do I set up a Perl socket similarly? Can I
> still use IO::Socket::INET to set PF_INET (is that the same as
> PF_PACKET?) or do I have to use Socket instead?
No, PF_PACKET isn't the same as PF_INET. PF_INET is the normal Internet
protocol family: basically TCP or UDP over IP, though on most OSen you
can use PF_INET/SOCK_RAW sockets to talk any IP protocol, including
ICMP. In Perl you can use IO::Socket::INET to do anything your OS will
let you do with PF_INET sockets.
PF_PACKET is a Linux-specific protocol family for talking directly to
the network driver; it allows you to send and receive raw Ethernet
frames, for example. Perl has no IO::Socket support for PF_PACKET
(though writing an IO::Socket::PACKET wouldn't be too hard: it's mostly
just packing and unpacking the addresses, which is code you'd need to
write to use these sockets anyway), so if you need these you would have
to use the socket builtins (Socket wouldn't help, here, as that only has
AF_INET/AF_UNIX support).
Ben