Python - 使用 requests 模块从 HTTP 请求获取 IP

标签 python python-requests

问题

我需要在请求之前检查 URL 中的域是否未指向私有(private) IP,并返回用于 HTTP 连接的 IP。

这是我的测试脚本:

import ipaddress
import requests
import socket
import sys

from urllib.parse import urlparse


def get_ip(url):
    hostname = socket.gethostbyname(urlparse(url).hostname)
    print('IP: {}'.format(hostname))
    if hostname:
        return ipaddress.IPv4Address(hostname).is_private

def get_req(url):
    private_ip = get_ip(url)
    if not private_ip:
        try:
            with requests.Session() as s:
                s.max_redirects = 5
                r = s.get(url, timeout=5, stream=True)
            return {'url': url, 'staus_code': r.status_code}
        except requests.exceptions.RequestException:
            return 'ERROR'
    return 'Private IP'

if __name__ == '__main__':
    print(get_req(sys.argv[1]))

如果域名解析为多个 IP,例如网站托管在 CloudFlare 后面,则此操作将不起作用:

# python test.py http://example.com
IP: 104.31.65.106
{'staus_code': 200, 'url': 'http://exmaple.com'}

来自 tcpdump 的片段:

22:21:51.833221 IP 1.2.3.4.54786 > 104.31.64.106.80: Flags [S], seq 902413592, win 29200, options [mss 1460,sackOK,TS val 252001723 ecr 0,nop,wscale 7], length 0
22:21:51.835313 IP 104.31.64.106.80 > 1.2.3.4.54786: Flags [S.], seq 2314392251, ack 902413593, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 10], length 0
22:21:51.835373 IP 1.2.3.4.54786 > 104.31.64.106.80: Flags [.], ack 1, win 229, length 0

脚本在 104.31.65.106 上进行了测试,但 HTTP 连接是在 104.31.64.106 上建立的

我看到了this线程,但我不会消耗响应主体,所以 the connection won't be released实际上我的请求模块版本没有这些属性。

有没有办法通过 requests 模块来实现此目的,或者我是否必须使用 urlliburliib3 等其他库?

澄清一下:我只需要在尝试连接到专用网络地址时阻止该请求。如果有多个选项并且选择了公共(public)地址,那就可以了。

最佳答案

urllib3 将自动跳过给定 DNS 名称的不可路由地址。这不是需要预防的事情。

创建连接时内部发生的事情是这样的:

  • 请求 DNS 信息;如果您的系统支持 IPv6(绑定(bind)到 ::1 成功),则其中包括 IPv6 地址。
  • 按照地址列出的顺序,逐一尝试
    • 为每个地址配置一个合适的套接字,并且
    • 套接字被告知连接到 IP 地址
    • 如果连接失败,则尝试下一个IP地址,否则返回已连接的套接字。

请参阅urllib3.util.connection.create_connection() function 。专用网络通常不可路由,因此会自动被跳过。

但是,如果您自己位于专用网络上,则无论如何都可能会尝试连接到该 IP 地址,这可能需要一些时间才能解决。

解决办法是adapt a previous answer of mine允许您在创建套接字连接时解析主机名;这应该可以让您跳过私有(private)使用地址。通过 socket.getaddrinfo() 创建您自己的循环,并在尝试使用专用网络地址时引发异常:

import socket
from ipaddress import ip_address
from urllib3.util import connection


class PrivateNetworkException(Exception):
    pass


_orig_create_connection = connection.create_connection

def patched_create_connection(address, *args, **kwargs):
    """Wrap urllib3's create_connection to resolve the name elsewhere"""
    # resolve hostname to an ip address; use your own
    # resolver here, as otherwise the system resolver will be used.
    family = connection.allowed_gai_family()

    host, port = address
    err = None
    for *_, sa in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
        ip, port = sa
        if ip_address(ip).is_private:
            # Private network address, raise an exception to prevent
            # connecting
            raise PrivateNetworkException(ip)
        try:
            # try to create connection for this one address
            return _orig_create_connection((ip, port), *args, **kwargs)
        except socket.error as err:
            last_err = err
            continue

        if last_err is not None:
            raise last_err

connection.create_connection = patched_create_connection

因此,此代码会提前查找主机的 IP 地址,然后引发自定义异常。捕获该异常:

with requests.Session(max_redirects=5) as s:
    try:
        r = s.get(url, timeout=5, stream=True)
        return {'url': url, 'staus_code': r.status_code}
    except PrivateNetworkException:
        return 'Private IP'
    except requests.exceptions.RequestException:
        return 'ERROR'

关于Python - 使用 requests 模块从 HTTP 请求获取 IP,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44531962/

相关文章:

Python 应用程序行为异常,是否缓存了 URL 参数?

python - c_mul 和常规 python 乘法

javascript - Django 制作按钮 Ajax

django - 有没有最简单的方法来异步运行多个python请求?

python - 为什么我在抓取网站时会得到一个空列表?

php - 在 PHP 中更快地处理多个 cURL 请求

python - 如何在 Python 中将 Elasticsearch 批量索引与单个 JSON 文件结合使用

python - 预期的 Chromecast 音频延迟?

c++ - 如何使用curl将POST请求从python重写为C++

python - 使用python登录网站