问题
我需要在请求之前检查 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
模块来实现此目的,或者我是否必须使用 urllib
或 urliib3
等其他库?
澄清一下:我只需要在尝试连接到专用网络地址时阻止该请求。如果有多个选项并且选择了公共(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/