以编程方式发现公共 IP

Posted

技术标签:

【中文标题】以编程方式发现公共 IP【英文标题】:Discovering public IP programmatically 【发布时间】:2010-10-11 10:31:50 【问题描述】:

我在路由器后面,我需要一个简单的命令来发现我的公共 ip(而不是在谷歌上搜索我的 ip 并单击结果)

是否有任何标准协议?听说过 STUN 但不知道怎么用?

附:我打算写一个简短的python脚本来做它

【问题讨论】:

【参考方案1】:

这可能是最简单的方法。解析以下命令的输出:

    运行 traceroute 以查找距离您的计算机不到 3 跳的路由器。 使用选项运行 ping 以记录源路由并解析输出。记录路由中的第一个 IP 地址是您的公共地址。

例如,我在一台 Windows 机器上,但同样的想法也应该适用于 unix。

> 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

68.85.228.121 是 Comcast(我的提供商)路由器。我们可以ping通:

> 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

瞧! 66.176.38.51 是我的公共 IP。

执行此操作的 Python 代码(希望适用于 py2 或 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())

从命令行(在 Windows 上)或 python 程序中使用它:

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

【讨论】:

+1:也许不是最简单的方法,但很高兴看到一种不依赖于某些网站的方法。 但请注意“许多主机忽略或丢弃 RECORD_ROUTE 选项。”见 man ping。 这是真正的答案。其他的都需要一些 3rd 方网站才能保持活力。 这依赖于经常被阻止的 ICMP 消息传递。所以这种方法不可靠(而且速度慢)。 这个答案有很多缺陷。假设您的流量以 3 或更少的跃点通过所有 NAT 网关可能适用于许多人,但肯定是农场形式。许多服务器不响应 ICMP,甚至那些响应的服务器也可能完全忽略 RECORD_ROUTE 选项(请参阅 linux.die.net/man/8/ping)。【参考方案2】:

我制作了一个连接http://automation.whatismyip.com/n09230945.asp的程序 它是用 D 写的,让别人告诉你他们看到你的 ip 是什么,这可能是最可靠的方式:

/*
    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");
      

编辑python代码应该是这样的:

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

【讨论】:

呵呵,我喜欢你的名字,伙计! (顺便说一句,我爱 D) 网址已更新为automation.whatismyip.com/n09230945.asp;规则见whatismyip.com/faq/automation.asp。 他们还要求您根据常见问题解答添加一个 Mozilla 用户代理——在***.com/questions/6452952/… 发布了一个这样做的版本(如果您要依赖于这个其他代码)--gist.github.com/2786450【参考方案3】:

我喜欢ipify.org:

它可以免费使用(甚至可以通过编程方式使用,甚至允许大量流量) 响应只包含 IP 地址,没有任何垃圾(无需解析) 您也可以请求 JSON 格式的响应 适用于 IPv4 和 IPv6 它托管在云中 它是开源的
$ curl api.ipify.org
167.220.196.42

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

【讨论】:

它运行良好。如果您怀疑某些网络无法正常工作,请先尝试downforeveryoneorjustme.com/ipify.org 之类的服务。【参考方案4】:

定位 www.whatsmyip.org 是不礼貌的。他们恳求不要在页面上这样做。

只有与目标处于同一 NAT 级别的系统才能看到相同的 IP。 例如,您的应用程序可能位于多层 NAT 之后(当您远离 IP 过剩的美国时,这种情况发生得更多)。

STUN 确实是最好的方法。 一般来说,您应该计划在您的某个地方运行(STUN)服务器 应用程序可以问:不要硬编码其他人的服务器。你必须编码 发送一些特定的消息,如 rfc5389 中所述。

我建议好好阅读相关链接。 http://www.ietf.org/html.charters/behave-charter.html

您可能更喜欢查看 IPv6 和 Teredo,以确保您始终拥有 IPv6 访问权限。 (有人告诉我,Microsoft Vista 让这变得非常简单)

【讨论】:

只要你遵循一些规则,whatsmyip 似乎不介意自动查找:forum.whatismyip.com/f14。如果您违反规则,则可能会被禁止。 黑麦建议的网站不同.. 什么 .. 不是什么s .. mykp.org 我在 whatismyip.org 上没有看到这样的请求。你说的是哪个页面?【参考方案5】:

每当我想这样做时,我都会抓取whatismyip.org。当您访问该站点时,它会为您提供纯文本公共 IP。简单明了。

只需让您的脚本访问该站点并读取 IP。

我不知道您是否在帖子中暗示了这一点,但不可能从您自己的计算机上获取您的公共 IP。它必须来自外部来源。

2013 年编辑:此站点现在返回图像而不是文本,因此它对此无用。

【讨论】:

不错!我不知道这个网站!通常谷歌会出现一堆网站,每个网站都又大又肥,可能还有一条说明说不要使用自动化工具!但这太棒了!谢谢 它使您的程序依赖于您无法控制的外部站点(当然,与 STUN 相同,除非您运行自己的 STUN 服务器)... Iwhatismyip.org 似乎完全崩溃了:我得到答案 192.168.2.1 这显然不是我的公共 IP... 如果您正在开发一个应用程序并且它需要知道本地 IP,那么您可能做错了什么。不要依赖本地 IP 地址,因为它们可以随时更改(即 DHCP)。只使用经过 NAT 处理的地址有什么问题? STUN 是正确的方法。抓取 whatismyip.org 会使您的应用程序依赖于它并不能正常运行的服务。【参考方案6】:

正如一些人提到的,STUN 确实是正确的解决方案。

public STUN servers 列表 免费软件STUN client STUN standard

这是一个使用pynat Python module的代码示例:

>>> import pynat
>>> pynat.get_ip_info()
('UDP Firewall', '192.0.2.2', 54320)

【讨论】:

【参考方案7】:

编辑:curmyip.com 不再可用。 (感谢 maxywb)

原帖

在撰写本文时,curmyip.com 有效。从命令行:

curl curlmyip.com

这是一个第三方网站,几年后可能会也可能不会。但就目前而言,这似乎很简单,也很中肯。

【讨论】:

另一个服务:curl icanhazip.com 另一个是 mypubip.com,它在使用 curl 时显示三行文本响应。 9 年,直到 api.ident.me :)【参考方案8】:

如果网络有一个在网关上运行的 UpNp 服务器,您可以与网关通信并询问您的外部 IP 地址。

【讨论】:

【参考方案9】:

您最简单的方法可能是询问网络外部的某个服务器。

要记住的一点是,不同的目的地可能会为您看到不同的地址。路由器可能是多宿主的。而这正是问题开始的地方。

【讨论】:

如果您的 ISP 正在做任何代理,则同上 完全错误:有一个标准协议,STUN,RFC 5389。 有趣 - 不熟悉那个 RFC。它是否已广泛实施? 好问题,但我不知道有任何调查。无论如何,你不需要互联网上的每一台机器来实现它。如果你开发一个网络程序,只需将STUN代码放入你的程序中,并设置几个公共STUN服务器(或者看看现有的是否可以使用)。【参考方案10】:

要获取您的外部 ip,您可以使用特殊主机名“myip.opendns.com”对 opendns 服务器进行 dns 查询:

from subprocess import check_output

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

在 Windows 上,您可以尝试nslookup

Python 标准库中没有允许指定自定义 dns 服务器的 dns 模块。您可以使用第三方库,例如 Twisted 进行 dns 查询:

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)

这里使用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])

这是使用aiodns library 的asyncio 变体:

$ 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]

获取 IPv6 地址:

>>> resolver.nameservers = "2620:119:35::35", "2620:119:53::53"
>>> await resolver.query("myip.opendns.com", "AAAA")

【讨论】:

【参考方案11】:

curl api.infoip.io - 详细信息

curl api.infoip.io/ip - 只是 IP 地址

curl api.infoip.io/country - 只是国家名称

...还有更多相同的

您可以在http://docs.ciokan.apiary.io/查看文档

【讨论】:

【参考方案12】:

我将与您分享我无需使用外部 API 即可检索服务器公共 IP 地址的方法(这当然存在安全风险)

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 '`

如果您愿意,也可以在 python 中:

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)

它是这样工作的:

确定访问 google dns 服务器的最佳接口 从该接口的 ifconfig 条目中获取公共 IP

【讨论】:

这只是给了我我的本地 IP,而不是我的公共 IP。 也许你正在使用一些具有不同 ip 路由输出的奇异 linux 我认为 Ubuntu 算不上“异国情调”【参考方案13】:

以下是一些支持 IPv4 和 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

以下似乎目前仅支持 IPv4:

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

以编程方式进行 HTTP 调用很容易。所以一切都应该相对容易使用,你可以尝试多个不同的 URL,以防万一失败。

【讨论】:

【参考方案14】:

Duck Duck Go 根据他们自己的页面在这里免费访问他们的 API: https://duckduckgo.com/api

如果您想要您的 IP 地址,请点击以下 URL: http://api.duckduckgo.com/?q=my+ip&format=json

返回一个 JSON 对象。 Answer 属性有一个人类可读的字符串,其中包含您的 IP 地址。示例:


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

如果您认为可以安全地假设此字符串永远不会更改或者您愿意需要定期修复它,则可以使用 split()[4] 从该字符串中提取 IP 地址。

或者,如果您想要一个更面向未来的方法,您可以遍历 split() 返回的所有内容并返回第一个 IP 地址项。请参阅此处以验证 IP 地址: How to validate IP address in Python?

【讨论】:

几乎没有“程序化”,但我也使用 DuckDuckGo @Nemo - 什么不是“程序化”的?我没有给出完整的代码示例,但这似乎很多,如果你愿意,你可以使用它。再说一次,我认为自从我写我的之后人们添加的其他一些答案比我的要好。【参考方案15】:

另一种厚颜无耻的方式: 如果您的路由器可以选择在DynDNS 上更新其网络 IP,您可以通过以下方式获得自己的 IP:

IP=`resolveip -s myalias.dyndns-home.com`

【讨论】:

确实很厚脸皮:如果您有动态 DNS 服务,您可能不需要需要找到您的外部 IP 地址,只需使用名称即可!这正是正是此类服务的用途:P【参考方案16】:

现在托管您自己的“无服务器”API 非常便宜。所有主要的云提供商都为此提供服务。例如,使用 Google Cloud Functions 只需:

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

这种方法可能比使用上面的公共服务更可靠,您可以在函数名称中添加一个长随机字符串以保持其私密性。

【讨论】:

以上是关于以编程方式发现公共 IP的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式获取公共 IP 地址?

获取公共/外部 IP 地址?

使用 JAVA 以编程方式到 Google 云存储的 S3 对象(公共 URL)[关闭]

如何使用 JavaScript 查询 STUN 服务器以获取公共 IP 和端口?

可靠(加密)方式验证NAT后面的设备公共IP地址

是否可以将静态公共 IP 分配给 Docker 容器?