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

时间:2017-06-13 21:35:17

标签: python python-requests

问题

我需要检查来自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的片段:

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

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

1 个答案:

答案 0 :(得分:1)

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

创建连接时内部发生的是:

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

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

但是,如果您自己 on 私有网络,则可能会尝试连接到该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'