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:
isboyjc
2026-03-30 01:02:00 +08:00
parent cede441015
commit b1555a702f
11 changed files with 1052 additions and 93 deletions

View File

@@ -2,9 +2,11 @@
CONTAINER_NAME=goproxy
# 端口配置
STABLE_PORT=7776 # 最低延迟代理端口
RANDOM_PORT=7777 # 随机轮换代理端口
WEBUI_PORT=7778 # WebUI 管理端口
STABLE_PORT=7776 # HTTP 最低延迟代理端口
RANDOM_PORT=7777 # HTTP 随机轮换代理端口
WEBUI_PORT=7778 # WebUI 管理端口
SOCKS5_RANDOM_PORT=7779 # SOCKS5 随机轮换代理端口
SOCKS5_STABLE_PORT=7780 # SOCKS5 最低延迟代理端口
# 地理过滤配置
# 屏蔽指定国家代码的出口代理(逗号分隔,如 CN,RU,KP

447
README.md
View File

@@ -7,12 +7,23 @@
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
[![Go Version](https://img.shields.io/badge/Go-1.25-00ADD8?logo=go)](https://go.dev/)
GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供统一的 HTTP 代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。
GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供 **HTTP 和 SOCKS5 双协议**代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。
**GitHub**[github.com/isboyjc/GoProxy](https://github.com/isboyjc/GoProxy)
![](https://cdn.isboyjc.com/img/Xnip2026-03-29_03-35-06.png)
## 📋 快速导航
- [✨ 核心特性](#-核心特性) - 智能池子、按需抓取、健康管理、双协议支持
- [🔌 HTTP vs SOCKS5 协议对比](#-http-vs-socks5-协议对比) - 协议区别、使用建议
- [🚀 快速开始](#-快速开始) - 本地运行、端口说明、代理使用
- [🐳 Docker 部署](#-docker-部署) - 容器化部署、环境配置、安全建议
- [⚙️ 配置说明](#-配置说明) - 完整参数详解、性能优化
- [🛠️ 开发与调试](#-开发与调试) - 日志查看、数据库操作
- [🧪 测试代理服务](#-测试代理服务) - 测试脚本、持续测试、认证测试
- [❓ 常见问题](#-常见问题) - SOCKS5 使用、浏览器配置、故障排查
## ✨ 核心特性
### 🎯 智能池子机制
@@ -41,12 +52,17 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
- **用户无感知**:自动重试在服务端完成,用户只会收到成功响应或最终失败提示
- **防重复尝试**:已尝试过的失败代理不会在同一请求中再次使用
### 🚪 端口策略
- **7777 端口(随机轮换)**每次请求随机选择代理IP 高度分散,适合爬虫和数据采集
- **7776 端口(最低延迟)**:固定使用延迟最低的代理,除非失败才切换,适合长连接和流媒体
- **自动切换**两个端口都支持失败自动重试7776 失败后切换到次优代理
- **共享池子**:两个端口使用同一个代理池,统一管理和优化
- **可选认证**:支持 Basic Auth 认证保护,对外开放时可启用
### 🚪 端口多协议支持
- **双协议支持**:同时提供 HTTP 和 SOCKS5 协议服务,满足不同应用需求
- **双模式策略**:每种协议都提供随机轮换和最低延迟两种模式
- **4 个服务端口**
- `7777` - HTTP 随机轮换IP 多样性,适合爬虫)
- `7776` - HTTP 最低延迟(稳定连接,适合流媒体)
- `7779` - SOCKS5 随机轮换IP 多样性,适合浏览器/游戏)
- `7780` - SOCKS5 最低延迟(稳定连接,适合固定应用)
- **自动切换**:所有端口都支持失败自动重试,智能切换备用代理
- **共享池子**:四个端口使用同一个代理池,统一管理和优化
- **可选认证**:支持 Basic AuthHTTP和用户名/密码认证SOCKS5对外开放时可启用
### 🎨 黑客风格 WebUI
- **Matrix 美学**:荧光绿 + 纯黑背景CRT 扫描线效果JetBrains Mono 等宽字体
@@ -58,15 +74,40 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
- **交互优化**:点击地址复制、单个代理刷新、实时日志倒计时
### 📊 适用场景
- **Web 开发测试**HTTP 代理测试 API、爬虫开发、数据采集
- **浏览器代理**SOCKS5 协议配置浏览器代理,访问受限网站
- **命令行工具**curl、wget、git 等工具使用 HTTP 代理
- **应用代理**:需要 SOCKS5 协议的应用SSH、游戏、聊天工具
- **小型 VPS**:低资源消耗(固定池子 + 按需抓取 + 限流查询)
- **稳定需求**:自动剔除失败代理,始终保持健康池子
- **质量优先**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) - 国家代码、使用场景、测试方法
- [数据目录说明](DATA_DIRECTORY.md) - 数据库、配置文件、备份恢复
- [测试脚本使用](test/README.md) - Bash/Go/Python 测试脚本详细说明
- [测试脚本使用](test/README.md) - HTTP + SOCKS5 测试脚本详细说明
- [架构设计文档](POOL_DESIGN.md) - 完整的系统设计和实现细节
## 📦 项目结构
@@ -84,11 +125,14 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格
├── checker/ # 🆕 分批健康检查器
├── optimizer/ # 🆕 优化轮换器(定时优化池子质量)
├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表)
├── proxy/ # 🆕 对外 HTTP 代理服务(双端口 + 可选认证)
├── proxy/ # 🆕 对外代理服务HTTP + SOCKS5 双协议4 端口 + 可选认证)
│ ├── server.go # HTTP 代理服务器
│ └── socks5_server.go # SOCKS5 代理服务器
├── webui/ # 🆕 黑客风格 WebUI健康仪表盘、配置界面、RBAC
├── logger/ # 内存日志收集
├── test/ # 🧪 测试脚本与文档
│ ├── test_proxy.sh # Bash 测试脚本
│ ├── test_proxy.sh # HTTP 代理测试脚本Bash
│ ├── test_socks5.sh # SOCKS5 代理测试脚本Bash
│ ├── test_proxy.go # Go 测试脚本
│ ├── test_proxy.py # Python 测试脚本
│ └── README.md # 测试脚本使用说明
@@ -134,22 +178,34 @@ go build -o proxygo .
- 状态监控(每 30 秒)
- 健康检查(默认 5 分钟)
- 优化轮换(默认 30 分钟)
7. 启动个代理服务(支持可选认证):
- `:7776` - 最低延迟模式(稳定连接)
- `:7777` - 随机轮换模式IP 多样性)
7. 启动个代理服务(支持可选认证):
- `:7776` - HTTP 最低延迟模式(稳定连接)
- `: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`
- **默认密码**`goproxy`(可通过 `WEBUI_PASSWORD` 环境变量自定义)
- **访问方式**:本地使用 `localhost`,远程使用服务器 IP 地址
### 使用代理
GoProxy 提供**个代理端口**,满足不同场景需求:
GoProxy 提供**个代理端口**支持 HTTP 和 SOCKS5 两种协议,满足不同场景需求:
#### 🎲 7777 端口 - 随机轮换模式
#### 🌐 HTTP 协议代理
##### 🎲 7777 端口 - HTTP 随机轮换模式
适合需要 **IP 多样性** 的场景(爬虫、数据采集、负载均衡):
@@ -166,7 +222,7 @@ curl -x http://your-server-ip:7777 https://httpbin.org/ip
- 优先使用高质量S/A 级)代理
- 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
# 使用随机模式
export http_proxy=http://localhost:7777
@@ -195,25 +291,77 @@ export https_proxy=http://localhost:7777
# 或使用稳定模式
export http_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
# 格式http://username:password@host:port
export http_proxy=http://proxy:your_password@192.168.1.100:7777
export https_proxy=http://proxy:your_password@192.168.1.100:7777
# 使用随机模式
export ALL_PROXY=socks5://localhost:7779
# 或使用稳定模式
export ALL_PROXY=socks5://localhost:7780
# 远程使用(带认证)
export ALL_PROXY=socks5://proxy:your_password@your-server-ip:7779
```
#### 端口对比
| 特性 | 7777随机轮换 | 7776最低延迟 |
|------|-----------------|-----------------|
| **选择策略** | 随机选择(优先高质量) | 固定使用延迟最低的 |
| **IP 多样性** | ⭐⭐⭐⭐⭐ 高度分散 | ⭐ 基本固定 |
| **连接稳定性** | ⭐⭐⭐ 每次切换 | ⭐⭐⭐⭐⭐ 固定不变 |
| **性能表现** | ⭐⭐⭐⭐ 平均延迟 | ⭐⭐⭐⭐⭐ 最优延迟 |
| **适用场景** | 爬虫、数据采集、防封禁 | 长连接、流媒体、下载 |
| **失败切换** | 自动重试 3 次 | 失败后切换到次优代理 |
| 端口 | 协议 | 模式 | IP 多样性 | 稳定性 | 性能 | 适用场景 |
|------|------|------|----------|--------|------|---------|
| **7777** | HTTP | 随机轮换 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 爬虫、数据采集 |
| **7776** | HTTP | 最低延迟 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 长连接、流媒体 |
| **7779** | SOCKS5 | 随机轮换 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | 浏览器、游戏、SSH |
| **7780** | SOCKS5 | 最低延迟 | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 稳定应用连接 |
#### 协议选择建议
**何时使用 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 7777:7777 \
-p 7778:7778 \
-p 7779:7779 \
-p 7780:7780 \
-e WEBUI_PASSWORD=your_password \
-v goproxy-data:/app/data \
ghcr.io/isboyjc/goproxy:latest
@@ -327,16 +477,18 @@ docker compose up -d
| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 |
| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码 |
| `WEBUI_PASSWORD` | `goproxy` | WebUI 登录密码 |
| `STABLE_PORT` | `7776` | 最低延迟代理端口 |
| `RANDOM_PORT` | `7777` | 随机轮换代理端口 |
| `STABLE_PORT` | `7776` | HTTP 最低延迟代理端口 |
| `RANDOM_PORT` | `7777` | HTTP 随机轮换代理端口 |
| `WEBUI_PORT` | `7778` | WebUI 管理端口 |
| `SOCKS5_STABLE_PORT` | `7780` | SOCKS5 最低延迟代理端口 |
| `SOCKS5_RANDOM_PORT` | `7779` | SOCKS5 随机轮换代理端口 |
完整环境变量列表请查看 `.env.example` 文件。
**⚠️ 生产部署注意事项**
- 如使用 Dokploy、Coolify 等平台部署,确保 `docker-compose.yml` 中配置了平台网络(如 `dokploy-network`
- WebUI 端口可通过平台的域名功能访问,无需手动配置端口绑定
- 代理端口7776/7777通过 `IP:端口` 直接访问,**强烈建议启用认证**
- 代理端口7776/7777/7779/7780)通过 `IP:端口` 直接访问,**强烈建议启用认证**
**常用配置示例**
@@ -371,7 +523,7 @@ docker compose up -d
### 安全部署配置
**默认配置**:代理服务对外开放(端口 7776、7777WebUI 对外开放(端口 7778
**默认配置**:代理服务对外开放(端口 7776、7777、7779、7780WebUI 对外开放(端口 7778
**⚠️ 重要提示**
@@ -393,15 +545,21 @@ WEBUI_PASSWORD=admin_pass
**客户端使用(带认证)**
```bash
# 环境变量方式
# HTTP 代理 - 环境变量方式
export http_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
# Python requests
proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': '...'}
# SOCKS5 代理 - curl 使用
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` | 随机轮换代理端口 |
| `stable_proxy_port` | `:7776` | 最低延迟代理端口 |
| `proxy_port` | `:7777` | HTTP 随机轮换代理端口 |
| `stable_proxy_port` | `:7776` | HTTP 最低延迟代理端口 |
| `socks5_port` | `:7779` | SOCKS5 随机轮换代理端口 |
| `stable_socks5_port` | `:7780` | SOCKS5 最低延迟代理端口 |
| `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_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
```
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 认证有什么区别?
A:
- **代理认证**:保护 7776/7777 代理服务端口,防止代理被滥用
- **代理认证**:保护代理服务端口(7776/7777/7779/7780,防止代理被滥用
- **WebUI 认证**:保护 7778 管理后台,区分访客和管理员权限
- 两者独立配置,互不影响
- 启用代理认证时HTTP 和 SOCKS5 代理都需要认证
## 🛠️ 开发与调试
@@ -937,6 +1103,7 @@ A:
- `[health]`:健康检查器
- `[optimize]`:优化器
- `[monitor]`:状态监控器
- `[socks5]`SOCKS5 代理服务器(握手、认证、连接建立)
### 数据库操作
@@ -956,35 +1123,52 @@ sqlite3 data/proxy.db "DELETE FROM proxies;"
## 🧪 测试代理服务
项目提供了种测试脚本,用于验证代理服务功能和性能(位于 `test/` 目录)。
项目提供了种测试脚本,用于验证 HTTP 和 SOCKS5 代理服务功能和性能(位于 `test/` 目录)。
### 快速测试
**HTTP 代理测试**
```bash
# 测试随机轮换模式(默认 7777 端口)
# 测试 HTTP 随机轮换模式7777 端口)
./test/test_proxy.sh
# 测试最低延迟模式7776 端口)
# 测试 HTTP 最低延迟模式7776 端口)
./test/test_proxy.sh 7776
# 使用 Go 脚本
go run test/test_proxy.go # 默认 7777
go run test/test_proxy.go 7776 # 测试 7776
# 使用 Go/Python 脚本
go run test/test_proxy.go 7777
python test/test_proxy.py 7776
```
# 使用 Python 脚本
python test/test_proxy.py # 默认 7777
python test/test_proxy.py 7776 # 测试 7776
**SOCKS5 代理测试**
```bash
# 测试 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 停止测试并查看统计
```
测试脚本特点:
**HTTP 测试脚本**`test_proxy.sh`特点:
- **持续运行模式**:类似 `ping` 命令,持续发送请求
- 实时显示每次请求的出口 IP 和延迟
- 实时显示每次请求的出口 IP、国家和延迟
- 动态更新成功率统计
- 验证代理轮换机制
- 验证 HTTP 代理轮换机制
- 按 `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
# 启用认证后测试
# HTTP 代理 - 带认证
curl -x http://myuser:mypass@localhost:7777 https://httpbin.org/ip
# 无认证请求(应该返回 407 错误)
# HTTP 代理 - 无认证(应该返回 407 错误)
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)
## ❓ 常见问题
### Q1: HTTP 和 SOCKS5 代理有什么区别?
**简单理解**
- **HTTP 代理**:专门为 HTTP/HTTPS 设计,简单易用,命令行工具支持好
- **SOCKS5 代理**:通用代理协议,支持所有 TCP/UDP 协议,功能更强大
**详细对比**见上方 [HTTP vs SOCKS5 协议对比](#-http-vs-socks5-协议对比) 章节。
### Q2: 我应该使用哪个端口?
根据你的需求:
| 需求 | 推荐端口 | 理由 |
|------|---------|------|
| 🕷️ **爬虫/数据采集** | 7777HTTP 随机) | IP 高度分散,降低封禁风险 |
| 🎥 **流媒体/长连接** | 7776HTTP 稳定) | 延迟最低,连接稳定 |
| 🌐 **浏览器代理** | 7779SOCKS5 随机) | 协议支持好IP 多样性高 |
| 🎮 **游戏/SSH/聊天** | 7780SOCKS5 稳定) | 非 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) 进行魔改和增强。
@@ -1029,12 +1351,13 @@ curl -x http://localhost:7777 https://httpbin.org/ip
- 🆕 **按需抓取策略**源分组、断路器保护、Emergency/Refill/Optimize 多模式
- 🆕 **分层健康管理**:批次检查、智能跳过 S 级、定时优化轮换
- 🆕 **智能重试机制**:自动故障切换、失败即删除、防重复尝试
- 🆕 **双端口服务**7777 随机轮换IP 多样性)+ 7776 最低延迟(稳定连接
- 🆕 **代理认证保护**:可选 Basic Auth 认证,对外开放时保护代理服务不被滥用
- 🆕 **双协议支持**HTTP + SOCKS5 双协议4 个服务端口(随机/稳定各 2 个
- 🆕 **双模式策略**每种协议都支持随机轮换IP 多样性)和最低延迟(稳定连接)
- 🆕 **代理认证保护**HTTP Basic Auth + SOCKS5 用户名/密码认证,对外开放时保护服务
- 🆕 **黑客风格 WebUI**Matrix 美学、实时仪表盘、完整配置界面、中英文切换
- 🆕 **双角色权限**:访客模式(只读)+ 管理员模式(完全控制),可安全公网开放
- 🆕 **扩展存储层**:质量等级、使用统计、源状态管理
- 🆕 **测试套件**Bash/Go/Python 三种测试脚本,持续运行模式,显示国旗 emoji
- 🆕 **测试套件**HTTP + SOCKS5 测试脚本,持续运行模式,显示国旗 emoji
- 🆕 **CI/CD 自动化**GitHub Actions 自动构建多架构镜像amd64/arm64双仓库发布
- 🆕 **环境变量配置**docker-compose + .env 文件,灵活配置各种部署场景

View File

@@ -34,10 +34,17 @@ type Config struct {
// 稳定代理端口(最低延迟模式)
StableProxyPort string
// SOCKS5 服务端口(随机轮换模式)
SOCKS5Port string
// 稳定 SOCKS5 端口(最低延迟模式)
StableSOCKS5Port string
// 代理服务认证配置
ProxyAuthEnabled bool // 是否启用代理认证(默认 false
ProxyAuthUsername string // 代理认证用户名(默认 "proxy"
ProxyAuthPasswordHash string // 代理认证密码 SHA256 哈希
ProxyAuthPassword string // 代理认证密码明文(用于 SOCKS5
ProxyAuthPasswordHash string // 代理认证密码 SHA256 哈希(用于 HTTP
// 地理过滤配置
BlockedCountries []string // 屏蔽的国家代码列表(如 ["CN", "RU"],默认 ["CN"]
@@ -139,11 +146,14 @@ func DefaultConfig() *Config {
WebUIPasswordHash: passwordHash(password),
ProxyPort: ":7777",
StableProxyPort: ":7776",
SOCKS5Port: ":7779",
StableSOCKS5Port: ":7780",
DBPath: dataDir() + "proxy.db",
// 代理认证配置
ProxyAuthEnabled: proxyAuthEnabled,
ProxyAuthUsername: proxyAuthUsername,
ProxyAuthPassword: proxyAuthPassword,
ProxyAuthPasswordHash: proxyAuthHash,
// 地理过滤配置

View File

@@ -10,9 +10,11 @@ services:
container_name: ${CONTAINER_NAME:-goproxy}
restart: unless-stopped
ports:
- "${STABLE_PORT:-7776}:7776" # 稳定代理(最低延迟
- "${RANDOM_PORT:-7777}:7777" # 随机轮换
- "${WEBUI_PORT:-7778}:7778" # WebUI有登录认证
- "${STABLE_PORT:-7776}:7776" # HTTP 最低延迟
- "${RANDOM_PORT:-7777}:7777" # HTTP 随机轮换
- "${WEBUI_PORT:-7778}:7778" # WebUI有登录认证
- "${SOCKS5_RANDOM_PORT:-7779}:7779" # SOCKS5 随机轮换
- "${SOCKS5_STABLE_PORT:-7780}:7780" # SOCKS5 最低延迟
volumes:
- goproxy-data:/app/data
environment:

View File

@@ -71,13 +71,13 @@ func (f *Fetcher) FetchSmart(mode string, preferredProtocol string) ([]storage.P
switch mode {
case "emergency":
// 紧急模式:使用所有可用源
sources = f.filterAvailableSources(allSources, preferredProtocol)
log.Printf("[fetch] 🚨 紧急模式: 使用 %d 个源", len(sources))
// 紧急模式:忽略断路器,强制使用所有源(包括被禁用的)
sources = f.filterAvailableSources(allSources, preferredProtocol, true)
log.Printf("[fetch] 🚨 紧急模式: 使用 %d 个源(忽略断路器)", len(sources))
case "refill":
// 补充模式:使用快更新源
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol)
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol, false)
log.Printf("[fetch] 🔄 补充模式: 使用 %d 个快更新源", len(sources))
case "optimize":
@@ -86,7 +86,7 @@ func (f *Fetcher) FetchSmart(mode string, preferredProtocol string) ([]storage.P
log.Printf("[fetch] ⚡ 优化模式: 使用 %d 个源", len(sources))
default:
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol)
sources = f.filterAvailableSources(fastUpdateSources, preferredProtocol, false)
}
if len(sources) == 0 {
@@ -97,11 +97,12 @@ func (f *Fetcher) FetchSmart(mode string, preferredProtocol string) ([]storage.P
}
// 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
for _, src := range sources {
// 检查断路器
if f.sourceManager != nil && !f.sourceManager.CanUseSource(src.URL) {
// 检查断路器(紧急模式下忽略)
if !ignoreCircuitBreaker && f.sourceManager != nil && !f.sourceManager.CanUseSource(src.URL) {
continue
}
// 如果指定了协议偏好,优先该协议的源
@@ -115,7 +116,7 @@ func (f *Fetcher) filterAvailableSources(sources []Source, preferredProtocol str
// selectRandomSources 随机选择N个源
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 {
return available
}

28
main.go
View File

@@ -70,9 +70,13 @@ func main() {
totalDeleted += int(deleted)
}
// 创建两个代理服务器:随机轮换 + 最低延迟
// 创建 HTTP 代理服务器:随机轮换 + 最低延迟
randomServer := proxy.New(store, cfg, "random", cfg.ProxyPort)
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
configChanged := make(chan struct{}, 1)
@@ -105,16 +109,30 @@ func main() {
// 监听配置变更
go watchConfigChanges(configChanged, poolMgr)
// 启动稳定代理服务(最低延迟模式)
// 启动 HTTP 稳定代理服务(最低延迟模式)
go func() {
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 {
log.Fatalf("random proxy server: %v", err)
log.Fatalf("random http proxy server: %v", err)
}
}

440
proxy/socks5_server.go Normal file
View 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)
}
}

View File

@@ -177,11 +177,21 @@ func (s *Storage) initSchema() error {
// AddProxy 新增代理,已存在则忽略
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 (?, ?)`,
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 批量新增
@@ -328,6 +338,56 @@ func (s *Storage) GetLowestLatencyExclude(excludes []string) (*Proxy, error) {
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 立即删除指定代理
func (s *Storage) Delete(address string) error {
_, err := s.db.Exec(`DELETE FROM proxies WHERE address = ?`, address)

View File

@@ -7,6 +7,7 @@
| 脚本 | 语言 | 依赖 | 运行模式 | 推荐度 |
|------|------|------|----------|--------|
| `test_proxy.sh` | Bash | curl + Python3 | 持续运行 | ⭐⭐⭐ |
| `test_socks5.sh` | Bash | curl + Python3 | 持续运行 | ⭐⭐⭐ |
| `test_proxy.go` | Go | `golang.org/x/net/proxy` | 持续运行 | ⭐⭐ |
| `test_proxy.py` | Python | `requests`, `pysocks` | 持续运行 | ⭐⭐ |
@@ -14,15 +15,24 @@
### Bash 脚本(推荐)
**HTTP 代理测试**
```bash
# 从项目根目录运行(持续测试 HTTP
./test/test_proxy.sh
# 测试 7777 端口(随机轮换
./test/test_proxy.sh 7777
# 测试 HTTP 协议
./test/test_proxy.sh http
# 测试 7776 端口(最低延迟)
./test/test_proxy.sh 7776
# 测试 SOCKS5 协议
./test/test_proxy.sh socks5
# 按 Ctrl+C 停止并查看统计
```
**SOCKS5 代理测试**
```bash
# 测试 7779 端口(随机轮换)
./test/test_socks5.sh 7779
# 测试 7780 端口(最低延迟)
./test/test_socks5.sh 7780
# 按 Ctrl+C 停止并查看统计
```
@@ -69,9 +79,9 @@ python test/test_proxy.py
## 🔀 测试不同端口策略
```bash
# 对比两个端口的行为差异:
### HTTP 代理端口对比
```bash
# 随机轮换模式 - IP 高度分散
./test/test_proxy.sh 7777
@@ -83,6 +93,22 @@ python test/test_proxy.py
- **7777 端口**:每次请求的出口 IP 应该不同(证明在轮换)
- **7776 端口**:连续多次请求的出口 IP 基本相同(证明固定使用最优代理)
### SOCKS5 代理端口对比
```bash
# 随机轮换模式 - IP 高度分散
./test/test_socks5.sh 7779
# 最低延迟模式 - 固定使用最快代理
./test/test_socks5.sh 7780
```
**观察要点**
- **7779 端口**:每次连接的出口 IP 应该不同
- **7780 端口**:连续多次连接的出口 IP 基本相同
> 💡 **提示**SOCKS5 测试脚本使用 `-k` 参数跳过 SSL 证书验证,因为免费上游代理常有证书问题。生产环境建议使用质量更好的付费代理。
## 🔍 预期输出
```

View File

@@ -4,6 +4,8 @@
# 按 Ctrl+C 停止测试
# 用法: ./test_proxy.sh [端口号默认7777]
# PROXY_HOST="192.227.184.201"
# PROXY_HOST="proxy.amux.ai"
PROXY_HOST="127.0.0.1"
PROXY_PORT="${1:-7777}"
TEST_URL="http://ip-api.com/json/?fields=countryCode,query"

75
test/test_socks5.sh Executable file
View 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