一、项目简介
这是一套基于 Python + Flask 的极简、安全、轻量的 IPv6 在线检测系统,专为服务器 IPv6 连通性、域名 IPv6 解析、TCP 端口开放状态检测设计。
- IPv6 Ping 检测:通过 ICMPv6 协议测试目标地址的连通性、延迟和丢包率
- TCP 端口检测:测试 IPv6 地址的指定端口是否开放
技术特点:
- 代码极简,核心逻辑清晰
- 采用容器化部署,开箱即用
- 已配置 CORS 跨域支持,前端可直接调用 API
- 内置频率限制,防止接口被滥用
- 非 root 用户运行,确保安全性
项目地址:
在线演示:https://ip.hx99.net/
项目地址:https://gitee.com/cncsrf/ipcahxun/
关联文档:https://blog.hx99.net/Tech/3483.html


二、环境要求
- 依赖:Docker、Docker Compose(可选)
- 网络:服务器必须具备 IPv6 公网地址
三、快速部署
1. 目录创建
直接在服务器上创建项目目录并进入。
mkdir -p ~/ipv6-checker
cd ~/ipv6-checker
2. 核心文件
项目包含 3 个核心文件:
- app.py:Flask API 服务,提供 Ping / TCP 检测接口
- requirements.txt:Python 依赖包
- Dockerfile:容器构建脚本
① app.py – API 服务(纯 JSON 接口,带 CORS 跨域支持)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify
import socket
import ipaddress
from flask_cors import CORS
import re
import subprocess
import time
from datetime import datetime
from functools import wraps
app = Flask(__name__)
CORS(app) # 启用 CORS 跨域支持
# ============ 频率限制 ============
RATE_LIMIT = {}
RATE_LIMIT_WINDOW = 60
RATE_LIMIT_MAX = 20
def rate_limit_check(client_ip):
now = time.time()
if client_ip not in RATE_LIMIT:
RATE_LIMIT[client_ip] = []
RATE_LIMIT[client_ip] = [t for t in RATE_LIMIT[client_ip] if now - t < RATE_LIMIT_WINDOW]
if len(RATE_LIMIT[client_ip]) >= RATE_LIMIT_MAX:
return False
RATE_LIMIT[client_ip].append(now)
return True
def rate_limit_decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_ip = request.remote_addr
if not rate_limit_check(client_ip):
return jsonify({
'success': False,
'error': f'Rate limit exceeded. Max {RATE_LIMIT_MAX} requests per {RATE_LIMIT_WINDOW} seconds'
}), 429
return f(*args, **kwargs)
return decorated_function
# ============ 验证函数 ============
def is_valid_ipv6(address):
try:
ipaddress.IPv6Address(address)
return True
except:
return False
def is_valid_domain(domain):
pattern = r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
return re.match(pattern, domain) is not None
def resolve_ipv6(hostname):
try:
addr_info = socket.getaddrinfo(hostname, None, socket.AF_INET6)
return addr_info[0][4][0]
except Exception as e:
return None
# ============ Ping 功能 ============
def ping_ipv6(target, count=4, timeout=2):
try:
cmd = ['ping6', '-c', str(count), '-W', str(timeout), target]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
output = result.stdout + result.stderr
loss_match = re.search(r'(\d+)% packet loss', output)
loss = int(loss_match.group(1)) if loss_match else 100
avg_match = re.search(r'rtt min/avg/max/mdev = [\d.]+/([\d.]+)/', output)
avg_time = float(avg_match.group(1)) if avg_match else None
min_match = re.search(r'rtt min/avg/max/mdev = ([\d.]+)/', output)
min_time = float(min_match.group(1)) if min_match else None
max_match = re.search(r'rtt min/avg/max/mdev = [\d.]+/[\d.]+/([\d.]+)/', output)
max_time = float(max_match.group(1)) if max_match else None
return {
'success': loss < 100,
'loss': loss,
'avg_time': avg_time,
'min_time': min_time,
'max_time': max_time,
'transmitted': count,
'received': count - int(count * loss / 100)
}
except subprocess.TimeoutExpired:
return {'success': False, 'loss': 100, 'error': 'Ping command timeout'}
except Exception as e:
return {'success': False, 'loss': 100, 'error': str(e)}
# ============ TCP 端口检测 ============
def tcp_check(ip, port, timeout=5):
try:
start_time = datetime.now()
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((ip, port))
end_time = datetime.now()
elapsed_ms = int((end_time - start_time).total_seconds() * 1000)
sock.close()
if result == 0:
return {'success': True, 'time_ms': elapsed_ms}
else:
error_map = {
10060: 'Connection timeout',
10061: 'Connection refused',
111: 'Connection refused',
110: 'Connection timeout',
113: 'No route to host'
}
return {'success': False, 'error': error_map.get(result, f'Error code: {result}')}
except socket.timeout:
return {'success': False, 'error': f'Connection timeout ({timeout}s)'}
except Exception as e:
return {'success': False, 'error': str(e)}
# ============ API 接口 ============
@app.route('/', methods=['GET'])
def index():
return jsonify({
'service': 'IPv6 Checker API',
'version': '1.0',
'endpoints': {
'/api/ping': {
'method': 'POST',
'description': 'Test IPv6 connectivity using ICMP ping',
'body': {'target': 'ipv6 address or domain'}
},
'/api/tcp': {
'method': 'POST',
'description': 'Test TCP port connectivity',
'body': {'target': 'ipv6 address or domain', 'port': 'port number', 'timeout': 'seconds (optional)'}
},
'/health': {
'method': 'GET',
'description': 'Health check'
}
}
})
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'ok', 'timestamp': datetime.now().isoformat()})
@app.route('/api/ping', methods=['POST'])
@rate_limit_decorator
def api_ping():
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'Invalid JSON body'}), 400
target = data.get('target', '').strip()
if not target:
return jsonify({'success': False, 'error': 'Missing target parameter'}), 400
is_ip = is_valid_ipv6(target)
is_domain = is_valid_domain(target)
if not is_ip and not is_domain:
return jsonify({'success': False, 'error': 'Invalid IPv6 address or domain format'}), 400
ip_address = target
if is_domain:
ip_address = resolve_ipv6(target)
if not ip_address:
return jsonify({'success': False, 'error': 'Failed to resolve domain to IPv6'}), 400
count = data.get('count', 4)
timeout = data.get('timeout', 2)
result = ping_ipv6(ip_address, count, timeout)
result['ip'] = ip_address
result['target'] = target
result['timestamp'] = datetime.now().isoformat()
return jsonify(result)
@app.route('/api/tcp', methods=['POST'])
@rate_limit_decorator
def api_tcp():
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'Invalid JSON body'}), 400
target = data.get('target', '').strip()
port = data.get('port')
if not target:
return jsonify({'success': False, 'error': 'Missing target parameter'}), 400
if not port:
return jsonify({'success': False, 'error': 'Missing port parameter'}), 400
try:
port = int(port)
if port < 1 or port > 65535:
raise ValueError
except:
return jsonify({'success': False, 'error': 'Port must be between 1 and 65535'}), 400
timeout = data.get('timeout', 5)
is_ip = is_valid_ipv6(target)
is_domain = is_valid_domain(target)
if not is_ip and not is_domain:
return jsonify({'success': False, 'error': 'Invalid IPv6 address or domain format'}), 400
ip_address = target
if is_domain:
ip_address = resolve_ipv6(target)
if not ip_address:
return jsonify({'success': False, 'error': 'Failed to resolve domain to IPv6'}), 400
result = tcp_check(ip_address, port, timeout)
result['ip'] = ip_address
result['target'] = target
result['port'] = port
result['timestamp'] = datetime.now().isoformat()
return jsonify(result)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=False, threaded=True)
② requirements.txt – Python 依赖
Flask==2.3.3
flask-cors==4.0.0
③ Dockerfile – 容器构建文件
FROM python:3.11-alpine
# 安装 ping6
RUN apk add --no-cache iputils
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip install --no-cache-dir gunicorn
COPY app.py .
# 创建非 root 用户
RUN addgroup -g 1000 -S appgroup && \
adduser -u 1000 -S appuser -G appgroup
USER appuser
EXPOSE 8080
# 生产环境推荐(使用 gunicorn)
CMD ["gunicorn", "-b", "0.0.0.0:8080", "-w", "4", "app:app"]
3. 构建镜像
进入目录后执行构建命令。
cd ~/ipv6-checker
# 构建镜像
docker build -t ipv6-checker .
4. 启动容器
使用 host 网络模式运行,确保 IPv6 正常使用,并配置开机自启。
# 运行容器(使用 host 网络)
docker run -d \
--name ipv6-checker \
--network host \
--restart unless-stopped \
ipv6-checker
5. 服务验证
- 查看容器运行状态
- 访问健康检查接口确认服务正常
- 使用 curl 测试 Ping / TCP 检测接口
# 验证
docker logs ipv6-checker
curl http://localhost:8080/health
curl -X POST http://localhost:8080/api/ping -H "Content-Type: application/json" -d '{"target": "www.qq.com"}'
五、API 接口说明
基础接口
| 接口 | 方法 | 说明 |
|---|---|---|
/health |
GET | 健康检查 |
/api/ping |
POST | IPv6 Ping 检测 |
/api/tcp |
POST | TCP 端口检测 |
接口说明
所有接口返回统一 JSON 格式,包含状态、延迟、丢包率、错误信息等。
Ping 请求示例:
curl -X POST http://localhost:8080/api/ping \
-H "Content-Type: application/json" \
-d '{"target": "2001:4860:4860::8888"}'
TCP 端口检测示例:
curl -X POST http://localhost:8080/api/tcp \
-H "Content-Type: application/json" \
-d '{"target": "www.qq.com", "port": 80}'
六、前端对接说明
- 已内置 flask-cors 开启全跨域,前端可直接调用
- 无需额外配置 Nginx 即可使用
- 如需域名访问,可使用 Nginx 反向代理
- 注:为避免对其它api干扰,ad80a1/ipv6-checker:latest镜像内置跨域功能未开启
七、Nginx 反向代理配置
如需通过域名 + SSL 访问,可配置反向代理,正常传递真实 IP 等头部信息即可。
八、日常运维命令
# 查看日志
docker logs -f ipv6-checker
# 停止容器
docker stop ipv6-checker
# 启动容器
docker start ipv6-checker
# 重启容器
docker restart ipv6-checker
# 删除容器
docker stop ipv6-checker && docker rm ipv6-checker
# 进入容器调试
docker exec -it ipv6-checker sh
九、后续一键部署方案
将镜像推送到 Docker Hub 后,后续仅需一条命令即可完成部署:
docker run -d \
--name ipv6-checker \
--network host \
--restart unless-stopped \
ad80a1/ipv6-checker:latest
十、常见问题
- 无法检测 IPv6检查服务器是否获取公网 IPv6 地址,关闭防火墙 IPv6 限制。
- Ping 全部丢包确认目标支持 IPv6,或检查本机 IPv6 路由与防火墙规则。
- API 访问报错 429触发频率限制,等待 60 秒后重试,可自行修改代码调整限流策略。
- Docker 启动失败检查端口 8080 是否被占用,或更换映射端口。
十一、总结
本套 IPv6 检测系统兼顾轻量化、安全性、易用性,非常适合作为:
- 个人 IPv6 网络检测工具
- 企业内网 IPv6 服务巡检平台
- 网站 IPv6 支持性检测入口
- 集成到运维系统的检测模块
部署简单、运行稳定、拓展方便,是 IPv6 时代必备小工具。