问题背景
最近,有少量用户反馈无法访问我们的网站,经过排查,发现他们的IP地址被识别为非允许访问的范围,导致出现403错误。进一步分析后,发现用户的IP地址来自于Cloudflare的主机IP段,这引起了我们的疑惑。
Cloudflare的IP通常用于CDN和反向代理,很少有普通用户直接使用。经过研究,我们发现这与Apple的iCloud+订阅服务中的“专用代理”功能有关。该功能类似于VPN,会使用户的流量通过苹果的服务器进行转发。
苹果官方提供了关于此功能的详细信息以及IP地址列表:
- 为 iCloud 专用代理准备网络或网页服务器: https://developer.apple.com/cn/icloud/prepare-your-network-for-icloud-private-relay/
- 访问 IP 地理位置源: https://mask-api.icloud.com/egress-ip-ranges.csv
我们查看了苹果提供的IP列表,发现其中包含了大量的IP CIDR地址,仅日本(JP)地区的IP CIDR就有6000多行。为了解决这个问题,我们需要自动化地获取并配置这些IP,以及其他搜索引擎的IP地址。
解决方案:使用脚本自动获取和配置IP列表
为了简化管理和减少手动操作,我编写了一个bash脚本,该脚本使用 Python3 来自动获取以下IP列表,并将其配置到我们的服务器中:
- 日本原生IP: 从APNIC获取日本地区的IP段信息。
- iCloud专用代理IP: 从苹果官方API获取iCloud专用代理的IP列表,并筛选出日本地区的IP。
- 搜索引擎IP: 获取Google, Bing, OpenAI和Cloudflare的IP列表,以便于搜索引擎爬虫正常访问。
以下是脚本的具体内容和步骤:
#!/bin/bash
# 配置日本IP列表
configure_japan_ips() {
echo "正在配置日本IP列表..."
# 安装 Python3 和必要的系统包
apt install -y python3-full python3-pip python3-requests || handle_error "Python包安装失败"
# 创建日本IP获取脚本
cat > /root/jpip.py << 'EOF'
import requests
import math
import sys
from pathlib import Path
def download_file(url, filename):
"""下载文件并保存"""
try:
response = requests.get(url)
response.raise_for_status()
with open(filename, 'w') as f:
f.write(response.text)
return True
except Exception as e:
print(f"下载 {url} 失败: {str(e)}", file=sys.stderr)
return False
def download_search_engine_ips():
"""下载并处理搜索引擎和其他服务的IP列表"""
search_ips = []
# 获取Google IP
try:
response = requests.get('https://developers.google.com/search/apis/ipranges/googlebot.json')
google_data = response.json()
search_ips.append("#Google的IP")
for prefix in google_data['prefixes']:
if 'ipv4Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv4Prefix']}")
if 'ipv6Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv6Prefix']}")
except Exception as e:
print(f"获取Google IP失败: {str(e)}", file=sys.stderr)
# 获取Apple爬虫IP
try:
response = requests.get('https://search.developer.apple.com/applebot.json')
apple_data = response.json()
search_ips.append("\n#Apple的IP")
for prefix in apple_data['prefixes']:
if 'ipv4Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv4Prefix']}")
if 'ipv6Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv6Prefix']}")
except Exception as e:
print(f"获取Apple IP失败: {str(e)}", file=sys.stderr)
# 获取Bing IP
try:
response = requests.get('https://www.bing.com/toolbox/bingbot.json')
bing_data = response.json()
search_ips.append("\n#Bing的IP")
for prefix in bing_data['prefixes']:
if 'ipv4Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv4Prefix']}")
if 'ipv6Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv6Prefix']}")
except Exception as e:
print(f"获取Bing IP失败: {str(e)}", file=sys.stderr)
# 获取OpenAI IP
try:
openai_urls = [
'https://openai.com/searchbot.json',
'https://openai.com/gptbot.json',
'https://openai.com/chatgpt-user.json'
]
search_ips.append("\n#OpenAI的IP")
for url in openai_urls:
response = requests.get(url)
openai_data = response.json()
for prefix in openai_data.get('prefixes', []):
if 'ipv4Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv4Prefix']}")
if 'ipv6Prefix' in prefix:
search_ips.append(f"Require ip {prefix['ipv6Prefix']}")
except Exception as e:
print(f"获取OpenAI IP失败: {str(e)}", file=sys.stderr)
# 获取Cloudflare IPv4
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get('https://www.cloudflare.com/ips-v4', headers=headers)
if response.status_code == 200 and not response.text.lower().startswith('= 7 and parts[1] == 'JP' and (parts[2] == 'ipv4' or parts[2] == 'ipv6'):
ip = parts[3]
if parts[2] == 'ipv4':
count = int(parts[4])
prefix = 32 - int(math.log2(count))
else: # ipv6
prefix = parts[4]
japan_ips.add(f"Require ip {ip}/{prefix}")
# 处理iCloud数据
with open('/root/egress-ip-ranges.csv', 'r') as f:
for line in f:
try:
ip_range, country, region, city, *_ = line.strip().split(',')
if country == 'JP':
japan_ips.add(f"Require ip {ip_range}")
except:
continue
# 添加搜索引擎IP下载
download_search_engine_ips()
# 确保输出目录存在
output_path = Path(f"/root/jpip.conf")
output_path.parent.mkdir(parents=True, exist_ok=True)
# 保存到文件
with open(output_path, 'w') as f:
f.write('\n'.join(sorted(japan_ips)))
print(f"成功保存 {len(japan_ips)} 条 IP 记录到 {output_path}")
return 0
except Exception as e:
print(f"错误: {str(e)}", file=sys.stderr)
return 1
if __name__ == '__main__':
sys.exit(main())
EOF
# 设置执行权限
chmod +x /root/jpip.py || handle_error "设置脚本权限失败"
echo "开始运行 jpip.py 脚本..."
python3 /root/jpip.py || handle_error "日本IP列表获取失败"
echo "删除临时文件"
rm -rf /root/delegated-apnic-latest
rm -rf /root/egress-ip-ranges.csv
rm -rf /root/jpip.py
sleep 2
echo "设置每周定时运行"
if ! grep -q "^0 0 \* \* 0 /root/jpip\.sh" /etc/crontab; then
echo "0 0 * * 0 /root/jpip.sh" >> /etc/crontab
systemctl restart cron
echo "已添加定时任务"
else
echo "定时任务已存在,跳过添加"
fi
sleep 2
echo "重启apache2"
systemctl restart apache2
sleep 2
echo "配置完成"
sleep 2
}
# 主函数
main() {
# 检查root权限
[ "$(id -u)" != "0" ] && handle_error "请使用root权限运行此脚本"
configure_japan_ips
}
main
脚本说明
- 错误处理: 使用
handle_error
函数统一处理错误,方便调试。 - 安装依赖: 自动安装
python3-full
,python3-pip
和python3-requests
。 - Python脚本 (
/root/jpip.py
):- 使用
requests
库下载 APNIC 数据,iCloud IP列表和搜索引擎的IP列表。 - 解析 APNIC 数据,筛选出日本地区的 IPv4 和 IPv6 地址,并计算前缀长度。
- 解析 iCloud IP 列表,筛选出日本地区的 IP 段。
- 下载并解析Google, Bing, OpenAI和Cloudflare的IP列表。
- 将所有IP地址保存到
/root/jpip.conf
和/root/searchip.conf
文件中。
- 使用
- 脚本执行:
- 设置 Python 脚本的执行权限。
- 执行 Python 脚本,获取并保存 IP 地址。
- 删除临时文件。
- 设置定时任务,每周日凌晨0点自动更新 IP 地址列表。
- 重启
apache2
使配置生效。
使用方法
- 将上述脚本保存为
/root/jpip.sh
。 - 赋予执行权限:
chmod +x /root/jpip.sh
- 以 root 用户运行脚本:
bash /root/jpip.sh
- 脚本运行后,会在
/root/
目录下生成jpip.conf
文件,该文件包含了所有需要允许访问的IP地址。以及/root/searchip.conf
包含搜索引擎的IP地址。 - 根据实际情况,将
/root/jpip.conf
和/root/searchip.conf
文件中的内容配置到你的Web服务器(如Apache或Nginx)的IP访问控制规则中。
- 例如在Apache中你可以使用
Require ip
指令。
结论
通过使用该脚本,我们能够自动获取并更新日本地区的原生IP、iCloud专用代理IP以及搜索引擎IP列表。这样,既可以解决由于iCloud专用代理导致的访问问题,同时也能保证搜索引擎爬虫的正常访问。
希望这篇文章能够帮助你解决类似的问题。如有任何疑问,请随时留言。