1

I have a python class which grabs the IP address of the local machine you are on. It works on actual hardware (unless it's MacOS with bridged devices involved), or local VMs. This is the code:

class IPAddress:
    ip_address = None

    def __init__(self):
        return

    def _get_interface_ip(self,ifname):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s',
                            ifname[:15]))[20:24])

    # does not work on MacOS with bridged devices such as vboxnet0, tap0, tun0
    def get_lan_ip(self):
        hostname = socket.gethostname()
        ip = u''
        try:
            ip = socket.gethostbyname(hostname)
        except socket.gaierror, e:
            try:
                ip = socket.gethostbyname(u'localhost')
            except: pass

        if ip.startswith("127.") and os.name != "nt":
            interfaces = [
                "eth0",
                "eth1",
                "eth2",
                "en0",
                "en1",
                "en2",
                "wlan0",
                "wlan1",
                "wifi0",
                "ath0",
                "ath1",
                "ppp0",
                ]
            for ifname in interfaces:
                try:
                    ip = self._get_interface_ip(ifname)
                    break
                except IOError:
                    pass
        return ip

# Find out this instance
x = IPAddress()
server_ip = x.get_lan_ip()

This works everywhere it seems except on AWS. On AWS ifconfig eth0 gives the internal IP address assigned to eth0, whereas the class gives the external CNAME'd IP address associated with accessing the instance (in EC2 Classic).

What gives? Is there a better way to do self discovery with (for example) boto?

dcmbrown
  • 1,049
  • 9
  • 15
  • Which behavior do you want? Do you want the address of the local interface, or do you want the address of the NAT device between you and the Internet? – Robᵩ Jul 22 '14 at 20:27
  • possible duplicate of [Finding local IP addresses using Python's stdlib](http://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib) – Robᵩ Jul 22 '14 at 20:39
  • @Robᵩ I would want the IP address that ifconfig gives. This is something like 10.11.102.5. What it is giving me instead is the public IP address of the AWS EC2 Classic server, eg. 54.102.78.220 Realistically I don't care if it is using only Python Stdlib and I also just discovered netifaces which also seems to be popular enough to try so I'll take a look at that and make sure it's not giving the same results. If it is I may just do something like `curl http://169.254.169.254/latest/meta-data/local-ipv4` – dcmbrown Jul 22 '14 at 23:06

1 Answers1

2

First, I wouldn't use gethostbyname() or its kin. Doing so relies upon external databases that you do not control, and that are often not up to date.

Next, I wouldn't call ioctl(). The existence of any particular ioctl, much less its behavior, is not standardized across operating systems.

If it were me, I would connect() to some external service and then call socket.getsockname():

def get_my_addr():
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  s.connect(('8.8.8.8', 53))
  return s.getsockname()[0]

If that didn't work, I'd connect() to a TCP-based service:

def get_my_addr():
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect(('www.google.com', 80))
  return s.getsockname()[0]
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • It seems rather arbitrary to connect to another service (whether google, or a service running locally) in order to then discover a piece of information about one's self. It would be as simple just make an external call such as described here: http://tech-queries.blogspot.ca/2011/06/how-to-get-public-ip-for-amazon-ec2.html – dcmbrown Jul 22 '14 at 21:50
  • The first code snippet in my answer doesn't actually connect to anything. That is, it generates no traffic, and doesn't perturb the named machine ('8.8.8.8' in the example) in any way. And, of course, it works. The code you link to, however, connects to an external service, and only works in the Amazon cloud. The code you posted in your question connects to an external service, and doesn't work universally. The duplicate question I linked to has several solutions, with discussion on the relative merits. Take your pick. – Robᵩ Jul 22 '14 at 22:03
  • Tested this on a VM with outgoing traffic blocked, found it rather strange that connect() does not actually **connect** or do anything. It rather simply sets up the socket so it's ready to transmit; a departure from what the docs seem to say too. It would be like using open(filename, 'w') on a file and it setting up a file descriptor, but when you write() it gives an error because the file isn't actually writable. Although I'm fairly new to the specifics of Python so it may be the case. Anyway I'll take a further look between this and netifaces and figure out which will work better. Thx! – dcmbrown Jul 23 '14 at 16:15