29

I'm behind a router, I need a simple command to discover my public ip (instead of googling what's my ip and clicking one the results)

Are there any standard protocols for this? I've heard about STUN but I don't know how can I use it?

P.S. I'm planning on writing a short python script to do it

Cœur
  • 37,241
  • 25
  • 195
  • 267
hasen
  • 161,647
  • 65
  • 194
  • 231
  • Related: https://unix.stackexchange.com/questions/22615/how-can-i-get-my-external-ip-address-in-a-shell-script | https://stackoverflow.com/questions/3097589/getting-my-public-ip-via-api – Ciro Santilli OurBigBook.com Jul 23 '23 at 17:53

16 Answers16

24

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)
Ribo
  • 3,363
  • 1
  • 29
  • 35
Carlos A. Ibarra
  • 6,002
  • 1
  • 28
  • 38
  • 4
    +1: maybe not the easiest way, but it's neat to see a way that doesn't rely on some website. – David Z Mar 05 '09 at 04:57
  • 3
    But note that "Many hosts ignore or discard the RECORD_ROUTE option." See man ping. – kkurian Jun 18 '12 at 20:58
  • 1
    This is the real answer. The other's all require some 3rd party website to stay alive. – maxywb Jul 05 '16 at 19:44
  • 1
    This relies on ICMP messaging, which is often blocked. So this method is unreliable (and slow). – rustyx Mar 04 '20 at 11:39
  • This answer has many flaws. The assumption that your traffic get's past all NAT gateways in 3 or less hops might work for many but is farm form certain. A lot of servers don't respond to ICMP and even those that do may completely ignore the `RECORD_ROUTE` option (see https://linux.die.net/man/8/ping). – Philip Couling Nov 25 '20 at 22:02
17

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()
Michael Hampton
  • 9,737
  • 4
  • 55
  • 96
Tim Matthews
  • 5,031
  • 8
  • 38
  • 45
  • hehe I like ur name dude! (btw I love D) – hasen Mar 05 '09 at 05:03
  • The URL has been updated to http://automation.whatismyip.com/n09230945.asp; see http://www.whatismyip.com/faq/automation.asp for rules. – Jason R. Coombs Feb 15 '12 at 01:21
  • they also ask you to add a Mozilla user agent per the faq -- posted a verison that does so at http://stackoverflow.com/questions/6452952/find-my-ip-address-in-python/10750359#10750359 (wrapping with an except is a good idea if you're going to rely on this other code) -- https://gist.github.com/2786450 – Alvin May 25 '12 at 07:45
13

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"}
David Ferenczy Rogožan
  • 23,966
  • 9
  • 79
  • 68
11

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)

mcr
  • 4,615
  • 2
  • 31
  • 30
  • 1
    It seems that whatsmyip don't mind automated lookups so long as you follow some rules: http://forum.whatismyip.com/f14/ . If you break the rules than you can be banned. – dan-gph Mar 05 '09 at 04:56
  • the site ryeguy suggested is different .. what*is* .. not what*s* .. mykp.org – hasen Mar 05 '09 at 05:02
  • 4
    I see no such pleas on whatismyip.org . What page are you speaking of? – Brian Jun 01 '09 at 16:57
7

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.

ryeguy
  • 65,519
  • 58
  • 198
  • 260
  • nice! I didn't know about this site! usually google turns up a bunch of sites each of which is big and fat, probably with a note saying don't use automated tools! but this is awesome! thanks – hasen Mar 05 '09 at 03:23
  • It makes your program depend on an external site you do not control (same thing with STUN, of course, unless you run your own STUN server)... – bortzmeyer Mar 09 '09 at 21:49
  • Iwhatismyip.org seems completely broken: I get the answer 192.168.2.1 which is clearly not my public IP... – bortzmeyer Mar 09 '09 at 21:56
  • If you're developing an application and it somehow needs to know the local IP, then you're likely doing something wrong. Don't make dependencies on local IP addrs, as they can change at any time (i.e. DHCP). What's wrong with just using the NAT'd address? – slacy Mar 09 '09 at 21:58
  • STUN is the right way to do this. Scraping whatismyip.org makes your application dependent on that service it isn't playing nice. – Troy J. Farrell Dec 09 '09 at 22:57
  • After looking at the site, it looks like the IP address is actually an imange generated with PHP. Wouldn't this be hard to parse? – glen3b Jun 17 '13 at 13:17
  • @Glen: At the time of this answer, it just returned a plain text no-frills response. – ryeguy Jun 17 '13 at 21:26
  • @ryeguy Good point, but I was saying as of now, the site generates an image. – glen3b Jul 03 '13 at 18:35
  • Read the image with Tesseract-OCR. LOL – MathCrackExchange Jan 13 '21 at 05:02
7

As mentioned by several people, STUN is indeed the proper solution.

Here's a code example using pynat Python module:

>>> import pynat
>>> pynat.get_ip_info()
('UDP Firewall', '192.0.2.2', 54320)
jfs
  • 399,953
  • 195
  • 994
  • 1,670
bortzmeyer
  • 34,164
  • 12
  • 67
  • 91
6

EDIT: curlmyip.com is no longer available. (thanks maxywb)

Original Post:

As of writing this post, curlmyip.com works. From the command line:

curl curlmyip.com

It's a third-party website, which may or may not be available a couple years down the road. But for the time being, it seems pretty simple and to the point.

thejoshwolfe
  • 5,328
  • 3
  • 29
  • 21
3

To get your external ip, you could make a dns query to an opendns server with the special hostname "myip.opendns.com":

from subprocess import check_output

ip = check_output(["dig", "+short", "@resolver1.opendns.com",
                   "myip.opendns.com"]).decode().strip()

On Windows, you could try nslookup.

There is no dns module in Python stdlib that would allow to specify custom dns server. You could use third party libraries e.g., Twisted to make the dns query:

from twisted.internet     import task # $ pip install twisted
from twisted.names.client import Resolver
from twisted.python.util  import println

def main(reactor):
    opendns_resolvers = [("208.67.222.222", 53), ("208.67.220.220", 53)]
    resolver = Resolver(servers=opendns_resolvers, reactor=reactor)
    # use magical hostname to get our public ip
    return resolver.getHostByName('myip.opendns.com').addCallback(println)
task.react(main)

Here's the same using dnspython library:

import dns.resolver # $ pip install dnspython

resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = ["208.67.222.222", "208.67.220.220"]
print(resolver.query('myip.opendns.com')[0])

Here's asyncio variant using aiodns library:

$ pipenv install aiodns
$ pipenv run python -m asyncio
...
>>> import asyncio
>>> import aiodns  # pip install aiodns
>>> resolver = aiodns.DNSResolver()
>>> resolver.nameservers = "208.67.222.222", "208.67.220.220"
>>> await resolver.query("myip.opendns.com", "A")
[<ares_query_a_result> host=192.0.2.2, ttl=0]

To get IPv6 address:

>>> resolver.nameservers = "2620:119:35::35", "2620:119:53::53"
>>> await resolver.query("myip.opendns.com", "AAAA")
jfs
  • 399,953
  • 195
  • 994
  • 1,670
3

Your simplest way may be to ask some server on the outside of your network.

One thing to keep in mind is that different destinations may see a different address for you. The router may be multihomed. And really that's just where problems begin.

John Fricker
  • 3,304
  • 21
  • 21
3

If the network has an UpNp server running on the gateway you are able to talk to the gateway and ask it for your outside IP address.

X-Istence
  • 16,324
  • 6
  • 57
  • 74
1

Duck Duck Go gives free access to their API according to their own page here: https://duckduckgo.com/api

Here's the URL you hit if you want your IP address: http://api.duckduckgo.com/?q=my+ip&format=json

That returns a JSON object. The Answer attribute has a human readable string with your ip address in it. Example:

{
    ...
    "Answer": "Your IP address is ww.xx.yyy.zzz in <a href=\"http://open.mapquest.com/?q=aaaaa(bbbbb)\">aaaaa(bbbbb)</a>"
    ...
}

You could extract the ip address from that string by using split()[4], if you think that it's a safe assumption that this string won't ever change or you're willing to need to periodically fix it.

Alternatively, if you want to have a more future proof method, you could loop over everything returned by split() and return the first item that is an ip address. See here for validating IP addresses: How to validate IP address in Python?

Community
  • 1
  • 1
ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193
  • Hardly "programmatic", but I use DuckDuckGo too – Nemo May 07 '18 at 08:38
  • @Nemo - What's not "programmatic" about it? I didn't give a full code example, but this seems like plenty that you could use it if you want to. Then again, I think some other answers that people have added since I wrote mine are better than mine is. – ArtOfWarfare May 07 '18 at 13:58
1

curl api.infoip.io - full details

curl api.infoip.io/ip - just the ip address

curl api.infoip.io/country - just the country name

... and more of the same

you can view the docs at http://docs.ciokan.apiary.io/

Romeo Mihalcea
  • 9,714
  • 12
  • 50
  • 102
1

I'm sharing you my method of retrieving a server's public IP address without having to use external APIs (which is a security risk of course)

INTERFACE=`ip route get 8.8.8.8 | grep 8.8.8.8 | cut -d' ' -f5`
HOSTIP=`ifconfig $INTERFACE | grep "inet " | awk -F'[: ]+' '{ print $4 }'`

Or in python if you prefer:

import subprocess
public_ip = subprocess.check_output(["ifconfig `ip route get 8.8.8.8 | grep 8.8.8.8 | cut -d' ' -f5` | grep \'inet \' | awk -F'[: ]+' '{ print $4 }'"], shell=True)

It works like this:

  • determines the best interface to reach the google dns server
  • greps public IP from the ifconfig entry for that interface
Rápli András
  • 3,869
  • 1
  • 35
  • 55
1

Here are a few public services that support IPv4 and IPv6:

  • curl http://icanhazip.com
  • curl http://www.trackip.net/ip
  • curl https://ipapi.co/ip
  • curl http://api6.ipify.org
  • curl http://www.cloudflare.com/cdn-cgi/trace
  • curl http://checkip.dns.he.net

The following seem to support only IPv4 at this time:

  • curl http://bot.whatismyipaddress.com
  • curl http://checkip.dyndns.org
  • curl http://ifconfig.me
  • curl http://ip-api.com
  • curl http://api.infoip.io/ip

It's easy to make an HTTP call programmatically. So all should be relatively easy to use, and you can try multiple different URLs in case one fails.

rustyx
  • 80,671
  • 25
  • 200
  • 267
-1

another cheeky way: if your router has got the option to update it's web IP on DynDNS, you can get your own IP with something like:

IP=`resolveip -s myalias.dyndns-home.com`
David Ferenczy Rogožan
  • 23,966
  • 9
  • 79
  • 68
scatha
  • 7
  • 1
  • Cheeky indeed: if you have a Dynamic DNS service you probably don't *need* to find your external IP address, just use the name instead! That's *exactly* what such services are for :P – MestreLion Jun 16 '14 at 21:22
-2

It's now quite cheap to host your own "serverless" API. All the major cloud providers have a service for this. For example, using Google Cloud Functions all it takes is:

exports.requestIP = (req, res) => {
    res.status(200).send(req.ip)
}

This approach is probably more reliable than using the public services above and you can add a long random string to the function's name to keep it private.

eug
  • 1,118
  • 1
  • 16
  • 25