Get my IP address?

J

John W. Krahn

$ perl -v

This is perl, v5.6.0 built for i586-linux

Copyright 1987-2000, Larry Wall

(Yes I know. [My current machine is in the shop so I am stuck with this
old thing.])


I did an strace of ifconfig:

strace /sbin/ifconfig eth0
[ SNIP ]
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 6
[ SNIP ]
ioctl(6, SIOCGIFADDR, 0xbffff4ec) = 0
ioctl(6, SIOCGIFDSTADDR, 0xbffff4ec) = 0
ioctl(6, SIOCGIFBRDADDR, 0xbffff4ec) = 0
ioctl(6, SIOCGIFNETMASK, 0xbffff4ec) = 0


And I thought that this would be easy enough to do in Perl:

$ perl -le'
use Socket qw/PF_INET SOCK_DGRAM/;
require "linux/ioctl.ph";
my $proto = getprotobyname "ip";
socket SOCKET, PF_INET, SOCK_DGRAM, $proto or die "socket: $!";
defined( ioctl SOCKET, SIOCGIFADDR, my $address ) or die "ioctl: $!";
'
ioctl: No such device at -e line 6.


Is this just broken on 5.6.0 or does it work anywhere? :)



John
 
B

Ben Morrow

Quoth "John W. Krahn said:
I did an strace of ifconfig:

strace /sbin/ifconfig eth0
[ SNIP ]
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 6
[ SNIP ]
ioctl(6, SIOCGIFADDR, 0xbffff4ec) = 0
ioctl(6, SIOCGIFDSTADDR, 0xbffff4ec) = 0
ioctl(6, SIOCGIFBRDADDR, 0xbffff4ec) = 0
ioctl(6, SIOCGIFNETMASK, 0xbffff4ec) = 0

And I thought that this would be easy enough to do in Perl:

$ perl -le'
use Socket qw/PF_INET SOCK_DGRAM/;
require "linux/ioctl.ph";
my $proto = getprotobyname "ip";
socket SOCKET, PF_INET, SOCK_DGRAM, $proto or die "socket: $!";
defined( ioctl SOCKET, SIOCGIFADDR, my $address ) or die "ioctl: $!";

You need to give $address a string value at least as long as a struct
sockaddr, or you'll get a buffer overrun. ioctl can't create a value for
you: it doesn't know how long to make it.
'
ioctl: No such device at -e line 6.

Is this just broken on 5.6.0 or does it work anywhere? :)

You haven't said which network interface you're talking about; there
must be something else in that strace that binds the socket to a given
interface.

Ben
 
B

Ben Morrow

Sorry, scratch my last response. I (and you, I think) was completely
misunderstanding how SIOCGIFADDR is supposed to be called.

Quoth "John W. Krahn said:
$ perl -le'
use Socket qw/PF_INET SOCK_DGRAM/;
require "linux/ioctl.ph";
my $proto = getprotobyname "ip";
socket SOCKET, PF_INET, SOCK_DGRAM, $proto or die "socket: $!";
defined( ioctl SOCKET, SIOCGIFADDR, my $address ) or die "ioctl: $!";
'
ioctl: No such device at -e line 6.

You're calling it wrong :). SIOCGIFADDR takes a pointer to a struct
ifreq, which starts with an interface name. If you pass it an empty
string (which has an implicit "\0" at the end) then it's looking for an
interface with an empty name. Unsurprisingly, it can't find one :).

Under FreeBSD, with 5.8.8 or 5.6.0, this script

#!/usr/bin/perl -l

use Socket qw/PF_INET SOCK_DGRAM inet_ntoa sockaddr_in/;

# h2ph gets SIOCGIFADDR wrong on my system.
# Modern systems have complicated ioctl definitions that encode the
# sizes of the types used, and h2ph can't cope.
# h2ph is evil anyway. Use h2xs instead. ;)

my $SIOCGIFADDR = 3223349537;

# This is slightly daft... ip is protocol 0 by definition, and 0
# means 'default protocol for this socket type', so you'll actually
# get an ordinary UDP socket. IPPROTO_IP only really applies to
# SOCK_RAW sockets.

my $proto = getprotobyname 'ip';

socket SOCKET, PF_INET, SOCK_DGRAM, $proto
or die "socket: $!";

# struct ifreq is 16 bytes of name, null-padded, followed by 16
# bytes of answer. In the case of SIOCGIFADDR, that answer is a
# packed address.

my $ifreq = pack 'a32', 'xl0';
ioctl SOCKET, $SIOCGIFADDR, $ifreq
or die "ioctl: $!";

my ($if, $sin) = unpack 'a16 a16', $ifreq;
my ($port, $addr) = sockaddr_in $sin;
my $ip = inet_ntoa $addr;

print "addr: $ip";

__END__

prints my ip address. You will of course need to change the value of
$SIOCGIFADDR (write a trivial bit of C to tell you what it should be)
and the interface name, and perhaps look up Linux' struct ifreq and
change the unpack format (it's probably the same though).

Now do it properly with Net::Interface :).

Ben
 
J

John W. Krahn

Ben said:
Sorry, scratch my last response.

OK, scratched.
I (and you, I think) was completely
misunderstanding how SIOCGIFADDR is supposed to be called.

Correct on this end.
You're calling it wrong :). SIOCGIFADDR takes a pointer to a struct
ifreq, which starts with an interface name. If you pass it an empty
string (which has an implicit "\0" at the end) then it's looking for an
interface with an empty name. Unsurprisingly, it can't find one :).

Under FreeBSD, with 5.8.8 or 5.6.0, this script

#!/usr/bin/perl -l

use Socket qw/PF_INET SOCK_DGRAM inet_ntoa sockaddr_in/;

# h2ph gets SIOCGIFADDR wrong on my system.
# Modern systems have complicated ioctl definitions that encode the
# sizes of the types used, and h2ph can't cope.
# h2ph is evil anyway. Use h2xs instead. ;)

I just looked the numbers up in the header file.
my $SIOCGIFADDR = 3223349537;

That seems like a rather large number, on my Linux system its 0x8915.
(Although I would probably use SIOCGIFDSTADDR instead.)
# This is slightly daft... ip is protocol 0 by definition, and 0
# means 'default protocol for this socket type', so you'll actually
# get an ordinary UDP socket. IPPROTO_IP only really applies to
# SOCK_RAW sockets.

my $proto = getprotobyname 'ip';

socket SOCKET, PF_INET, SOCK_DGRAM, $proto
or die "socket: $!";

# struct ifreq is 16 bytes of name, null-padded, followed by 16
# bytes of answer. In the case of SIOCGIFADDR, that answer is a
# packed address.

my $ifreq = pack 'a32', 'xl0';

I found that this:

my $ifreq = 'eth0';

works fine for me (I don't have an 'x10' interface.) Perl will expand
the string to the required size.
ioctl SOCKET, $SIOCGIFADDR, $ifreq
or die "ioctl: $!";

my ($if, $sin) = unpack 'a16 a16', $ifreq;
my ($port, $addr) = sockaddr_in $sin;
my $ip = inet_ntoa $addr;

print "addr: $ip";

__END__

prints my ip address. You will of course need to change the value of
$SIOCGIFADDR (write a trivial bit of C to tell you what it should be)
and the interface name, and perhaps look up Linux' struct ifreq and
change the unpack format (it's probably the same though).

Thanks a lot Ben. Now if I could figure out SIOCGIFCONF to get the list
of interfaces! :)



John
 
B

Ben Morrow

Quoth "John W. Krahn said:
I just looked the numbers up in the header file.


That seems like a rather large number, on my Linux system its 0x8915.
(Although I would probably use SIOCGIFDSTADDR instead.)

It's defined as (more-or-less)

(IOC_IN | IOC_OUT | (sizeof(struct ifreq) << 16) | ('i' << 8) | 33)

where 'i' means 'Internet ioctls' and 33 is the real ioctl number, for
the benefit of the unpacking code in the kernel. IIRC Linux defines most
of its ioctls like this; for some reason the networking ones must be
different <shrug>.

struct ifreq contains a union, so it's hardly surprising h2ph gets the
value wrong.
I found that this:

my $ifreq = 'eth0';

works fine for me (I don't have an 'x10' interface.) Perl will expand
the string to the required size.

It will when it can. It always can on BSDish systems (due to the
encoding above); 5.8.8 and later sometimes can on Linux (but not in this
case, as the Linux networking ioctls don't encode the size). Otherwise
perl just assumes 256 bytes is enough, which may or may not be true.
It's always safer to preallocate enough memory yourself.
Thanks a lot Ben. Now if I could figure out SIOCGIFCONF to get the list
of interfaces! :)

Calling SIOCGIFCONF is very hard, because the ifconf structure contains
a list of ifreqs, which are not all the same length. Not only are IPv6
address longer than 32 bytes, SIOCGIFCONF returns link-level addresses
which are a different length again. You would need to go poking around
in the struct sockaddr to find out how long it actually is. See
http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/net/getifaddrs.c?rev=1.6
for an example of how to call it correctly, and note that that depends
on struct sockaddr having a sa_len member, which I don't think is the
case under Linux.

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,167
Latest member
SusanaSwan
Top