Discovering public IP programmatically

Solution 1:

This may be the easiest way. Parse the output of the following commands:

  1. run a traceroute to find a router that is less than 3 hops out from your machine.
  2. run ping with the option to record the source route and parse the output. The first IP address in the recorded route is your public one.

For example, I am on a Windows machine, but the same idea should work from unix too.

> tracert -d www.yahoo.com

Tracing route to www-real.wa1.b.yahoo.com [69.147.76.15]
over a maximum of 30 hops:

  1    <1 ms    <1 ms    <1 ms  192.168.14.203
  2     *        *        *     Request timed out.
  3     8 ms     8 ms     9 ms  68.85.228.121
  4     8 ms     8 ms     9 ms  68.86.165.234
  5    10 ms     9 ms     9 ms  68.86.165.237
  6    11 ms    10 ms    10 ms  68.86.165.242

The 68.85.228.121 is a Comcast (my provider) router. We can ping that:

> ping -r 9 68.85.228.121 -n 1

Pinging 68.85.228.121 with 32 bytes of data:

Reply from 68.85.228.121: bytes=32 time=10ms TTL=253
    Route: 66.176.38.51 ->
           68.85.228.121 ->
           68.85.228.121 ->
           192.168.14.203

Voila! The 66.176.38.51 is my public IP.

Python code to do this (hopefully works for py2 or py3):

#!/usr/bin/env python

def natIpAddr():
  # Find next visible host out from us to the internet
  hostList = []
  resp, rc = execute("tracert -w 100 -h 3 -d 8.8.8.8") # Remove '-w 100 -h d' if this fails

  for ln in resp.split('\n'):
    if len(ln)>0 and ln[-1]=='\r': ln = ln[:-1]  # Remove trailing CR
    if len(ln)==0: continue
    tok = ln.strip().split(' ')[-1].split('.') # Does last token look like a dotted IP address?
    if len(tok)!=4: continue
    hostList.append('.'.join(tok))
    if len(hostList)>1: break  # If we found a second host, bail
    
  if len(hostList)<2:
    print("!!tracert didn't work, try removing '-w 100 -h 3' options")
    # Those options were to speed up tracert results

  else:
    resp, rc = execute("ping -r 9 "+hostList[1]+" -n 1")
    ii = resp.find("Route: ")
    if ii>0: return resp[ii+7:].split(' ')[0]
  return none     


def execute(cmd, showErr=True, returnStr=True):
  import subprocess
  if type(cmd)==str:
    cmd = cmd.split(' ')
  # Remove ' ' tokens caused by multiple spaces in str             
  cmd = [xx for xx in cmd if xx!='']
  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  out, err = proc.communicate()
  if type(out)==bytes:  # Needed for python 3 (stupid python)
    out = out.decode()
    try:
      err = err.decode()
    except Exception as ex: 
      err = "!!--"+str(type(ex))+"--!!"
  
  if showErr and len(err)>0:
    out += err
  if returnStr and str(type(out))=="<type 'unicode'>":
    # Trying to make 'out' be an ASCII string whether in py2 or py3, sigh.
    out = out.encode()  # Convert UNICODE (u'xxx') to string
  return out, proc.returncode


if __name__ == "__main__":
  print("(This could take 30 sec)")
  print(natIpAddr())

Use it from the command line (on Windows) or from a python program:

import natIpAddr
myip = natIpAddr.natIpAddr()
print(myip)

Solution 2:

I have made a program that connects to http://automation.whatismyip.com/n09230945.asp it is is written in D an getting someone else to tell you what they see your ip as is probably the most reliable way:

/*
    Get my IP address
*/


import tango.net.http.HttpGet;
import tango.io.Stdout;

void main()
{
      try
      {
          auto page = new HttpGet ("http://automation.whatismyip.com/n09230945.asp");
          Stdout(cast(char[])page.read);
      }
      catch(Exception ex)
      {
          Stdout("An exception occurred");
      }
}

Edit python code should be like:

from urllib import urlopen
print urlopen('http://automation.whatismyip.com/n09230945.asp').read()

Solution 3:

I like the ipify.org:

  • it's free to use (even programmatically and allows even heavy traffic)
  • the response contains only the IP address without any garbage (no need for parsing)
  • you can also request a response in JSON
  • works for both IPv4 and IPv6
  • it's hosted in a cloud
  • it's open-source
$ curl api.ipify.org
167.220.196.42

$ curl "api.ipify.org?format=json"
{"ip":"167.220.196.42"}

Solution 4:

Targeting www.whatsmyip.org is rude. They plea not to do that on the page.

Only a system on the same level of NAT as your target will see the same IP. For instance, your application may be behind multiple layers of NAT (this happens more as you move away from the US, where the glut of IPs are).

STUN is indeed the best method. In general, you should be planning to run a (STUN) server somewhere that you application can ask: do not hard code other people's servers. You have to code to send some specific messages as described in rfc5389.

I suggest a good read of, and related links. http://www.ietf.org/html.charters/behave-charter.html

You may prefer to look at IPv6, and Teredo to make sure that you always have IPv6 access. (Microsoft Vista makes this very easy, I'm told)

Solution 5:

Whenever I wanted to do this, I would just scrape whatismyip.org. When you go to the site, it gives you your plain text public IP. Plain and simple.

Just have your script access that site and read the IP.

I don't know if you were implying this in your post or not, but it isn't possible to get your public IP from your own computer. It has to come from an external source.

2013 edit: This site returns an image now instead of text, so it's useless for this purpose.