【问题标题】:Python - get IP from HTTP request using requests modulePython - 使用请求模块从 HTTP 请求中获取 IP
【发布时间】:2017-11-15 20:23:34
【问题描述】:

问题

我需要在请求之前检查来自 URL 的域是否未指向私有 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 的 sn-p:

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 上对其进行了测试,但在104.31.64.106 上建立了 HTTP 连接

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

有没有办法通过requests 模块实现这一点,还是我必须使用另一个库,如urlliburliib3

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

【问题讨论】:

  • 为什么rsp=requests.get(..., stream=True);rsp.raw._connection.sock.getpeername() 不适合你?
  • 好的,所以我刚刚测试了它,我想我可以在 try/except 块中关闭连接,但看起来流只有在服务器启用了 keep-alive 时才有效,否则连接会立即关闭,我得到AttributeError: 'NoneType' object has no attribute 'getpeername'。我还想在发出请求之前检查 IP。
  • 为什么所有的恶作剧都是with requests.Session() as s 然后s = requests.Session()?这只是替换了您配置的会话,删除 s = ... 行。
  • @MartijnPieters 我相信这是测试留下的,我刚刚删除了它。
  • 如果主机名可以解析为公共地址或私有地址,您想阻止该请求吗?或者仅当当前选择的 IP 地址不是公共地址时?

标签: python python-requests


【解决方案1】:

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

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

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

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

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

解决方案是adapt a previous answer of mine,它可以让您在创建套接字连接的位置解析主机名;这应该让您跳过私人使用地址。在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'

【讨论】:

  • 谢谢,有什么建议可以让我将连接实际建立的 IP 传递给requests.raw._original_response
  • @HTF:我假设您使用的是 Python 3,因此您在 SO 上找到的适用于 Python 2 的其他答案不再有效。那是因为套接字文件现在有点复杂。 requests.raw._original_responsehttp.client.HTTPResponse 实例,.fp 是套接字文件,这里包含一个缓冲区,该缓冲区将SocketIO 对象与_sock 属性中的实际套接字一起包装。所以原始套接字可以作为requests.raw._original_response.fp.raw._sock 使用。请致电.getpeername()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-28
  • 2020-12-28
  • 2011-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多