Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ require (
github.com/Calcium-Ion/go-epay v0.0.4
github.com/andybalholm/brotli v1.1.1
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/credentials v1.17.70
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b
github.com/gin-contrib/cors v1.7.2
Expand Down Expand Up @@ -42,9 +43,16 @@ require (
require (
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down
36 changes: 26 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,36 @@ github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0 h1:onfun1RA+Kc
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0/go.mod h1:4yg+jNTYlDEzBjhGS96v+zjyA3lfXlFd5CiTLIkPBLI=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 h1:HblK3eJHq54yET63qPCTJnks3loDse5xRmmqHgHzwoI=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6/go.mod h1:pbiaLIeYLUbgMY1kwEAdwO6UKD5ZNwdPGQlwokS9fe8=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4 h1:JgHnonzbnA3pbqj76wYsSZIZZQYBxkmMEjvL6GHy8XU=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4/go.mod h1:nZspkhg+9p8iApLFoyAqfyuMP0F38acy2Hm3r5r95Cg=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b h1:LTGVFpNmNHhj0vhOlfgWueFJ32eK9blaIlHR2ciXOT0=
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
Expand Down
11 changes: 10 additions & 1 deletion relay/channel/aws/relay-aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"

"one-api/common"
"one-api/dto"
"one-api/relay/channel/claude"
relaycommon "one-api/relay/common"
"one-api/relay/helper"
"one-api/service"
"one-api/types"
"strings"

"github.com/gin-gonic/gin"
"github.com/pkg/errors"
Expand All @@ -29,9 +31,16 @@ func newAwsClient(c *gin.Context, info *relaycommon.RelayInfo) (*bedrockruntime.
ak := awsSecret[0]
sk := awsSecret[1]
region := awsSecret[2]

tmpClient, err := service.NewProxyHttpClient(info.ChannelSetting.Proxy)
if err != nil {
return nil, err
}

client := bedrockruntime.New(bedrockruntime.Options{
Region: region,
Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(ak, sk, "")),
HTTPClient: tmpClient,
})

return client, nil
Expand Down
20 changes: 19 additions & 1 deletion service/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"net"
"net/http"
"net/url"
"one-api/common"
"time"

"one-api/common"
"one-api/util"

"golang.org/x/net/proxy"
)

Expand All @@ -25,11 +27,27 @@ func InitHttpClient() {
}

func GetHttpClient() *http.Client {
client, err := getHttpClient("")
// 2025-07-17 兼容模式,当获取不到 httpClient 时,使用默认业务逻辑返回的 httpClient
if err == nil {
return client
}

return httpClient
}

func getHttpClient(proxyURL string) (client *http.Client, err error) {
return util.GetHttpClient(proxyURL)
}

// NewProxyHttpClient 创建支持代理的 HTTP 客户端
func NewProxyHttpClient(proxyURL string) (*http.Client, error) {
client, err := getHttpClient(proxyURL)
// 2025-07-17 兼容模式,当获取不到 httpClient 时,使用默认业务逻辑返回的 httpClient
if err == nil {
return client, err
}

if proxyURL == "" {
return http.DefaultClient, nil
}
Expand Down
113 changes: 113 additions & 0 deletions util/http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package util

import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"

"one-api/common"

"golang.org/x/net/proxy"
)

const (
KeyOfMaxIdleConnsPerHost = "MAX_IDLE_CONNS_PER_HOST"
KeyOfMaxIdleConns = "MAX_IDLE_CONNS"
KeyOfIdleConnTimeout = "IDLE_CONN_TIMEOUT"
)

var (
innerHttpClients sync.Map

defaultMaxIdleConnsPerHost = 10
defaultMaxIdleConns = 100
defaultIdleConnTimeout = 60
)

func GetHttpClient(proxyURL string) (*http.Client, error) {
var err error
var parsedURL *url.URL
if proxyURL != "" {
parsedURL, err = url.Parse(proxyURL)
if err != nil {
return nil, err
}
}

if client, ok := innerHttpClients.Load(proxyURL); ok {
return client.(*http.Client), nil
}

var maxIdleConns = common.GetEnvOrDefault(KeyOfMaxIdleConns, defaultMaxIdleConns)
var maxIdleConnsPerHost = common.GetEnvOrDefault(KeyOfMaxIdleConnsPerHost, defaultMaxIdleConnsPerHost)
var idleConnTimeout = time.Duration(common.GetEnvOrDefault(KeyOfIdleConnTimeout, defaultIdleConnTimeout)) * time.Second

var transport *http.Transport
if proxyURL != "" {
switch parsedURL.Scheme {
case "http", "https":
transport = &http.Transport{
Proxy: http.ProxyURL(parsedURL),
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: idleConnTimeout,
}

case "socks5", "socks5h":
// 获取认证信息
var auth *proxy.Auth
if parsedURL.User != nil {
auth = &proxy.Auth{
User: parsedURL.User.Username(),
Password: "",
}
if password, ok := parsedURL.User.Password(); ok {
auth.Password = password
}
}

// 创建 SOCKS5 代理拨号器
// proxy.SOCKS5 使用 tcp 参数,所有 TCP 连接包括 DNS 查询都将通过代理进行。行为与 socks5h 相同
dialer, err := proxy.SOCKS5("tcp", parsedURL.Host, auth, proxy.Direct)
if err != nil {
return nil, err
}

transport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: idleConnTimeout,
}
Comment on lines +60 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify SOCKS5 proxy implementation behavior.

The comment on lines 74-75 states that proxy.SOCKS5 with "tcp" makes all connections including DNS queries go through the proxy (socks5h behavior), but this needs verification as the distinction between socks5 and socks5h schemes might not be properly handled.


🌐 Web query:

What is the difference between SOCKS5 and SOCKS5H proxy schemes and how does golang.org/x/net/proxy handle DNS resolution for each?

💡 Result:

The key difference between SOCKS5 and SOCKS5H proxy schemes centers on how DNS resolution is performed when making a proxied connection:

  • SOCKS5: The client (your local machine or application) performs DNS resolution. When you connect through a SOCKS5 proxy and provide a hostname, your client resolves that hostname to an IP address before sending the connection request to the proxy. The proxy then establishes a connection to the resolved IP. This method may expose your DNS queries locally, potentially leaking your intended targets[1][7].

  • SOCKS5H: The 'h' stands for hostname resolution by the proxy. When using SOCKS5H, your client sends the hostname (not the resolved IP address) to the proxy, which then performs the DNS lookup on your behalf and connects to the target host. This approach helps prevent DNS leaks and can be important for privacy and accessing geo-restricted content[1][7].

Here’s a table summarizing the difference:

Scheme DNS Resolution Location Risk of Local DNS Leak Use Case Example
SOCKS5 Client-side Yes General proxying, but less privacy for DNS
SOCKS5H Proxy-side No Privacy-sensitive tasks, geo-sensitive DNS

How does golang.org/x/net/proxy handle each?

  • The golang.org/x/net/proxy package only supports the socks5:// (SOCKS5) scheme and does not explicitly support the socks5h:// (SOCKS5H) scheme. When you provide a hostname, it is resolved locally before dialing through the proxy—consistent with standard SOCKS5 behavior[6].
    • For example, if you use the package’s proxy.SOCKS5() function and pass a hostname, the Go DNS resolver is used, so DNS requests go out locally, not through the proxy[3][2].
    • There is an open and longstanding issue where users have asked for support of socks5h:// so that hostname resolution can happen at the proxy. As of now, the package does not offer this functionality[6].

Summary:

  • SOCKS5 resolves DNS locally (potential leaks).
  • SOCKS5H resolves DNS at the proxy (no DNS leaks).
  • golang.org/x/net/proxy does not currently support SOCKS5H: DNS will always be resolved locally by the client, regardless of the scheme[2][3][6][7].
  • To avoid DNS leaks in Go, you would need to implement or use a library that specifically supports proxy-side DNS resolution or use a workaround to ensure DNS queries are sent through the proxy.

Citations:


Correct SOCKS5 proxy DNS behavior comment

The current comment implying that proxy.SOCKS5 over “tcp” will proxy DNS lookups (i.e. socks5h behavior) is inaccurate. The golang.org/x/net/proxy package only implements standard SOCKS5, which always resolves hostnames locally before dialing. It does not support remote (proxy-side) DNS resolution (socks5h).

Please update the comment in util/http_client.go (around lines 74–75) accordingly. For example:

-   // proxy.SOCKS5 使用 tcp 参数,所有 TCP 连接包括 DNS 查询都将通过代理进行。行为与 socks5h 相同
+   // proxy.SOCKS5 使用 tcp 参数代理 TCP 连接,但主机名解析仍在本地完成(无 socks5h 支持)

If you need DNS resolution by the proxy, you’ll have to implement or use a library that explicitly supports SOCKS5H.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "socks5", "socks5h":
// 获取认证信息
var auth *proxy.Auth
if parsedURL.User != nil {
auth = &proxy.Auth{
User: parsedURL.User.Username(),
Password: "",
}
if password, ok := parsedURL.User.Password(); ok {
auth.Password = password
}
}
// 创建 SOCKS5 代理拨号器
// proxy.SOCKS5 使用 tcp 参数,所有 TCP 连接包括 DNS 查询都将通过代理进行。行为与 socks5h 相同
dialer, err := proxy.SOCKS5("tcp", parsedURL.Host, auth, proxy.Direct)
if err != nil {
return nil, err
}
transport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: idleConnTimeout,
}
case "socks5", "socks5h":
// 获取认证信息
var auth *proxy.Auth
if parsedURL.User != nil {
auth = &proxy.Auth{
User: parsedURL.User.Username(),
Password: "",
}
if password, ok := parsedURL.User.Password(); ok {
auth.Password = password
}
}
// 创建 SOCKS5 代理拨号器
// proxy.SOCKS5 使用 tcp 参数代理 TCP 连接,但主机名解析仍在本地完成(无 socks5h 支持)
dialer, err := proxy.SOCKS5("tcp", parsedURL.Host, auth, proxy.Direct)
if err != nil {
return nil, err
}
transport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: idleConnTimeout,
}
🤖 Prompt for AI Agents
In util/http_client.go around lines 74 to 75, the comment incorrectly states
that proxy.SOCKS5 with "tcp" proxies DNS lookups like socks5h, but the
golang.org/x/net/proxy package only performs local DNS resolution before
dialing. Update the comment to clarify that DNS resolution is done locally and
that this implementation does not support remote DNS resolution (socks5h).
Mention that to achieve proxy-side DNS resolution, a different implementation or
library supporting SOCKS5H is required.

default:
return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
}
} else {
transport = &http.Transport{
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: idleConnTimeout,
}
}

var tmpClient *http.Client
if common.RelayTimeout > 0 {
tmpClient = &http.Client{
Transport: transport,
Timeout: time.Duration(common.RelayTimeout) * time.Second,
}
} else {
tmpClient = &http.Client{
Transport: transport,
}
}

innerHttpClients.Store(proxyURL, tmpClient)
return tmpClient, nil
Comment on lines +111 to +112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider memory management for the client cache.

The current implementation stores clients indefinitely in the sync.Map, which could lead to memory leaks if many different proxy URLs are used over time. Consider implementing a cleanup mechanism or LRU eviction.

Potential solutions:

  1. Add a periodic cleanup goroutine to remove unused clients
  2. Implement an LRU cache with size limits
  3. Add client reference counting with automatic cleanup

Do you want me to generate an implementation for any of these approaches?

🤖 Prompt for AI Agents
In util/http_client.go around lines 111 to 112, the current code stores HTTP
clients indefinitely in a sync.Map, risking memory leaks with many proxy URLs.
To fix this, implement a cache eviction strategy such as adding a periodic
cleanup goroutine to remove unused clients, or replace the sync.Map with an LRU
cache that limits size, or add reference counting to track client usage and
clean up when no longer needed. Choose one approach and modify the client
storage logic accordingly to prevent unbounded memory growth.

}