问题
我需要检查来自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
我看到this线程,但我不会使用响应正文the connection won't be released,实际上我的请求模块版本没有这些属性。
有没有办法通过requests
模块实现此功能,还是必须使用其他库urllib
或urliib3
?
澄清一下:如果尝试连接到专用网络地址,我只需要阻止该请求。如果有多个选项并且选择了公共地址,那就没关系。
答案 0 :(得分:1)
urllib3
会自动跳过给定DNS名称的不可路由地址。这不是需要预防的事情。
创建连接时内部发生的是:
::1
成功),则包括IPv6地址。请参阅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'