mirror of
https://github.com/isboyjc/GoProxy.git
synced 2026-05-06 20:02:54 +08:00
feat: ✨ add SOCKS5 proxy support and enhance configuration
- Introduced SOCKS5 proxy functionality with separate ports for random rotation and lowest latency. - Updated `.env.example` and `docker-compose.yml` to include SOCKS5 port configurations. - Enhanced `main.go` to initialize and start SOCKS5 servers alongside existing HTTP proxies. - Revised README to document new SOCKS5 features, including usage examples and testing scripts. - Improved proxy selection logic in the storage layer to support SOCKS5 protocol.
This commit is contained in:
@@ -2,9 +2,11 @@
|
|||||||
CONTAINER_NAME=goproxy
|
CONTAINER_NAME=goproxy
|
||||||
|
|
||||||
# 端口配置
|
# 端口配置
|
||||||
STABLE_PORT=7776 # 最低延迟代理端口
|
STABLE_PORT=7776 # HTTP 最低延迟代理端口
|
||||||
RANDOM_PORT=7777 # 随机轮换代理端口
|
RANDOM_PORT=7777 # HTTP 随机轮换代理端口
|
||||||
WEBUI_PORT=7778 # WebUI 管理端口
|
WEBUI_PORT=7778 # WebUI 管理端口
|
||||||
|
SOCKS5_RANDOM_PORT=7779 # SOCKS5 随机轮换代理端口
|
||||||
|
SOCKS5_STABLE_PORT=7780 # SOCKS5 最低延迟代理端口
|
||||||
|
|
||||||
# 地理过滤配置
|
# 地理过滤配置
|
||||||
# 屏蔽指定国家代码的出口代理(逗号分隔,如 CN,RU,KP)
|
# 屏蔽指定国家代码的出口代理(逗号分隔,如 CN,RU,KP)
|
||||||
|
|||||||
447
README.md
447
README.md
@@ -7,12 +7,23 @@
|
|||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://go.dev/)
|
[](https://go.dev/)
|
||||||
|
|
||||||
GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供统一的 HTTP 代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。
|
GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供 **HTTP 和 SOCKS5 双协议**代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。
|
||||||
|
|
||||||
**GitHub**:[github.com/isboyjc/GoProxy](https://github.com/isboyjc/GoProxy)
|
**GitHub**:[github.com/isboyjc/GoProxy](https://github.com/isboyjc/GoProxy)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 📋 快速导航
|
||||||
|
|
||||||
|
- [✨ 核心特性](#-核心特性) - 智能池子、按需抓取、健康管理、双协议支持
|
||||||
|
- [🔌 HTTP vs SOCKS5 协议对比](#-http-vs-socks5-协议对比) - 协议区别、使用建议
|
||||||
|
- [🚀 快速开始](#-快速开始) - 本地运行、端口说明、代理使用
|
||||||
|
- [🐳 Docker 部署](#-docker-部署) - 容器化部署、环境配置、安全建议
|
||||||
|
- [⚙️ 配置说明](#️-配置说明) - 完整参数详解、性能优化
|
||||||
|
- [🛠️ 开发与调试](#️-开发与调试) - 日志查看、数据库操作
|
||||||
|
- [🧪 测试代理服务](#-测试代理服务) - 测试脚本、持续测试、认证测试
|
||||||
|
- [❓ 常见问题](#-常见问题) - SOCKS5 使用、浏览器配置、故障排查
|
||||||
|
|
||||||
## ✨ 核心特性
|
## ✨ 核心特性
|
||||||
|
|
||||||
### 🎯 智能池子机制
|
### 🎯 智能池子机制
|
||||||
@@ -41,12 +52,17 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
|
|||||||
- **用户无感知**:自动重试在服务端完成,用户只会收到成功响应或最终失败提示
|
- **用户无感知**:自动重试在服务端完成,用户只会收到成功响应或最终失败提示
|
||||||
- **防重复尝试**:已尝试过的失败代理不会在同一请求中再次使用
|
- **防重复尝试**:已尝试过的失败代理不会在同一请求中再次使用
|
||||||
|
|
||||||
### 🚪 双端口策略
|
### 🚪 多端口多协议支持
|
||||||
- **7777 端口(随机轮换)**:每次请求随机选择代理,IP 高度分散,适合爬虫和数据采集
|
- **双协议支持**:同时提供 HTTP 和 SOCKS5 协议服务,满足不同应用需求
|
||||||
- **7776 端口(最低延迟)**:固定使用延迟最低的代理,除非失败才切换,适合长连接和流媒体
|
- **双模式策略**:每种协议都提供随机轮换和最低延迟两种模式
|
||||||
- **自动切换**:两个端口都支持失败自动重试,7776 失败后切换到次优代理
|
- **4 个服务端口**:
|
||||||
- **共享池子**:两个端口使用同一个代理池,统一管理和优化
|
- `7777` - HTTP 随机轮换(IP 多样性,适合爬虫)
|
||||||
- **可选认证**:支持 Basic Auth 认证保护,对外开放时可启用
|
- `7776` - HTTP 最低延迟(稳定连接,适合流媒体)
|
||||||
|
- `7779` - SOCKS5 随机轮换(IP 多样性,适合浏览器/游戏)
|
||||||
|
- `7780` - SOCKS5 最低延迟(稳定连接,适合固定应用)
|
||||||
|
- **自动切换**:所有端口都支持失败自动重试,智能切换备用代理
|
||||||
|
- **共享池子**:四个端口使用同一个代理池,统一管理和优化
|
||||||
|
- **可选认证**:支持 Basic Auth(HTTP)和用户名/密码认证(SOCKS5),对外开放时可启用
|
||||||
|
|
||||||
### 🎨 黑客风格 WebUI
|
### 🎨 黑客风格 WebUI
|
||||||
- **Matrix 美学**:荧光绿 + 纯黑背景,CRT 扫描线效果,JetBrains Mono 等宽字体
|
- **Matrix 美学**:荧光绿 + 纯黑背景,CRT 扫描线效果,JetBrains Mono 等宽字体
|
||||||
@@ -58,15 +74,40 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
|
|||||||
- **交互优化**:点击地址复制、单个代理刷新、实时日志倒计时
|
- **交互优化**:点击地址复制、单个代理刷新、实时日志倒计时
|
||||||
|
|
||||||
### 📊 适用场景
|
### 📊 适用场景
|
||||||
|
- **Web 开发测试**:HTTP 代理测试 API、爬虫开发、数据采集
|
||||||
|
- **浏览器代理**:SOCKS5 协议配置浏览器代理,访问受限网站
|
||||||
|
- **命令行工具**:curl、wget、git 等工具使用 HTTP 代理
|
||||||
|
- **应用代理**:需要 SOCKS5 协议的应用(SSH、游戏、聊天工具)
|
||||||
- **小型 VPS**:低资源消耗(固定池子 + 按需抓取 + 限流查询)
|
- **小型 VPS**:低资源消耗(固定池子 + 按需抓取 + 限流查询)
|
||||||
- **稳定需求**:自动剔除失败代理,始终保持健康池子
|
- **稳定需求**:自动剔除失败代理,始终保持健康池子
|
||||||
- **质量优先**:S/A 级代理优先使用,自动优化延迟
|
- **质量优先**:S/A 级代理优先使用,自动优化延迟
|
||||||
|
|
||||||
|
### 🔌 HTTP vs SOCKS5 协议对比
|
||||||
|
|
||||||
|
| 特性 | HTTP 代理 | SOCKS5 代理 |
|
||||||
|
|------|----------|-------------|
|
||||||
|
| **协议支持** | 仅 HTTP/HTTPS | 所有 TCP 协议(HTTP/HTTPS/SSH/FTP/游戏等) |
|
||||||
|
| **工作层级** | 应用层(Layer 7) | 会话层(Layer 5) |
|
||||||
|
| **浏览器支持** | ✅ 良好 | ✅ 更好(无协议限制) |
|
||||||
|
| **命令行工具** | ✅ curl/wget 原生支持 | ⚠️ 部分工具需要额外配置 |
|
||||||
|
| **非 HTTP 应用** | ❌ 不支持 | ✅ 完全支持(SSH/游戏/聊天) |
|
||||||
|
| **UDP 支持** | ❌ 不支持 | ✅ 支持(SOCKS5 协议特性) |
|
||||||
|
| **性能开销** | 较低 | 稍高 |
|
||||||
|
| **认证方式** | Basic Auth | 用户名/密码 |
|
||||||
|
|
||||||
|
**快速选择建议**:
|
||||||
|
- 🌐 **HTTP 代理**(端口 7776/7777):适合 Web 开发、API 测试、爬虫、数据采集
|
||||||
|
- 🔒 **SOCKS5 代理**(端口 7779/7780):适合浏览器、SSH 隧道、游戏、聊天应用、需要完整协议支持的场景
|
||||||
|
|
||||||
|
**架构设计**:
|
||||||
|
- HTTP 代理服务:可使用池中的 HTTP 或 SOCKS5 上游代理
|
||||||
|
- SOCKS5 代理服务:**仅使用 SOCKS5 上游代理**(因为许多免费 HTTP 代理不支持 HTTPS CONNECT 方法)
|
||||||
|
|
||||||
### 📝 扩展文档
|
### 📝 扩展文档
|
||||||
|
|
||||||
- [地理过滤配置指南](GEO_FILTER.md) - 国家代码、使用场景、测试方法
|
- [地理过滤配置指南](GEO_FILTER.md) - 国家代码、使用场景、测试方法
|
||||||
- [数据目录说明](DATA_DIRECTORY.md) - 数据库、配置文件、备份恢复
|
- [数据目录说明](DATA_DIRECTORY.md) - 数据库、配置文件、备份恢复
|
||||||
- [测试脚本使用](test/README.md) - Bash/Go/Python 测试脚本详细说明
|
- [测试脚本使用](test/README.md) - HTTP + SOCKS5 测试脚本详细说明
|
||||||
- [架构设计文档](POOL_DESIGN.md) - 完整的系统设计和实现细节
|
- [架构设计文档](POOL_DESIGN.md) - 完整的系统设计和实现细节
|
||||||
|
|
||||||
## 📦 项目结构
|
## 📦 项目结构
|
||||||
@@ -84,11 +125,14 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
|
|||||||
├── checker/ # 🆕 分批健康检查器
|
├── checker/ # 🆕 分批健康检查器
|
||||||
├── optimizer/ # 🆕 优化轮换器(定时优化池子质量)
|
├── optimizer/ # 🆕 优化轮换器(定时优化池子质量)
|
||||||
├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表)
|
├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表)
|
||||||
├── proxy/ # 🆕 对外 HTTP 代理服务(双端口 + 可选认证)
|
├── proxy/ # 🆕 对外代理服务(HTTP + SOCKS5 双协议,4 端口 + 可选认证)
|
||||||
|
│ ├── server.go # HTTP 代理服务器
|
||||||
|
│ └── socks5_server.go # SOCKS5 代理服务器
|
||||||
├── webui/ # 🆕 黑客风格 WebUI(健康仪表盘、配置界面、RBAC)
|
├── webui/ # 🆕 黑客风格 WebUI(健康仪表盘、配置界面、RBAC)
|
||||||
├── logger/ # 内存日志收集
|
├── logger/ # 内存日志收集
|
||||||
├── test/ # 🧪 测试脚本与文档
|
├── test/ # 🧪 测试脚本与文档
|
||||||
│ ├── test_proxy.sh # Bash 测试脚本
|
│ ├── test_proxy.sh # HTTP 代理测试脚本(Bash)
|
||||||
|
│ ├── test_socks5.sh # SOCKS5 代理测试脚本(Bash)
|
||||||
│ ├── test_proxy.go # Go 测试脚本
|
│ ├── test_proxy.go # Go 测试脚本
|
||||||
│ ├── test_proxy.py # Python 测试脚本
|
│ ├── test_proxy.py # Python 测试脚本
|
||||||
│ └── README.md # 测试脚本使用说明
|
│ └── README.md # 测试脚本使用说明
|
||||||
@@ -134,22 +178,34 @@ go build -o proxygo .
|
|||||||
- 状态监控(每 30 秒)
|
- 状态监控(每 30 秒)
|
||||||
- 健康检查(默认 5 分钟)
|
- 健康检查(默认 5 分钟)
|
||||||
- 优化轮换(默认 30 分钟)
|
- 优化轮换(默认 30 分钟)
|
||||||
7. 启动两个代理服务(支持可选认证):
|
7. 启动四个代理服务(支持可选认证):
|
||||||
- `:7776` - 最低延迟模式(稳定连接)
|
- `:7776` - HTTP 最低延迟模式(稳定连接)
|
||||||
- `:7777` - 随机轮换模式(IP 多样性)
|
- `:7777` - HTTP 随机轮换模式(IP 多样性)
|
||||||
|
- `:7779` - SOCKS5 随机轮换模式(IP 多样性)
|
||||||
|
- `:7780` - SOCKS5 最低延迟模式(稳定连接)
|
||||||
|
|
||||||
### 默认端口
|
### 默认端口
|
||||||
- **代理服务(随机轮换)**:`7777` - 每次请求随机选择代理,IP 多样性高
|
|
||||||
- **代理服务(最低延迟)**:`7776` - 固定使用延迟最低的代理,性能优先
|
#### HTTP 代理端口
|
||||||
|
- **7777 端口(HTTP 随机轮换)**:每次请求随机选择代理,IP 多样性高
|
||||||
|
- **7776 端口(HTTP 最低延迟)**:固定使用延迟最低的代理,性能优先
|
||||||
|
|
||||||
|
#### SOCKS5 代理端口
|
||||||
|
- **7779 端口(SOCKS5 随机轮换)**:每次连接随机选择代理,IP 多样性高
|
||||||
|
- **7780 端口(SOCKS5 最低延迟)**:固定使用延迟最低的代理,性能优先
|
||||||
|
|
||||||
|
#### 管理端口
|
||||||
- **WebUI**:`7778`
|
- **WebUI**:`7778`
|
||||||
- **默认密码**:`goproxy`(可通过 `WEBUI_PASSWORD` 环境变量自定义)
|
- **默认密码**:`goproxy`(可通过 `WEBUI_PASSWORD` 环境变量自定义)
|
||||||
- **访问方式**:本地使用 `localhost`,远程使用服务器 IP 地址
|
- **访问方式**:本地使用 `localhost`,远程使用服务器 IP 地址
|
||||||
|
|
||||||
### 使用代理
|
### 使用代理
|
||||||
|
|
||||||
GoProxy 提供**两个代理端口**,满足不同场景需求:
|
GoProxy 提供**四个代理端口**,支持 HTTP 和 SOCKS5 两种协议,满足不同场景需求:
|
||||||
|
|
||||||
#### 🎲 7777 端口 - 随机轮换模式
|
#### 🌐 HTTP 协议代理
|
||||||
|
|
||||||
|
##### 🎲 7777 端口 - HTTP 随机轮换模式
|
||||||
|
|
||||||
适合需要 **IP 多样性** 的场景(爬虫、数据采集、负载均衡):
|
适合需要 **IP 多样性** 的场景(爬虫、数据采集、负载均衡):
|
||||||
|
|
||||||
@@ -166,7 +222,7 @@ curl -x http://your-server-ip:7777 https://httpbin.org/ip
|
|||||||
- 优先使用高质量(S/A 级)代理
|
- 优先使用高质量(S/A 级)代理
|
||||||
- IP 地址高度分散
|
- IP 地址高度分散
|
||||||
|
|
||||||
#### ⚡ 7776 端口 - 最低延迟模式
|
##### ⚡ 7776 端口 - HTTP 最低延迟模式
|
||||||
|
|
||||||
适合需要 **稳定连接** 的场景(长连接、流媒体、实时通信):
|
适合需要 **稳定连接** 的场景(长连接、流媒体、实时通信):
|
||||||
|
|
||||||
@@ -184,9 +240,49 @@ curl -x http://your-server-ip:7776 https://httpbin.org/ip
|
|||||||
- 失败时自动删除并切换到下一个最低延迟代理
|
- 失败时自动删除并切换到下一个最低延迟代理
|
||||||
- 性能和稳定性最优
|
- 性能和稳定性最优
|
||||||
|
|
||||||
|
#### 🔌 SOCKS5 协议代理
|
||||||
|
|
||||||
|
##### 🎲 7779 端口 - SOCKS5 随机轮换模式
|
||||||
|
|
||||||
|
适合需要 **原生 SOCKS5** 和 **IP 多样性** 的场景:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 curl(需要 7.21.7+)
|
||||||
|
curl --socks5 localhost:7779 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# 远程使用
|
||||||
|
curl --socks5 your-server-ip:7779 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# 使用 proxychains
|
||||||
|
echo "socks5 127.0.0.1 7779" > ~/.proxychains.conf
|
||||||
|
proxychains4 curl https://httpbin.org/ip
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
- 原生 SOCKS5 协议,更广泛的应用支持
|
||||||
|
- 每次连接随机选择代理
|
||||||
|
- 支持 TCP 和 UDP(如果上游代理支持)
|
||||||
|
|
||||||
|
##### ⚡ 7780 端口 - SOCKS5 最低延迟模式
|
||||||
|
|
||||||
|
适合需要 **SOCKS5 协议** 和 **稳定连接** 的场景:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地使用
|
||||||
|
curl --socks5 localhost:7780 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# 远程使用
|
||||||
|
curl --socks5 your-server-ip:7780 https://httpbin.org/ip
|
||||||
|
```
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
- 固定使用延迟最低的代理
|
||||||
|
- 适合需要 SOCKS5 协议的应用(如浏览器、游戏客户端)
|
||||||
|
- 最佳性能和稳定性
|
||||||
|
|
||||||
#### 环境变量配置
|
#### 环境变量配置
|
||||||
|
|
||||||
**本地使用**:
|
**HTTP 代理**:
|
||||||
```bash
|
```bash
|
||||||
# 使用随机模式
|
# 使用随机模式
|
||||||
export http_proxy=http://localhost:7777
|
export http_proxy=http://localhost:7777
|
||||||
@@ -195,25 +291,77 @@ export https_proxy=http://localhost:7777
|
|||||||
# 或使用稳定模式
|
# 或使用稳定模式
|
||||||
export http_proxy=http://localhost:7776
|
export http_proxy=http://localhost:7776
|
||||||
export https_proxy=http://localhost:7776
|
export https_proxy=http://localhost:7776
|
||||||
|
|
||||||
|
# 远程使用(带认证)
|
||||||
|
export http_proxy=http://proxy:your_password@your-server-ip:7777
|
||||||
|
export https_proxy=http://proxy:your_password@your-server-ip:7777
|
||||||
```
|
```
|
||||||
|
|
||||||
**远程使用(带认证)**:
|
**SOCKS5 代理**(更多应用支持):
|
||||||
```bash
|
```bash
|
||||||
# 格式:http://username:password@host:port
|
# 使用随机模式
|
||||||
export http_proxy=http://proxy:your_password@192.168.1.100:7777
|
export ALL_PROXY=socks5://localhost:7779
|
||||||
export https_proxy=http://proxy:your_password@192.168.1.100:7777
|
|
||||||
|
# 或使用稳定模式
|
||||||
|
export ALL_PROXY=socks5://localhost:7780
|
||||||
|
|
||||||
|
# 远程使用(带认证)
|
||||||
|
export ALL_PROXY=socks5://proxy:your_password@your-server-ip:7779
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 端口对比
|
#### 端口对比
|
||||||
|
|
||||||
| 特性 | 7777(随机轮换) | 7776(最低延迟) |
|
| 端口 | 协议 | 模式 | IP 多样性 | 稳定性 | 性能 | 适用场景 |
|
||||||
|------|-----------------|-----------------|
|
|------|------|------|----------|--------|------|---------|
|
||||||
| **选择策略** | 随机选择(优先高质量) | 固定使用延迟最低的 |
|
| **7777** | HTTP | 随机轮换 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 爬虫、数据采集 |
|
||||||
| **IP 多样性** | ⭐⭐⭐⭐⭐ 高度分散 | ⭐ 基本固定 |
|
| **7776** | HTTP | 最低延迟 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 长连接、流媒体 |
|
||||||
| **连接稳定性** | ⭐⭐⭐ 每次切换 | ⭐⭐⭐⭐⭐ 固定不变 |
|
| **7779** | SOCKS5 | 随机轮换 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 浏览器、游戏、SSH |
|
||||||
| **性能表现** | ⭐⭐⭐⭐ 平均延迟 | ⭐⭐⭐⭐⭐ 最优延迟 |
|
| **7780** | SOCKS5 | 最低延迟 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 稳定应用连接 |
|
||||||
| **适用场景** | 爬虫、数据采集、防封禁 | 长连接、流媒体、下载 |
|
|
||||||
| **失败切换** | 自动重试 3 次 | 失败后切换到次优代理 |
|
#### 协议选择建议
|
||||||
|
|
||||||
|
**何时使用 HTTP 代理(7776/7777)**:
|
||||||
|
- 简单的 HTTP/HTTPS 请求
|
||||||
|
- curl、wget 等命令行工具
|
||||||
|
- 大多数编程语言的 HTTP 客户端
|
||||||
|
|
||||||
|
**何时使用 SOCKS5 代理(7779/7780)**:
|
||||||
|
- 需要代理非 HTTP 协议(如 SSH、FTP、游戏)
|
||||||
|
- 浏览器代理设置(SOCKS5 支持更好)
|
||||||
|
- 需要 UDP 支持的应用
|
||||||
|
- 某些应用只支持 SOCKS5 协议
|
||||||
|
|
||||||
|
#### SOCKS5 使用示例
|
||||||
|
|
||||||
|
**Python**:
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
proxies = {
|
||||||
|
'http': 'socks5://localhost:7779',
|
||||||
|
'https': 'socks5://localhost:7779'
|
||||||
|
}
|
||||||
|
response = requests.get('https://httpbin.org/ip', proxies=proxies)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Node.js**:
|
||||||
|
```javascript
|
||||||
|
const SocksProxyAgent = require('socks-proxy-agent');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const agent = new SocksProxyAgent('socks5://localhost:7779');
|
||||||
|
fetch('https://httpbin.org/ip', { agent }).then(res => res.json());
|
||||||
|
```
|
||||||
|
|
||||||
|
**浏览器配置**:
|
||||||
|
- 类型:SOCKS5
|
||||||
|
- 地址:`localhost` 或服务器 IP
|
||||||
|
- 端口:`7779`(随机)或 `7780`(稳定)
|
||||||
|
|
||||||
|
**SSH 隧道**:
|
||||||
|
```bash
|
||||||
|
ssh -o ProxyCommand='nc -X 5 -x localhost:7779 %h %p' user@remote-server
|
||||||
|
```
|
||||||
|
|
||||||
#### 自动重试机制说明
|
#### 自动重试机制说明
|
||||||
|
|
||||||
@@ -283,6 +431,8 @@ docker run -d --name proxygo \
|
|||||||
-p 7776:7776 \
|
-p 7776:7776 \
|
||||||
-p 7777:7777 \
|
-p 7777:7777 \
|
||||||
-p 7778:7778 \
|
-p 7778:7778 \
|
||||||
|
-p 7779:7779 \
|
||||||
|
-p 7780:7780 \
|
||||||
-e WEBUI_PASSWORD=your_password \
|
-e WEBUI_PASSWORD=your_password \
|
||||||
-v goproxy-data:/app/data \
|
-v goproxy-data:/app/data \
|
||||||
ghcr.io/isboyjc/goproxy:latest
|
ghcr.io/isboyjc/goproxy:latest
|
||||||
@@ -327,16 +477,18 @@ docker compose up -d
|
|||||||
| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 |
|
| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 |
|
||||||
| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码 |
|
| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码 |
|
||||||
| `WEBUI_PASSWORD` | `goproxy` | WebUI 登录密码 |
|
| `WEBUI_PASSWORD` | `goproxy` | WebUI 登录密码 |
|
||||||
| `STABLE_PORT` | `7776` | 最低延迟代理端口 |
|
| `STABLE_PORT` | `7776` | HTTP 最低延迟代理端口 |
|
||||||
| `RANDOM_PORT` | `7777` | 随机轮换代理端口 |
|
| `RANDOM_PORT` | `7777` | HTTP 随机轮换代理端口 |
|
||||||
| `WEBUI_PORT` | `7778` | WebUI 管理端口 |
|
| `WEBUI_PORT` | `7778` | WebUI 管理端口 |
|
||||||
|
| `SOCKS5_STABLE_PORT` | `7780` | SOCKS5 最低延迟代理端口 |
|
||||||
|
| `SOCKS5_RANDOM_PORT` | `7779` | SOCKS5 随机轮换代理端口 |
|
||||||
|
|
||||||
完整环境变量列表请查看 `.env.example` 文件。
|
完整环境变量列表请查看 `.env.example` 文件。
|
||||||
|
|
||||||
**⚠️ 生产部署注意事项**:
|
**⚠️ 生产部署注意事项**:
|
||||||
- 如使用 Dokploy、Coolify 等平台部署,确保 `docker-compose.yml` 中配置了平台网络(如 `dokploy-network`)
|
- 如使用 Dokploy、Coolify 等平台部署,确保 `docker-compose.yml` 中配置了平台网络(如 `dokploy-network`)
|
||||||
- WebUI 端口可通过平台的域名功能访问,无需手动配置端口绑定
|
- WebUI 端口可通过平台的域名功能访问,无需手动配置端口绑定
|
||||||
- 代理端口(7776/7777)通过 `IP:端口` 直接访问,**强烈建议启用认证**
|
- 代理端口(7776/7777/7779/7780)通过 `IP:端口` 直接访问,**强烈建议启用认证**
|
||||||
|
|
||||||
**常用配置示例**:
|
**常用配置示例**:
|
||||||
|
|
||||||
@@ -371,7 +523,7 @@ docker compose up -d
|
|||||||
|
|
||||||
### 安全部署配置
|
### 安全部署配置
|
||||||
|
|
||||||
**默认配置**:代理服务对外开放(端口 7776、7777),WebUI 对外开放(端口 7778)。
|
**默认配置**:代理服务对外开放(端口 7776、7777、7779、7780),WebUI 对外开放(端口 7778)。
|
||||||
|
|
||||||
**⚠️ 重要提示**:
|
**⚠️ 重要提示**:
|
||||||
|
|
||||||
@@ -393,15 +545,21 @@ WEBUI_PASSWORD=admin_pass
|
|||||||
**客户端使用(带认证)**:
|
**客户端使用(带认证)**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 环境变量方式
|
# HTTP 代理 - 环境变量方式
|
||||||
export http_proxy=http://myuser:secure_pass_123@server-ip:7777
|
export http_proxy=http://myuser:secure_pass_123@server-ip:7777
|
||||||
export https_proxy=http://myuser:secure_pass_123@server-ip:7777
|
export https_proxy=http://myuser:secure_pass_123@server-ip:7777
|
||||||
|
|
||||||
# curl 直接指定
|
# HTTP 代理 - curl 直接指定
|
||||||
curl -x http://myuser:secure_pass_123@server-ip:7777 https://httpbin.org/ip
|
curl -x http://myuser:secure_pass_123@server-ip:7777 https://httpbin.org/ip
|
||||||
|
|
||||||
# Python requests
|
# SOCKS5 代理 - curl 使用
|
||||||
proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': '...'}
|
curl --socks5 myuser:secure_pass_123@server-ip:7779 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# Python - HTTP 代理
|
||||||
|
proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': 'http://myuser:secure_pass_123@server-ip:7777'}
|
||||||
|
|
||||||
|
# Python - SOCKS5 代理
|
||||||
|
proxies = {'http': 'socks5://myuser:secure_pass_123@server-ip:7779', 'https': 'socks5://myuser:secure_pass_123@server-ip:7779'}
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚙️ 配置说明
|
## ⚙️ 配置说明
|
||||||
@@ -433,8 +591,10 @@ proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': '...
|
|||||||
|
|
||||||
| 参数 | 默认值 | 说明 |
|
| 参数 | 默认值 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `proxy_port` | `:7777` | 随机轮换代理端口 |
|
| `proxy_port` | `:7777` | HTTP 随机轮换代理端口 |
|
||||||
| `stable_proxy_port` | `:7776` | 最低延迟代理端口 |
|
| `stable_proxy_port` | `:7776` | HTTP 最低延迟代理端口 |
|
||||||
|
| `socks5_port` | `:7779` | SOCKS5 随机轮换代理端口 |
|
||||||
|
| `stable_socks5_port` | `:7780` | SOCKS5 最低延迟代理端口 |
|
||||||
| `webui_port` | `:7778` | WebUI 端口 |
|
| `webui_port` | `:7778` | WebUI 端口 |
|
||||||
|
|
||||||
**代理认证配置**
|
**代理认证配置**
|
||||||
@@ -443,9 +603,12 @@ proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': '...
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `proxy_auth_enabled` | `false` | 是否启用代理认证(对外开放时建议启用) |
|
| `proxy_auth_enabled` | `false` | 是否启用代理认证(对外开放时建议启用) |
|
||||||
| `proxy_auth_username` | `proxy` | 代理认证用户名 |
|
| `proxy_auth_username` | `proxy` | 代理认证用户名 |
|
||||||
| `proxy_auth_password_hash` | 空 | 代理认证密码 SHA256 哈希(通过环境变量 `PROXY_AUTH_PASSWORD` 设置原始密码) |
|
| `proxy_auth_password_hash` | 空 | 代理认证密码 SHA256 哈希(HTTP Basic Auth) |
|
||||||
|
|
||||||
> 💡 **注意**:代理认证配置通过**环境变量**设置,不在 `config.json` 中。启动时从 `PROXY_AUTH_ENABLED`、`PROXY_AUTH_USERNAME`、`PROXY_AUTH_PASSWORD` 环境变量读取。
|
> 💡 **注意**:
|
||||||
|
> - 代理认证配置通过**环境变量**设置,不在 `config.json` 中
|
||||||
|
> - 启动时从 `PROXY_AUTH_ENABLED`、`PROXY_AUTH_USERNAME`、`PROXY_AUTH_PASSWORD` 环境变量读取
|
||||||
|
> - **HTTP 代理**使用 Basic Auth(密码哈希),**SOCKS5 代理**使用用户名/密码认证(明文传输,建议内网使用)
|
||||||
|
|
||||||
**池子容量配置**
|
**池子容量配置**
|
||||||
|
|
||||||
@@ -916,13 +1079,16 @@ A:
|
|||||||
PROXY_AUTH_PASSWORD=mypass
|
PROXY_AUTH_PASSWORD=mypass
|
||||||
```
|
```
|
||||||
2. 重启服务:`docker compose up -d`
|
2. 重启服务:`docker compose up -d`
|
||||||
3. 客户端使用:`http://myuser:mypass@server-ip:7777`
|
3. 客户端使用:
|
||||||
|
- HTTP:`http://myuser:mypass@server-ip:7777`
|
||||||
|
- SOCKS5:`socks5://myuser:mypass@server-ip:7779`
|
||||||
|
|
||||||
### Q: 代理认证和 WebUI 认证有什么区别?
|
### Q: 代理认证和 WebUI 认证有什么区别?
|
||||||
A:
|
A:
|
||||||
- **代理认证**:保护 7776/7777 代理服务端口,防止代理被滥用
|
- **代理认证**:保护代理服务端口(7776/7777/7779/7780),防止代理被滥用
|
||||||
- **WebUI 认证**:保护 7778 管理后台,区分访客和管理员权限
|
- **WebUI 认证**:保护 7778 管理后台,区分访客和管理员权限
|
||||||
- 两者独立配置,互不影响
|
- 两者独立配置,互不影响
|
||||||
|
- 启用代理认证时,HTTP 和 SOCKS5 代理都需要认证
|
||||||
|
|
||||||
## 🛠️ 开发与调试
|
## 🛠️ 开发与调试
|
||||||
|
|
||||||
@@ -937,6 +1103,7 @@ A:
|
|||||||
- `[health]`:健康检查器
|
- `[health]`:健康检查器
|
||||||
- `[optimize]`:优化器
|
- `[optimize]`:优化器
|
||||||
- `[monitor]`:状态监控器
|
- `[monitor]`:状态监控器
|
||||||
|
- `[socks5]`:SOCKS5 代理服务器(握手、认证、连接建立)
|
||||||
|
|
||||||
### 数据库操作
|
### 数据库操作
|
||||||
|
|
||||||
@@ -956,35 +1123,52 @@ sqlite3 data/proxy.db "DELETE FROM proxies;"
|
|||||||
|
|
||||||
## 🧪 测试代理服务
|
## 🧪 测试代理服务
|
||||||
|
|
||||||
项目提供了三种测试脚本,用于验证代理服务功能和性能(位于 `test/` 目录)。
|
项目提供了多种测试脚本,用于验证 HTTP 和 SOCKS5 代理服务功能和性能(位于 `test/` 目录)。
|
||||||
|
|
||||||
### 快速测试
|
### 快速测试
|
||||||
|
|
||||||
|
**HTTP 代理测试**:
|
||||||
```bash
|
```bash
|
||||||
# 测试随机轮换模式(默认 7777 端口)
|
# 测试 HTTP 随机轮换模式(7777 端口)
|
||||||
./test/test_proxy.sh
|
./test/test_proxy.sh
|
||||||
|
|
||||||
# 测试最低延迟模式(7776 端口)
|
# 测试 HTTP 最低延迟模式(7776 端口)
|
||||||
./test/test_proxy.sh 7776
|
./test/test_proxy.sh 7776
|
||||||
|
|
||||||
# 使用 Go 脚本
|
# 使用 Go/Python 脚本
|
||||||
go run test/test_proxy.go # 默认 7777
|
go run test/test_proxy.go 7777
|
||||||
go run test/test_proxy.go 7776 # 测试 7776
|
python test/test_proxy.py 7776
|
||||||
|
```
|
||||||
|
|
||||||
# 使用 Python 脚本
|
**SOCKS5 代理测试**:
|
||||||
python test/test_proxy.py # 默认 7777
|
```bash
|
||||||
python test/test_proxy.py 7776 # 测试 7776
|
# 测试 SOCKS5 随机轮换模式(7779 端口)
|
||||||
|
./test/test_socks5.sh localhost 7779
|
||||||
|
|
||||||
|
# 测试 SOCKS5 最低延迟模式(7780 端口)
|
||||||
|
./test/test_socks5.sh localhost 7780
|
||||||
|
|
||||||
|
# 持续测试 50 次
|
||||||
|
./test/test_socks5.sh localhost 7779 50
|
||||||
|
|
||||||
# 按 Ctrl+C 停止测试并查看统计
|
# 按 Ctrl+C 停止测试并查看统计
|
||||||
```
|
```
|
||||||
|
|
||||||
测试脚本特点:
|
**HTTP 测试脚本**(`test_proxy.sh`)特点:
|
||||||
- **持续运行模式**:类似 `ping` 命令,持续发送请求
|
- **持续运行模式**:类似 `ping` 命令,持续发送请求
|
||||||
- 实时显示每次请求的出口 IP 和延迟
|
- 实时显示每次请求的出口 IP、国家和延迟
|
||||||
- 动态更新成功率统计
|
- 动态更新成功率统计
|
||||||
- 验证代理轮换机制
|
- 验证 HTTP 代理轮换机制
|
||||||
- 按 `Ctrl+C` 停止并显示完整统计报告
|
- 按 `Ctrl+C` 停止并显示完整统计报告
|
||||||
|
|
||||||
|
**SOCKS5 测试脚本**(`test_socks5.sh`)特点:
|
||||||
|
- 使用 `curl --socks5-hostname -k` 测试 SOCKS5 协议
|
||||||
|
- 显示出口 IP、国家、延迟和时间戳
|
||||||
|
- 验证 SOCKS5 代理轮换和稳定性
|
||||||
|
- 支持指定测试次数或持续测试
|
||||||
|
- 实时统计成功率和平均延迟
|
||||||
|
- 自动跳过 SSL 证书验证(免费代理常有证书问题)
|
||||||
|
|
||||||
### 测试输出示例
|
### 测试输出示例
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1004,15 +1188,153 @@ proxy from 🇯🇵 198.51.100.12: seq=5 time=890ms
|
|||||||
|
|
||||||
**测试认证功能**:
|
**测试认证功能**:
|
||||||
```bash
|
```bash
|
||||||
# 启用认证后测试
|
# HTTP 代理 - 带认证
|
||||||
curl -x http://myuser:mypass@localhost:7777 https://httpbin.org/ip
|
curl -x http://myuser:mypass@localhost:7777 https://httpbin.org/ip
|
||||||
|
|
||||||
# 无认证请求(应该返回 407 错误)
|
# HTTP 代理 - 无认证(应该返回 407 错误)
|
||||||
curl -x http://localhost:7777 https://httpbin.org/ip
|
curl -x http://localhost:7777 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# SOCKS5 代理 - 带认证
|
||||||
|
curl --socks5 myuser:mypass@localhost:7779 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# SOCKS5 代理 - 无认证(应该连接失败)
|
||||||
|
curl --socks5 localhost:7779 https://httpbin.org/ip
|
||||||
```
|
```
|
||||||
|
|
||||||
**测试脚本使用**:[`test/README.md`](./test/README.md)
|
**测试脚本使用**:[`test/README.md`](./test/README.md)
|
||||||
|
|
||||||
|
## ❓ 常见问题
|
||||||
|
|
||||||
|
### Q1: HTTP 和 SOCKS5 代理有什么区别?
|
||||||
|
|
||||||
|
**简单理解**:
|
||||||
|
- **HTTP 代理**:专门为 HTTP/HTTPS 设计,简单易用,命令行工具支持好
|
||||||
|
- **SOCKS5 代理**:通用代理协议,支持所有 TCP/UDP 协议,功能更强大
|
||||||
|
|
||||||
|
**详细对比**见上方 [HTTP vs SOCKS5 协议对比](#-http-vs-socks5-协议对比) 章节。
|
||||||
|
|
||||||
|
### Q2: 我应该使用哪个端口?
|
||||||
|
|
||||||
|
根据你的需求:
|
||||||
|
|
||||||
|
| 需求 | 推荐端口 | 理由 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 🕷️ **爬虫/数据采集** | 7777(HTTP 随机) | IP 高度分散,降低封禁风险 |
|
||||||
|
| 🎥 **流媒体/长连接** | 7776(HTTP 稳定) | 延迟最低,连接稳定 |
|
||||||
|
| 🌐 **浏览器代理** | 7779(SOCKS5 随机) | 协议支持好,IP 多样性高 |
|
||||||
|
| 🎮 **游戏/SSH/聊天** | 7780(SOCKS5 稳定) | 非 HTTP 协议,稳定优先 |
|
||||||
|
|
||||||
|
### Q3: SOCKS5 代理支持认证吗?
|
||||||
|
|
||||||
|
支持。当 `PROXY_AUTH_ENABLED=true` 时,所有代理端口(包括 SOCKS5)都需要认证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SOCKS5 带认证
|
||||||
|
curl --socks5 username:password@server-ip:7779 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# 浏览器配置
|
||||||
|
# SOCKS5 Host: server-ip
|
||||||
|
# SOCKS5 Port: 7779
|
||||||
|
# Username: username
|
||||||
|
# Password: password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 为什么推荐"域名:端口"访问而非域名直接配置代理?
|
||||||
|
|
||||||
|
**技术限制**:
|
||||||
|
- Cloudflare 等 DNS 托管服务的 HTTP 代理(橙云)**不支持非标准端口**(80/443 以外)
|
||||||
|
- 即使关闭橙云,DNS 记录也无法直接指向容器内部端口
|
||||||
|
|
||||||
|
**正确做法**:
|
||||||
|
```bash
|
||||||
|
# ✅ 推荐方式:IP:端口 或 域名:端口
|
||||||
|
curl -x http://proxy.example.com:7777 https://httpbin.org/ip
|
||||||
|
curl -x http://123.45.67.89:7777 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# ❌ 不支持:直接域名(除非反向代理到 80/443)
|
||||||
|
curl -x http://proxy.example.com https://httpbin.org/ip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q5: 如何测试 SOCKS5 代理是否正常工作?
|
||||||
|
|
||||||
|
使用提供的测试脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试 SOCKS5 随机端口(7779)
|
||||||
|
./test/test_socks5.sh localhost 7779
|
||||||
|
|
||||||
|
# 持续测试 50 次
|
||||||
|
./test/test_socks5.sh localhost 7779 50
|
||||||
|
```
|
||||||
|
|
||||||
|
或手动测试:
|
||||||
|
```bash
|
||||||
|
# curl 测试(-k 跳过 SSL 证书验证)
|
||||||
|
curl -k --socks5-hostname localhost:7779 https://httpbin.org/ip
|
||||||
|
|
||||||
|
# 或测试 HTTP(不需要 SSL)
|
||||||
|
curl --socks5-hostname localhost:7779 http://httpbin.org/ip
|
||||||
|
|
||||||
|
# Python 测试(需要安装 requests[socks])
|
||||||
|
python3 -c "import requests; print(requests.get('https://httpbin.org/ip', proxies={'http': 'socks5://localhost:7779', 'https': 'socks5://localhost:7779'}).json())"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q6: SOCKS5 代理失败率是否比 HTTP 高?
|
||||||
|
|
||||||
|
不会。两种服务质量和成功率相近,但**使用不同的上游代理**:
|
||||||
|
- **HTTP 代理服务**(7776/7777):可使用池中的 HTTP 或 SOCKS5 上游代理
|
||||||
|
- **SOCKS5 代理服务**(7779/7780):仅使用 SOCKS5 上游代理(因为许多免费 HTTP 代理不支持 HTTPS CONNECT)
|
||||||
|
- 两种服务都支持自动重试和故障切换
|
||||||
|
|
||||||
|
### Q7: 如何在浏览器中配置 SOCKS5 代理?
|
||||||
|
|
||||||
|
**Chrome/Edge**(使用插件如 SwitchyOmega):
|
||||||
|
1. 安装 Proxy SwitchyOmega 插件
|
||||||
|
2. 新建情景模式 → SOCKS5
|
||||||
|
3. 代理服务器:`server-ip` 或 `localhost`
|
||||||
|
4. 代理端口:`7779`(随机)或 `7780`(稳定)
|
||||||
|
5. 如启用认证,填写用户名和密码
|
||||||
|
|
||||||
|
**Firefox**:
|
||||||
|
1. 设置 → 网络设置 → 手动代理配置
|
||||||
|
2. SOCKS Host:`server-ip` 或 `localhost`
|
||||||
|
3. Port:`7779` 或 `7780`
|
||||||
|
4. 选择:SOCKS v5
|
||||||
|
5. 勾选"通过 SOCKS 代理 DNS 查询"(可选)
|
||||||
|
|
||||||
|
### Q8: 如何查看 SOCKS5 服务的运行日志?
|
||||||
|
|
||||||
|
**本地运行**:
|
||||||
|
```bash
|
||||||
|
# 直接查看终端输出,SOCKS5 日志前缀为 [socks5]
|
||||||
|
# 启动日志示例:
|
||||||
|
# socks5 server listening on :7779 [随机轮换] [需认证 (用户: proxy)]
|
||||||
|
# socks5 server listening on :7780 [最低延迟] [无认证]
|
||||||
|
|
||||||
|
# 连接日志示例:
|
||||||
|
# [socks5] google.com:443 via 203.0.113.45:1080 established
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker 部署**:
|
||||||
|
```bash
|
||||||
|
# 查看最近 50 行日志
|
||||||
|
docker logs proxygo --tail 50
|
||||||
|
|
||||||
|
# 实时跟踪日志(含 SOCKS5)
|
||||||
|
docker logs proxygo -f
|
||||||
|
|
||||||
|
# 过滤 SOCKS5 相关日志
|
||||||
|
docker logs proxygo -f | grep socks5
|
||||||
|
|
||||||
|
# 查看 SOCKS5 错误日志
|
||||||
|
docker logs proxygo --tail 200 | grep -i "socks5.*failed"
|
||||||
|
```
|
||||||
|
|
||||||
|
**WebUI 查看**:
|
||||||
|
- 访问 WebUI(端口 7778)→ 点击右上角 **Logs** 按钮
|
||||||
|
- 实时日志面板会显示所有协议的连接和错误信息
|
||||||
|
- 包括 SOCKS5 握手、认证、连接建立等详细日志
|
||||||
|
|
||||||
## 🙏 致谢与声明
|
## 🙏 致谢与声明
|
||||||
|
|
||||||
本项目基于 [jonasen1988/proxygo](https://github.com/jonasen1988/proxygo) 进行魔改和增强。
|
本项目基于 [jonasen1988/proxygo](https://github.com/jonasen1988/proxygo) 进行魔改和增强。
|
||||||
@@ -1029,12 +1351,13 @@ curl -x http://localhost:7777 https://httpbin.org/ip
|
|||||||
- 🆕 **按需抓取策略**:源分组、断路器保护、Emergency/Refill/Optimize 多模式
|
- 🆕 **按需抓取策略**:源分组、断路器保护、Emergency/Refill/Optimize 多模式
|
||||||
- 🆕 **分层健康管理**:批次检查、智能跳过 S 级、定时优化轮换
|
- 🆕 **分层健康管理**:批次检查、智能跳过 S 级、定时优化轮换
|
||||||
- 🆕 **智能重试机制**:自动故障切换、失败即删除、防重复尝试
|
- 🆕 **智能重试机制**:自动故障切换、失败即删除、防重复尝试
|
||||||
- 🆕 **双端口服务**:7777 随机轮换(IP 多样性)+ 7776 最低延迟(稳定连接)
|
- 🆕 **双协议支持**:HTTP + SOCKS5 双协议,4 个服务端口(随机/稳定各 2 个)
|
||||||
- 🆕 **代理认证保护**:可选 Basic Auth 认证,对外开放时保护代理服务不被滥用
|
- 🆕 **双模式策略**:每种协议都支持随机轮换(IP 多样性)和最低延迟(稳定连接)
|
||||||
|
- 🆕 **代理认证保护**:HTTP Basic Auth + SOCKS5 用户名/密码认证,对外开放时保护服务
|
||||||
- 🆕 **黑客风格 WebUI**:Matrix 美学、实时仪表盘、完整配置界面、中英文切换
|
- 🆕 **黑客风格 WebUI**:Matrix 美学、实时仪表盘、完整配置界面、中英文切换
|
||||||
- 🆕 **双角色权限**:访客模式(只读)+ 管理员模式(完全控制),可安全公网开放
|
- 🆕 **双角色权限**:访客模式(只读)+ 管理员模式(完全控制),可安全公网开放
|
||||||
- 🆕 **扩展存储层**:质量等级、使用统计、源状态管理
|
- 🆕 **扩展存储层**:质量等级、使用统计、源状态管理
|
||||||
- 🆕 **测试套件**:Bash/Go/Python 三种测试脚本,持续运行模式,显示国旗 emoji
|
- 🆕 **测试套件**:HTTP + SOCKS5 测试脚本,持续运行模式,显示国旗 emoji
|
||||||
- 🆕 **CI/CD 自动化**:GitHub Actions 自动构建多架构镜像(amd64/arm64),双仓库发布
|
- 🆕 **CI/CD 自动化**:GitHub Actions 自动构建多架构镜像(amd64/arm64),双仓库发布
|
||||||
- 🆕 **环境变量配置**:docker-compose + .env 文件,灵活配置各种部署场景
|
- 🆕 **环境变量配置**:docker-compose + .env 文件,灵活配置各种部署场景
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,17 @@ type Config struct {
|
|||||||
// 稳定代理端口(最低延迟模式)
|
// 稳定代理端口(最低延迟模式)
|
||||||
StableProxyPort string
|
StableProxyPort string
|
||||||
|
|
||||||
|
// SOCKS5 服务端口(随机轮换模式)
|
||||||
|
SOCKS5Port string
|
||||||
|
|
||||||
|
// 稳定 SOCKS5 端口(最低延迟模式)
|
||||||
|
StableSOCKS5Port string
|
||||||
|
|
||||||
// 代理服务认证配置
|
// 代理服务认证配置
|
||||||
ProxyAuthEnabled bool // 是否启用代理认证(默认 false)
|
ProxyAuthEnabled bool // 是否启用代理认证(默认 false)
|
||||||
ProxyAuthUsername string // 代理认证用户名(默认 "proxy")
|
ProxyAuthUsername string // 代理认证用户名(默认 "proxy")
|
||||||
ProxyAuthPasswordHash string // 代理认证密码 SHA256 哈希
|
ProxyAuthPassword string // 代理认证密码明文(用于 SOCKS5)
|
||||||
|
ProxyAuthPasswordHash string // 代理认证密码 SHA256 哈希(用于 HTTP)
|
||||||
|
|
||||||
// 地理过滤配置
|
// 地理过滤配置
|
||||||
BlockedCountries []string // 屏蔽的国家代码列表(如 ["CN", "RU"],默认 ["CN"])
|
BlockedCountries []string // 屏蔽的国家代码列表(如 ["CN", "RU"],默认 ["CN"])
|
||||||
@@ -139,11 +146,14 @@ func DefaultConfig() *Config {
|
|||||||
WebUIPasswordHash: passwordHash(password),
|
WebUIPasswordHash: passwordHash(password),
|
||||||
ProxyPort: ":7777",
|
ProxyPort: ":7777",
|
||||||
StableProxyPort: ":7776",
|
StableProxyPort: ":7776",
|
||||||
|
SOCKS5Port: ":7779",
|
||||||
|
StableSOCKS5Port: ":7780",
|
||||||
DBPath: dataDir() + "proxy.db",
|
DBPath: dataDir() + "proxy.db",
|
||||||
|
|
||||||
// 代理认证配置
|
// 代理认证配置
|
||||||
ProxyAuthEnabled: proxyAuthEnabled,
|
ProxyAuthEnabled: proxyAuthEnabled,
|
||||||
ProxyAuthUsername: proxyAuthUsername,
|
ProxyAuthUsername: proxyAuthUsername,
|
||||||
|
ProxyAuthPassword: proxyAuthPassword,
|
||||||
ProxyAuthPasswordHash: proxyAuthHash,
|
ProxyAuthPasswordHash: proxyAuthHash,
|
||||||
|
|
||||||
// 地理过滤配置
|
// 地理过滤配置
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ services:
|
|||||||
container_name: ${CONTAINER_NAME:-goproxy}
|
container_name: ${CONTAINER_NAME:-goproxy}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "${STABLE_PORT:-7776}:7776" # 稳定代理(最低延迟)
|
- "${STABLE_PORT:-7776}:7776" # HTTP 最低延迟
|
||||||
- "${RANDOM_PORT:-7777}:7777" # 随机轮换
|
- "${RANDOM_PORT:-7777}:7777" # HTTP 随机轮换
|
||||||
- "${WEBUI_PORT:-7778}:7778" # WebUI(有登录认证)
|
- "${WEBUI_PORT:-7778}:7778" # WebUI(有登录认证)
|
||||||
|
- "${SOCKS5_RANDOM_PORT:-7779}:7779" # SOCKS5 随机轮换
|
||||||
|
- "${SOCKS5_STABLE_PORT:-7780}:7780" # SOCKS5 最低延迟
|
||||||
volumes:
|
volumes:
|
||||||
- goproxy-data:/app/data
|
- goproxy-data:/app/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -71,13 +71,13 @@ func (f *Fetcher) FetchSmart(mode string, preferredProtocol string) ([]storage.P
|
|||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case "emergency":
|
case "emergency":
|
||||||
// 紧急模式:使用所有可用源
|
// 紧急模式:忽略断路器,强制使用所有源(包括被禁用的)
|
||||||
sources = f.filterAvailableSources(allSources, preferredProtocol)
|
sources = f.filterAvailableSources(allSources, preferredProtocol, true)
|
||||||
log.Printf("[fetch] 🚨 紧急模式: 使用 %d 个源", len(sources))
|
log.Printf("[fetch] 🚨 紧急模式: 使用 %d 个源(忽略断路器)", len(sources))
|
||||||
|
|
||||||
case "refill":
|
case "refill":
|
||||||
// 补充模式:使用快更新源
|
// 补充模式:使用快更新源
|
||||||
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol)
|
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol, false)
|
||||||
log.Printf("[fetch] 🔄 补充模式: 使用 %d 个快更新源", len(sources))
|
log.Printf("[fetch] 🔄 补充模式: 使用 %d 个快更新源", len(sources))
|
||||||
|
|
||||||
case "optimize":
|
case "optimize":
|
||||||
@@ -86,7 +86,7 @@ func (f *Fetcher) FetchSmart(mode string, preferredProtocol string) ([]storage.P
|
|||||||
log.Printf("[fetch] ⚡ 优化模式: 使用 %d 个源", len(sources))
|
log.Printf("[fetch] ⚡ 优化模式: 使用 %d 个源", len(sources))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol)
|
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sources) == 0 {
|
if len(sources) == 0 {
|
||||||
@@ -97,11 +97,12 @@ func (f *Fetcher) FetchSmart(mode string, preferredProtocol string) ([]storage.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filterAvailableSources 过滤可用的源(通过断路器)
|
// filterAvailableSources 过滤可用的源(通过断路器)
|
||||||
func (f *Fetcher) filterAvailableSources(sources []Source, preferredProtocol string) []Source {
|
// ignoreCircuitBreaker: 是否忽略断路器(Emergency 模式下使用)
|
||||||
|
func (f *Fetcher) filterAvailableSources(sources []Source, preferredProtocol string, ignoreCircuitBreaker bool) []Source {
|
||||||
var available []Source
|
var available []Source
|
||||||
for _, src := range sources {
|
for _, src := range sources {
|
||||||
// 检查断路器
|
// 检查断路器(紧急模式下忽略)
|
||||||
if f.sourceManager != nil && !f.sourceManager.CanUseSource(src.URL) {
|
if !ignoreCircuitBreaker && f.sourceManager != nil && !f.sourceManager.CanUseSource(src.URL) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 如果指定了协议偏好,优先该协议的源
|
// 如果指定了协议偏好,优先该协议的源
|
||||||
@@ -115,7 +116,7 @@ func (f *Fetcher) filterAvailableSources(sources []Source, preferredProtocol str
|
|||||||
|
|
||||||
// selectRandomSources 随机选择N个源
|
// selectRandomSources 随机选择N个源
|
||||||
func (f *Fetcher) selectRandomSources(sources []Source, count int, preferredProtocol string) []Source {
|
func (f *Fetcher) selectRandomSources(sources []Source, count int, preferredProtocol string) []Source {
|
||||||
available := f.filterAvailableSources(sources, preferredProtocol)
|
available := f.filterAvailableSources(sources, preferredProtocol, false)
|
||||||
if len(available) <= count {
|
if len(available) <= count {
|
||||||
return available
|
return available
|
||||||
}
|
}
|
||||||
|
|||||||
28
main.go
28
main.go
@@ -70,9 +70,13 @@ func main() {
|
|||||||
totalDeleted += int(deleted)
|
totalDeleted += int(deleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建两个代理服务器:随机轮换 + 最低延迟
|
// 创建 HTTP 代理服务器:随机轮换 + 最低延迟
|
||||||
randomServer := proxy.New(store, cfg, "random", cfg.ProxyPort)
|
randomServer := proxy.New(store, cfg, "random", cfg.ProxyPort)
|
||||||
stableServer := proxy.New(store, cfg, "lowest-latency", cfg.StableProxyPort)
|
stableServer := proxy.New(store, cfg, "lowest-latency", cfg.StableProxyPort)
|
||||||
|
|
||||||
|
// 创建 SOCKS5 代理服务器:随机轮换 + 最低延迟
|
||||||
|
socks5RandomServer := proxy.NewSOCKS5(store, cfg, "random", cfg.SOCKS5Port)
|
||||||
|
socks5StableServer := proxy.NewSOCKS5(store, cfg, "lowest-latency", cfg.StableSOCKS5Port)
|
||||||
|
|
||||||
// 配置变更通知 channel
|
// 配置变更通知 channel
|
||||||
configChanged := make(chan struct{}, 1)
|
configChanged := make(chan struct{}, 1)
|
||||||
@@ -105,16 +109,30 @@ func main() {
|
|||||||
// 监听配置变更
|
// 监听配置变更
|
||||||
go watchConfigChanges(configChanged, poolMgr)
|
go watchConfigChanges(configChanged, poolMgr)
|
||||||
|
|
||||||
// 启动稳定代理服务(最低延迟模式)
|
// 启动 HTTP 稳定代理服务(最低延迟模式)
|
||||||
go func() {
|
go func() {
|
||||||
if err := stableServer.Start(); err != nil {
|
if err := stableServer.Start(); err != nil {
|
||||||
log.Fatalf("stable proxy server: %v", err)
|
log.Fatalf("stable http proxy server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 启动随机代理服务(阻塞)
|
// 启动 SOCKS5 稳定代理服务(最低延迟模式)
|
||||||
|
go func() {
|
||||||
|
if err := socks5StableServer.Start(); err != nil {
|
||||||
|
log.Fatalf("stable socks5 proxy server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 启动 SOCKS5 随机代理服务
|
||||||
|
go func() {
|
||||||
|
if err := socks5RandomServer.Start(); err != nil {
|
||||||
|
log.Fatalf("random socks5 proxy server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 启动 HTTP 随机代理服务(阻塞)
|
||||||
if err := randomServer.Start(); err != nil {
|
if err := randomServer.Start(); err != nil {
|
||||||
log.Fatalf("random proxy server: %v", err)
|
log.Fatalf("random http proxy server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
440
proxy/socks5_server.go
Normal file
440
proxy/socks5_server.go
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"goproxy/config"
|
||||||
|
"goproxy/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SOCKS5Server SOCKS5 协议服务器
|
||||||
|
type SOCKS5Server struct {
|
||||||
|
storage *storage.Storage
|
||||||
|
cfg *config.Config
|
||||||
|
mode string // "random" 或 "lowest-latency"
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSOCKS5 创建 SOCKS5 服务器
|
||||||
|
func NewSOCKS5(s *storage.Storage, cfg *config.Config, mode string, port string) *SOCKS5Server {
|
||||||
|
return &SOCKS5Server{
|
||||||
|
storage: s,
|
||||||
|
cfg: cfg,
|
||||||
|
mode: mode,
|
||||||
|
port: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动 SOCKS5 服务器
|
||||||
|
func (s *SOCKS5Server) Start() error {
|
||||||
|
modeDesc := "随机轮换"
|
||||||
|
if s.mode == "lowest-latency" {
|
||||||
|
modeDesc = "最低延迟"
|
||||||
|
}
|
||||||
|
authStatus := "无认证"
|
||||||
|
if s.cfg.ProxyAuthEnabled {
|
||||||
|
authStatus = fmt.Sprintf("需认证 (用户: %s)", s.cfg.ProxyAuthUsername)
|
||||||
|
}
|
||||||
|
log.Printf("socks5 server listening on %s [%s] [%s]", s.port, modeDesc, authStatus)
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", s.port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go s.handleConnection(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleConnection 处理 SOCKS5 连接
|
||||||
|
func (s *SOCKS5Server) handleConnection(clientConn net.Conn) {
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
// SOCKS5 握手
|
||||||
|
if err := s.socks5Handshake(clientConn); err != nil {
|
||||||
|
log.Printf("[socks5] handshake failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取请求
|
||||||
|
target, err := s.readSOCKS5Request(clientConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[socks5] read request failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带重试的连接上游代理
|
||||||
|
// 重试机制:只使用 SOCKS5 协议的上游代理(天然支持 HTTPS)
|
||||||
|
tried := []string{}
|
||||||
|
maxRetries := s.cfg.MaxRetry + 2 // 增加重试次数以应对质量差的代理
|
||||||
|
|
||||||
|
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||||
|
var p *storage.Proxy
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// SOCKS5 服务只使用 SOCKS5 上游代理
|
||||||
|
if s.mode == "lowest-latency" {
|
||||||
|
p, err = s.storage.GetLowestLatencyByProtocolExclude("socks5", tried)
|
||||||
|
} else {
|
||||||
|
p, err = s.storage.GetRandomByProtocolExclude("socks5", tried)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[socks5] no available socks5 upstream proxy: %v", err)
|
||||||
|
s.sendSOCKS5Reply(clientConn, 0x01) // General failure
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tried = append(tried, p.Address)
|
||||||
|
|
||||||
|
// 连接上游代理
|
||||||
|
upstreamConn, err := s.dialViaProxy(p, target)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[socks5] dial %s via %s (%s) failed: %v, removing", target, p.Address, p.Protocol, err)
|
||||||
|
s.storage.Delete(p.Address)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送成功响应
|
||||||
|
if err := s.sendSOCKS5Reply(clientConn, 0x00); err != nil {
|
||||||
|
upstreamConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[socks5] %s via %s established", target, p.Address)
|
||||||
|
|
||||||
|
// 双向转发数据
|
||||||
|
go io.Copy(upstreamConn, clientConn)
|
||||||
|
io.Copy(clientConn, upstreamConn)
|
||||||
|
|
||||||
|
// 转发完成,关闭连接
|
||||||
|
upstreamConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有重试都失败
|
||||||
|
s.sendSOCKS5Reply(clientConn, 0x01) // General failure
|
||||||
|
log.Printf("[socks5] all proxies failed for %s", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// socks5Handshake 处理 SOCKS5 握手
|
||||||
|
func (s *SOCKS5Server) socks5Handshake(conn net.Conn) error {
|
||||||
|
buf := make([]byte, 257)
|
||||||
|
|
||||||
|
// 读取客户端问候: [VER(1), NMETHODS(1), METHODS(1-255)]
|
||||||
|
n, err := io.ReadAtLeast(conn, buf, 2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
version := buf[0]
|
||||||
|
if version != 0x05 {
|
||||||
|
return fmt.Errorf("unsupported SOCKS version: %d", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
nmethods := int(buf[1])
|
||||||
|
if n < 2+nmethods {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:2+nmethods]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要认证
|
||||||
|
needAuth := s.cfg.ProxyAuthEnabled
|
||||||
|
methods := buf[2 : 2+nmethods]
|
||||||
|
|
||||||
|
// 选择认证方式
|
||||||
|
var selectedMethod byte = 0xFF // No acceptable methods
|
||||||
|
if needAuth {
|
||||||
|
// 需要用户名/密码认证 (0x02)
|
||||||
|
for _, method := range methods {
|
||||||
|
if method == 0x02 {
|
||||||
|
selectedMethod = 0x02
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 无需认证 (0x00)
|
||||||
|
for _, method := range methods {
|
||||||
|
if method == 0x00 {
|
||||||
|
selectedMethod = 0x00
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送方法选择: [VER(1), METHOD(1)]
|
||||||
|
if _, err := conn.Write([]byte{0x05, selectedMethod}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedMethod == 0xFF {
|
||||||
|
return fmt.Errorf("no acceptable authentication method")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要认证,进行用户名/密码认证
|
||||||
|
if selectedMethod == 0x02 {
|
||||||
|
if err := s.socks5Auth(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// socks5Auth 处理 SOCKS5 用户名/密码认证
|
||||||
|
func (s *SOCKS5Server) socks5Auth(conn net.Conn) error {
|
||||||
|
buf := make([]byte, 513)
|
||||||
|
|
||||||
|
// 读取认证请求: [VER(1), ULEN(1), UNAME(1-255), PLEN(1), PASSWD(1-255)]
|
||||||
|
n, err := io.ReadAtLeast(conn, buf, 2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != 0x01 {
|
||||||
|
return fmt.Errorf("unsupported auth version: %d", buf[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
ulen := int(buf[1])
|
||||||
|
if n < 2+ulen {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:2+ulen]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n = 2 + ulen
|
||||||
|
}
|
||||||
|
|
||||||
|
username := string(buf[2 : 2+ulen])
|
||||||
|
|
||||||
|
// 读取密码长度和密码
|
||||||
|
if n < 2+ulen+1 {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:2+ulen+1]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n = 2 + ulen + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
plen := int(buf[2+ulen])
|
||||||
|
if n < 2+ulen+1+plen {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:2+ulen+1+plen]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
password := string(buf[2+ulen+1 : 2+ulen+1+plen])
|
||||||
|
|
||||||
|
// 验证用户名和密码
|
||||||
|
if username != s.cfg.ProxyAuthUsername || password != s.cfg.ProxyAuthPassword {
|
||||||
|
// 认证失败: [VER(1), STATUS(1)]
|
||||||
|
conn.Write([]byte{0x01, 0x01})
|
||||||
|
return fmt.Errorf("authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证成功: [VER(1), STATUS(1)]
|
||||||
|
if _, err := conn.Write([]byte{0x01, 0x00}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readSOCKS5Request 读取 SOCKS5 请求
|
||||||
|
func (s *SOCKS5Server) readSOCKS5Request(conn net.Conn) (string, error) {
|
||||||
|
buf := make([]byte, 262)
|
||||||
|
|
||||||
|
// 读取请求: [VER(1), CMD(1), RSV(1), ATYP(1), DST.ADDR(variable), DST.PORT(2)]
|
||||||
|
n, err := io.ReadAtLeast(conn, buf, 4)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != 0x05 {
|
||||||
|
return "", fmt.Errorf("invalid version: %d", buf[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := buf[1]
|
||||||
|
if cmd != 0x01 { // 只支持 CONNECT
|
||||||
|
s.sendSOCKS5Reply(conn, 0x07) // Command not supported
|
||||||
|
return "", fmt.Errorf("unsupported command: %d", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
atyp := buf[3]
|
||||||
|
var host string
|
||||||
|
var addrLen int
|
||||||
|
|
||||||
|
switch atyp {
|
||||||
|
case 0x01: // IPv4
|
||||||
|
addrLen = 4
|
||||||
|
if n < 4+addrLen+2 {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:4+addrLen+2]); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
host = fmt.Sprintf("%d.%d.%d.%d", buf[4], buf[5], buf[6], buf[7])
|
||||||
|
case 0x03: // Domain name
|
||||||
|
addrLen = int(buf[4])
|
||||||
|
if n < 4+1+addrLen+2 {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:4+1+addrLen+2]); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
host = string(buf[5 : 5+addrLen])
|
||||||
|
case 0x04: // IPv6
|
||||||
|
addrLen = 16
|
||||||
|
if n < 4+addrLen+2 {
|
||||||
|
if _, err := io.ReadFull(conn, buf[n:4+addrLen+2]); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 简化处理,直接转换
|
||||||
|
host = net.IP(buf[4 : 4+addrLen]).String()
|
||||||
|
default:
|
||||||
|
s.sendSOCKS5Reply(conn, 0x08) // Address type not supported
|
||||||
|
return "", fmt.Errorf("unsupported address type: %d", atyp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取端口
|
||||||
|
portOffset := 4
|
||||||
|
if atyp == 0x03 {
|
||||||
|
portOffset = 5 + addrLen
|
||||||
|
} else {
|
||||||
|
portOffset = 4 + addrLen
|
||||||
|
}
|
||||||
|
port := binary.BigEndian.Uint16(buf[portOffset : portOffset+2])
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s:%d", host, port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendSOCKS5Reply 发送 SOCKS5 响应
|
||||||
|
func (s *SOCKS5Server) sendSOCKS5Reply(conn net.Conn, rep byte) error {
|
||||||
|
// [VER(1), REP(1), RSV(1), ATYP(1), BND.ADDR(variable), BND.PORT(2)]
|
||||||
|
// 简化:使用 0.0.0.0:0
|
||||||
|
reply := []byte{
|
||||||
|
0x05, // VER
|
||||||
|
rep, // REP: 0x00=成功, 0x01=一般失败, 0x07=命令不支持, 0x08=地址类型不支持
|
||||||
|
0x00, // RSV
|
||||||
|
0x01, // ATYP: IPv4
|
||||||
|
0, 0, 0, 0, // BND.ADDR: 0.0.0.0
|
||||||
|
0, 0, // BND.PORT: 0
|
||||||
|
}
|
||||||
|
_, err := conn.Write(reply)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialViaProxy 通过上游代理连接目标
|
||||||
|
func (s *SOCKS5Server) dialViaProxy(p *storage.Proxy, target string) (net.Conn, error) {
|
||||||
|
timeout := time.Duration(s.cfg.ValidateTimeout) * time.Second
|
||||||
|
|
||||||
|
switch p.Protocol {
|
||||||
|
case "http":
|
||||||
|
// 连接到 HTTP 代理
|
||||||
|
conn, err := net.DialTimeout("tcp", p.Address, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 发送 CONNECT 请求
|
||||||
|
fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", target, target)
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 检查 HTTP 响应
|
||||||
|
if n < 12 || string(buf[:12]) != "HTTP/1.1 200" {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("upstream proxy connect failed")
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
|
||||||
|
case "socks5":
|
||||||
|
// 使用 SOCKS5 代理
|
||||||
|
dialer := &net.Dialer{Timeout: timeout}
|
||||||
|
proxyConn, err := dialer.Dial("tcp", p.Address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKS5 握手(无认证)
|
||||||
|
if _, err := proxyConn.Write([]byte{0x05, 0x01, 0x00}); err != nil {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handshake := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(proxyConn, handshake); err != nil {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if handshake[0] != 0x05 || handshake[1] != 0x00 {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, fmt.Errorf("socks5 handshake failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 CONNECT 请求
|
||||||
|
host, port, err := net.SplitHostPort(target)
|
||||||
|
if err != nil {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建请求
|
||||||
|
req := []byte{0x05, 0x01, 0x00} // VER, CMD=CONNECT, RSV
|
||||||
|
|
||||||
|
// 判断是 IP 还是域名
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
req = append(req, 0x01) // IPv4
|
||||||
|
req = append(req, ip4...)
|
||||||
|
} else {
|
||||||
|
req = append(req, 0x04) // IPv6
|
||||||
|
req = append(req, ip...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
req = append(req, 0x03) // Domain
|
||||||
|
req = append(req, byte(len(host)))
|
||||||
|
req = append(req, []byte(host)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加端口
|
||||||
|
portNum := uint16(0)
|
||||||
|
fmt.Sscanf(port, "%d", &portNum)
|
||||||
|
portBytes := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(portBytes, portNum)
|
||||||
|
req = append(req, portBytes...)
|
||||||
|
|
||||||
|
if _, err := proxyConn.Write(req); err != nil {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
reply := make([]byte, 10)
|
||||||
|
if _, err := io.ReadAtLeast(proxyConn, reply, 10); err != nil {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply[1] != 0x00 {
|
||||||
|
proxyConn.Close()
|
||||||
|
return nil, fmt.Errorf("socks5 connect failed, code: %d", reply[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyConn, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported protocol: %s", p.Protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -177,11 +177,21 @@ func (s *Storage) initSchema() error {
|
|||||||
|
|
||||||
// AddProxy 新增代理,已存在则忽略
|
// AddProxy 新增代理,已存在则忽略
|
||||||
func (s *Storage) AddProxy(address, protocol string) error {
|
func (s *Storage) AddProxy(address, protocol string) error {
|
||||||
_, err := s.db.Exec(
|
result, err := s.db.Exec(
|
||||||
`INSERT OR IGNORE INTO proxies (address, protocol) VALUES (?, ?)`,
|
`INSERT OR IGNORE INTO proxies (address, protocol) VALUES (?, ?)`,
|
||||||
address, protocol,
|
address, protocol,
|
||||||
)
|
)
|
||||||
return err
|
if err != nil {
|
||||||
|
log.Printf("[storage] AddProxy %s error: %v", address, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否真的插入了
|
||||||
|
affected, _ := result.RowsAffected()
|
||||||
|
if affected == 0 {
|
||||||
|
log.Printf("[storage] AddProxy %s ignored (already exists or constraint)", address)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddProxies 批量新增
|
// AddProxies 批量新增
|
||||||
@@ -328,6 +338,56 @@ func (s *Storage) GetLowestLatencyExclude(excludes []string) (*Proxy, error) {
|
|||||||
return nil, fmt.Errorf("no available proxy")
|
return nil, fmt.Errorf("no available proxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRandomByProtocolExclude 按协议获取随机代理(排除已尝试的)
|
||||||
|
func (s *Storage) GetRandomByProtocolExclude(protocol string, excludes []string) (*Proxy, error) {
|
||||||
|
proxies, err := s.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeMap := make(map[string]bool)
|
||||||
|
for _, e := range excludes {
|
||||||
|
excludeMap[e] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var available []Proxy
|
||||||
|
for _, p := range proxies {
|
||||||
|
if p.Protocol == protocol && !excludeMap[p.Address] {
|
||||||
|
available = append(available, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(available) == 0 {
|
||||||
|
return nil, fmt.Errorf("no %s proxy available", protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := available[time.Now().UnixNano()%int64(len(available))]
|
||||||
|
return &proxy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLowestLatencyByProtocolExclude 按协议获取最低延迟代理(排除已尝试的)
|
||||||
|
func (s *Storage) GetLowestLatencyByProtocolExclude(protocol string, excludes []string) (*Proxy, error) {
|
||||||
|
proxies, err := s.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeMap := make(map[string]bool)
|
||||||
|
for _, e := range excludes {
|
||||||
|
excludeMap[e] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll() 已经按 latency ASC 排序,找到第一个匹配协议且不在排除列表中的
|
||||||
|
for _, p := range proxies {
|
||||||
|
if p.Protocol == protocol && !excludeMap[p.Address] {
|
||||||
|
proxy := p
|
||||||
|
return &proxy, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no %s proxy available", protocol)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete 立即删除指定代理
|
// Delete 立即删除指定代理
|
||||||
func (s *Storage) Delete(address string) error {
|
func (s *Storage) Delete(address string) error {
|
||||||
_, err := s.db.Exec(`DELETE FROM proxies WHERE address = ?`, address)
|
_, err := s.db.Exec(`DELETE FROM proxies WHERE address = ?`, address)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
| 脚本 | 语言 | 依赖 | 运行模式 | 推荐度 |
|
| 脚本 | 语言 | 依赖 | 运行模式 | 推荐度 |
|
||||||
|------|------|------|----------|--------|
|
|------|------|------|----------|--------|
|
||||||
| `test_proxy.sh` | Bash | curl + Python3 | 持续运行 | ⭐⭐⭐ |
|
| `test_proxy.sh` | Bash | curl + Python3 | 持续运行 | ⭐⭐⭐ |
|
||||||
|
| `test_socks5.sh` | Bash | curl + Python3 | 持续运行 | ⭐⭐⭐ |
|
||||||
| `test_proxy.go` | Go | `golang.org/x/net/proxy` | 持续运行 | ⭐⭐ |
|
| `test_proxy.go` | Go | `golang.org/x/net/proxy` | 持续运行 | ⭐⭐ |
|
||||||
| `test_proxy.py` | Python | `requests`, `pysocks` | 持续运行 | ⭐⭐ |
|
| `test_proxy.py` | Python | `requests`, `pysocks` | 持续运行 | ⭐⭐ |
|
||||||
|
|
||||||
@@ -14,15 +15,24 @@
|
|||||||
|
|
||||||
### Bash 脚本(推荐)
|
### Bash 脚本(推荐)
|
||||||
|
|
||||||
|
**HTTP 代理测试**:
|
||||||
```bash
|
```bash
|
||||||
# 从项目根目录运行(持续测试 HTTP)
|
# 测试 7777 端口(随机轮换)
|
||||||
./test/test_proxy.sh
|
./test/test_proxy.sh 7777
|
||||||
|
|
||||||
# 测试 HTTP 协议
|
# 测试 7776 端口(最低延迟)
|
||||||
./test/test_proxy.sh http
|
./test/test_proxy.sh 7776
|
||||||
|
|
||||||
# 测试 SOCKS5 协议
|
# 按 Ctrl+C 停止并查看统计
|
||||||
./test/test_proxy.sh socks5
|
```
|
||||||
|
|
||||||
|
**SOCKS5 代理测试**:
|
||||||
|
```bash
|
||||||
|
# 测试 7779 端口(随机轮换)
|
||||||
|
./test/test_socks5.sh 7779
|
||||||
|
|
||||||
|
# 测试 7780 端口(最低延迟)
|
||||||
|
./test/test_socks5.sh 7780
|
||||||
|
|
||||||
# 按 Ctrl+C 停止并查看统计
|
# 按 Ctrl+C 停止并查看统计
|
||||||
```
|
```
|
||||||
@@ -69,9 +79,9 @@ python test/test_proxy.py
|
|||||||
|
|
||||||
## 🔀 测试不同端口策略
|
## 🔀 测试不同端口策略
|
||||||
|
|
||||||
```bash
|
### HTTP 代理端口对比
|
||||||
# 对比两个端口的行为差异:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
# 随机轮换模式 - IP 高度分散
|
# 随机轮换模式 - IP 高度分散
|
||||||
./test/test_proxy.sh 7777
|
./test/test_proxy.sh 7777
|
||||||
|
|
||||||
@@ -83,6 +93,22 @@ python test/test_proxy.py
|
|||||||
- **7777 端口**:每次请求的出口 IP 应该不同(证明在轮换)
|
- **7777 端口**:每次请求的出口 IP 应该不同(证明在轮换)
|
||||||
- **7776 端口**:连续多次请求的出口 IP 基本相同(证明固定使用最优代理)
|
- **7776 端口**:连续多次请求的出口 IP 基本相同(证明固定使用最优代理)
|
||||||
|
|
||||||
|
### SOCKS5 代理端口对比
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 随机轮换模式 - IP 高度分散
|
||||||
|
./test/test_socks5.sh 7779
|
||||||
|
|
||||||
|
# 最低延迟模式 - 固定使用最快代理
|
||||||
|
./test/test_socks5.sh 7780
|
||||||
|
```
|
||||||
|
|
||||||
|
**观察要点**:
|
||||||
|
- **7779 端口**:每次连接的出口 IP 应该不同
|
||||||
|
- **7780 端口**:连续多次连接的出口 IP 基本相同
|
||||||
|
|
||||||
|
> 💡 **提示**:SOCKS5 测试脚本使用 `-k` 参数跳过 SSL 证书验证,因为免费上游代理常有证书问题。生产环境建议使用质量更好的付费代理。
|
||||||
|
|
||||||
## 🔍 预期输出
|
## 🔍 预期输出
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
# 按 Ctrl+C 停止测试
|
# 按 Ctrl+C 停止测试
|
||||||
# 用法: ./test_proxy.sh [端口号,默认7777]
|
# 用法: ./test_proxy.sh [端口号,默认7777]
|
||||||
|
|
||||||
|
# PROXY_HOST="192.227.184.201"
|
||||||
|
# PROXY_HOST="proxy.amux.ai"
|
||||||
PROXY_HOST="127.0.0.1"
|
PROXY_HOST="127.0.0.1"
|
||||||
PROXY_PORT="${1:-7777}"
|
PROXY_PORT="${1:-7777}"
|
||||||
TEST_URL="http://ip-api.com/json/?fields=countryCode,query"
|
TEST_URL="http://ip-api.com/json/?fields=countryCode,query"
|
||||||
|
|||||||
75
test/test_socks5.sh
Executable file
75
test/test_socks5.sh
Executable file
@@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###
|
||||||
|
# @LastEditTime: 2026-03-29 23:26:29
|
||||||
|
# @Description: ...
|
||||||
|
# @Date: 2026-03-29 23:14:32
|
||||||
|
# @Author: isboyjc
|
||||||
|
# @LastEditors: isboyjc
|
||||||
|
###
|
||||||
|
|
||||||
|
# GoProxy SOCKS5 代理测试脚本
|
||||||
|
# 用法: ./test_socks5.sh [端口号,默认7779]
|
||||||
|
|
||||||
|
PROXY_HOST="${PROXY_HOST:-127.0.0.1}"
|
||||||
|
PROXY_PORT="${1:-7779}"
|
||||||
|
TEST_URL="https://httpbin.org/ip"
|
||||||
|
DELAY=1
|
||||||
|
|
||||||
|
# 统计变量
|
||||||
|
total=0
|
||||||
|
success=0
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
# 获取毫秒时间戳(兼容 macOS 和 Linux)
|
||||||
|
get_ms_time() {
|
||||||
|
python3 -c 'import time; print(int(time.time() * 1000))'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 国家代码转 emoji 旗帜
|
||||||
|
country_to_emoji() {
|
||||||
|
local country_code="$1"
|
||||||
|
if [ -z "$country_code" ] || [ "$country_code" = "null" ]; then
|
||||||
|
echo "🌐"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local first="${country_code:0:1}"
|
||||||
|
local second="${country_code:1:1}"
|
||||||
|
python3 -c "print(chr(127462 + ord('$first') - ord('A')) + chr(127462 + ord('$second') - ord('A')))"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 捕获 Ctrl+C 信号
|
||||||
|
trap ctrl_c INT
|
||||||
|
function ctrl_c() {
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
loss_rate=$(awk "BEGIN {printf \"%.1f\", ($total - $success)/$total*100}")
|
||||||
|
echo "$total requests transmitted, $success received, $((total - success)) failed, ${loss_rate}% packet loss"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "SOCKS5 PROXY ${PROXY_HOST}:${PROXY_PORT}: continuous mode"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
total=$((total + 1))
|
||||||
|
|
||||||
|
# 使用 curl 的 SOCKS5 支持(-k 跳过 SSL 验证,因为免费代理证书常有问题)
|
||||||
|
start=$(get_ms_time)
|
||||||
|
response=$(curl -s -k --socks5-hostname ${PROXY_HOST}:${PROXY_PORT} ${TEST_URL} --max-time 10 2>&1)
|
||||||
|
end=$(get_ms_time)
|
||||||
|
latency=$((end - start))
|
||||||
|
|
||||||
|
if echo "$response" | grep -q '"origin"'; then
|
||||||
|
success=$((success + 1))
|
||||||
|
origin=$(echo "$response" | grep -o '"origin":"[^"]*"' | cut -d'"' -f4 | cut -d',' -f1)
|
||||||
|
country=$(curl -s "http://ip-api.com/json/${origin}?fields=countryCode" 2>/dev/null | grep -o '"countryCode":"[^"]*"' | cut -d'"' -f4)
|
||||||
|
emoji=$(country_to_emoji "$country")
|
||||||
|
echo "socks5 #${total}: ${origin} ${emoji} ${country} (${latency}ms)"
|
||||||
|
else
|
||||||
|
fail=$((fail + 1))
|
||||||
|
echo "socks5 #${total}: request failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep $DELAY
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user