update outbound

This commit is contained in:
WJQSERVER 2025-02-10 18:53:16 +08:00
parent 4c5d288f03
commit 2b7fbd2a0d
8 changed files with 139 additions and 46 deletions

View file

@ -1,5 +1,11 @@
# 更新日志
25w13a
---
- PRE-RELEASE: 此版本是v2.2.0的预发布版本,请勿在生产环境中使用;
- ADD: 加入`Socks5``HTTP(S)`出站支持
- CHANGE: 配置新增`Outbound`配置块
2.1.0
---
- RELEASE: v2.1.0正式版发布;

View file

@ -116,9 +116,9 @@ rateMethod = "total" # "ip" or "total" 速率限制方式
ratePerMinute = 180 # 每分钟限制请求数量
burst = 5 # 突发请求数量
[proxy]
enabled = false # 是否使用代理连接
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" 支持HTTP代理和SOCKS5代理 支持多级SOCKS5代理
[outbound]
enabled = false # 是否使用自定义代理出站
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" 支持Socks5/HTTP(S)出站传输
```
### 黑名单配置

View file

@ -13,7 +13,7 @@ type Config struct {
Blacklist BlacklistConfig
Whitelist WhitelistConfig
RateLimit RateLimitConfig
Proxy ProxyConfig
Outbound OutboundConfig
}
type ServerConfig struct {
@ -63,7 +63,12 @@ type RateLimitConfig struct {
Burst int `toml:"burst"`
}
type ProxyConfig struct {
/*
[outbound]
enabled = false
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
*/
type OutboundConfig struct {
Enabled bool `toml:"enabled"`
Url string `toml:"url"`
}

View file

@ -37,6 +37,6 @@ rateMethod = "total" # "ip" or "total"
ratePerMinute = 180
burst = 5
[proxy]
[outbound]
enabled = false
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"

View file

@ -36,3 +36,7 @@ enabled = false
rateMethod = "total" # "ip" or "total"
ratePerMinute = 180
burst = 5
[outbound]
enabled = false
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"

View file

@ -47,7 +47,9 @@ func initChunkedHTTPClient(cfg *config.Config) {
KeepAlive: 30 * time.Second,
}).DialContext,
}
initTransport(cfg, ctr)
if cfg.Outbound.Enabled {
initTransport(cfg, ctr)
}
cclient = &http.Client{
Transport: ctr,
}

View file

@ -1,3 +1,8 @@
/*
made&PR by @lfhy
https://github.com/WJQSERVER-STUDIO/ghproxy/pull/46
*/
package proxy
import (
@ -9,53 +14,122 @@ import (
"golang.org/x/net/proxy"
)
func newProxyDial(proxyUrls string) proxy.Dialer {
var proxyDialer proxy.Dialer = proxy.Direct
for _, proxyUrl := range strings.Split(proxyUrls, ",") {
urlInfo, err := url.Parse(proxyUrl)
if err != nil {
continue
}
if urlInfo.Scheme != "socks5" {
continue
}
var auth *proxy.Auth = nil
if urlInfo.User != nil {
pwd, ok := urlInfo.User.Password()
if !ok {
continue
}
auth = &proxy.Auth{
User: urlInfo.User.Username(),
Password: pwd,
}
}
dialer, err := proxy.SOCKS5("tcp", urlInfo.Host, auth, proxyDialer)
if err == nil {
proxyDialer = dialer
}
}
return proxyDialer
}
// initTransport 初始化 HTTP 传输层的代理设置
func initTransport(cfg *config.Config, transport *http.Transport) {
if !cfg.Proxy.Enabled {
// 如果代理功能未启用,直接返回
if !cfg.Outbound.Enabled {
return
}
if cfg.Proxy.Url == "" {
// 如果代理 URL 未设置,使用环境变量中的代理配置
if cfg.Outbound.Url == "" {
transport.Proxy = http.ProxyFromEnvironment
logWarning("Outbound proxy is not set, using environment variables")
return
}
// 尝试解析代理 URL
proxyInfo, err := url.Parse(cfg.Outbound.Url)
if err != nil {
// 如果解析失败,记录错误日志并使用环境变量中的代理配置
logError("Failed to parse outbound proxy URL %v", err)
transport.Proxy = http.ProxyFromEnvironment
return
}
proxyInfo, err := url.Parse(cfg.Proxy.Url)
if err == nil {
if strings.HasPrefix(cfg.Proxy.Url, "http") {
transport.Proxy = http.ProxyURL(proxyInfo)
// 根据代理 URL 的 scheme协议类型选择代理类型
switch strings.ToLower(proxyInfo.Scheme) {
case "http", "https": // 如果是 HTTP/HTTPS 代理
transport.Proxy = http.ProxyURL(proxyInfo) // 设置 HTTP(S) 代理
logInfo("Using HTTP(S) proxy: %s", proxyInfo.Redacted())
case "socks5": // 如果是 SOCKS5 代理
// 调用 newProxyDial 创建 SOCKS5 代理拨号器
proxyDialer := newProxyDial(cfg.Outbound.Url)
transport.Proxy = nil // 禁用 HTTP Proxy 设置,因为 SOCKS5 不需要 HTTP Proxy
// 尝试将 Dialer 转换为支持上下文的 ContextDialer
if contextDialer, ok := proxyDialer.(proxy.ContextDialer); ok {
transport.DialContext = contextDialer.DialContext
} else {
proxyDialer := newProxyDial(cfg.Proxy.Url)
// 如果不支持 ContextDialer则回退到传统的 Dial 方法
transport.Dial = proxyDialer.Dial
transport.DialContext = proxyDialer.(proxy.ContextDialer).DialContext
logWarning("SOCKS5 dialer does not support ContextDialer, using legacy Dial")
}
logInfo("Using SOCKS5 proxy chain: %s", cfg.Outbound.Url)
default: // 如果代理协议不支持
logError("Unsupported proxy scheme: %s", proxyInfo.Scheme)
transport.Proxy = http.ProxyFromEnvironment // 回退到环境变量代理
}
}
// newProxyDial 创建一个 SOCKS5 代理拨号器
func newProxyDial(proxyUrls string) proxy.Dialer {
var proxyDialer proxy.Dialer = proxy.Direct // 初始为直接连接,不使用代理
// 支持多个代理 URL以逗号分隔
for _, proxyUrl := range strings.Split(proxyUrls, ",") {
proxyUrl = strings.TrimSpace(proxyUrl) // 去除首尾空格
if proxyUrl == "" { // 跳过空的代理 URL
continue
}
// 解析代理 URL
urlInfo, err := url.Parse(proxyUrl)
if err != nil {
// 如果 URL 解析失败,记录错误日志并跳过
logError("Failed to parse proxy URL %q: %v", proxyUrl, err)
continue
}
// 检查代理协议是否为 SOCKS5
if urlInfo.Scheme != "socks5" {
logWarning("Skipping non-SOCKS5 proxy: %s", urlInfo.Scheme)
continue
}
// 解析代理认证信息(用户名和密码)
auth := parseAuth(urlInfo)
// 创建 SOCKS5 代理拨号器
dialer, err := createSocksDialer(urlInfo.Host, auth, proxyDialer)
if err != nil {
// 如果创建失败,记录错误日志并跳过
logError("Failed to create SOCKS5 dialer for %q: %v", proxyUrl, err)
continue
}
// 更新代理拨号器,支持代理链
proxyDialer = dialer
}
return proxyDialer
}
// parseAuth 解析代理 URL 中的认证信息(用户名和密码)
func parseAuth(urlInfo *url.URL) *proxy.Auth {
// 如果 URL 中没有用户信息,返回 nil
if urlInfo.User == nil {
return nil
}
// 获取用户名
username := urlInfo.User.Username()
// 获取密码注意Password() 返回两个值,需要显式处理第二个值)
password, passwordSet := urlInfo.User.Password()
if !passwordSet {
password = "" // 如果密码未设置,使用空字符串
}
// 返回包含用户名和密码的认证信息
return &proxy.Auth{
User: username,
Password: password, // 允许空密码
}
}
// createSocksDialer 创建 SOCKS5 拨号器
func createSocksDialer(host string, auth *proxy.Auth, previous proxy.Dialer) (proxy.Dialer, error) {
// 调用 golang.org/x/net/proxy 提供的 SOCKS5 方法创建拨号器
return proxy.SOCKS5("tcp", host, auth, previous)
}

View file

@ -23,7 +23,9 @@ func initGitHTTPClient(cfg *config.Config) {
MaxConnsPerHost: 30,
IdleConnTimeout: 30 * time.Second,
}
initTransport(cfg, gtr)
if cfg.Outbound.Enabled {
initTransport(cfg, ctr)
}
gclient = &http.Client{
Transport: gtr,
}