mirror of
https://github.com/qtgolang/SunnyNet.git
synced 2026-08-30 22:11:29 -07:00
2025-12-25
This commit is contained in:
@@ -226,11 +226,11 @@ func HTTPSendBin(Context int, data []byte) {
|
||||
random = public.GetTLSValues
|
||||
}
|
||||
k.respBody = nil
|
||||
resp, _, _, f := httpClient.Do(k.req, k.proxy, k.redirect, k.tlsConfig, time.Duration(k.outTime)*time.Millisecond, random, nil)
|
||||
|
||||
r := httpClient.Do(k.req, k.proxy, k.redirect, k.tlsConfig, time.Duration(k.outTime)*time.Millisecond, random, nil)
|
||||
//resp, _, _, f
|
||||
defer func() {
|
||||
if f != nil && resp != nil {
|
||||
f()
|
||||
if r.Close != nil && r.Response != nil {
|
||||
r.Close()
|
||||
}
|
||||
}()
|
||||
if k.resp != nil {
|
||||
@@ -238,7 +238,7 @@ func HTTPSendBin(Context int, data []byte) {
|
||||
_ = k.resp.Body.Close()
|
||||
}
|
||||
}
|
||||
k.resp = resp
|
||||
k.resp = r.Response
|
||||
if k.resp != nil {
|
||||
if k.resp.Body != nil {
|
||||
i, _ := io.ReadAll(k.resp.Body)
|
||||
|
||||
@@ -1040,17 +1040,13 @@ func (s *proxyRequest) doRequest() error {
|
||||
if s.Request.URL == nil {
|
||||
return errors.New("request.url is nil")
|
||||
}
|
||||
var do *http.Response
|
||||
var n net.Conn
|
||||
var err error
|
||||
var Close func()
|
||||
do, n, err, Close = httpClient.Do(s.Request, s.Proxy, false, s.TlsConfig, s.SendTimeout, s.getTLSValues, s.Conn)
|
||||
if err == nil && do != nil {
|
||||
r := httpClient.Do(s.Request, s.Proxy, false, s.TlsConfig, s.SendTimeout, s.getTLSValues, s.Conn)
|
||||
if r.Err == nil && r.Response != nil {
|
||||
if s.rawTarget != 0 {
|
||||
if s.Request.URL.Scheme != "https" {
|
||||
s.Global.cache.updateType(s.rawTarget, whoisNoHTTPS)
|
||||
} else {
|
||||
if do.ProtoMajor == 2 {
|
||||
if r.Response.ProtoMajor == 2 {
|
||||
s.Global.cache.updateType(s.rawTarget, whoisHTTPS2)
|
||||
} else {
|
||||
s.Global.cache.updateType(s.rawTarget, whoisHTTPS1)
|
||||
@@ -1058,16 +1054,16 @@ func (s *proxyRequest) doRequest() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Response.Conn = n
|
||||
s.Response.Conn = r.Conn
|
||||
ip, _ := s.Request.Context().Value(public.SunnyNetServerIpTags).(string)
|
||||
if ip != "" {
|
||||
s.Response.ServerIP = ip
|
||||
} else {
|
||||
s.Response.ServerIP = "unknown"
|
||||
}
|
||||
s.Response.Response = do
|
||||
s.Response.Close = Close
|
||||
return err
|
||||
s.Response.Response = r.Response
|
||||
s.Response.Close = r.Close
|
||||
return r.Err
|
||||
}
|
||||
func (s *proxyRequest) sendHttps(req *http.Request) {
|
||||
s.Target.Parse(req.Host, public.HttpsDefaultPort)
|
||||
@@ -1094,8 +1090,11 @@ func (s *proxyRequest) https() {
|
||||
}
|
||||
//是否开启了强制走TCP And 如果是DNS请求则不用判断了,直接强制走TCP
|
||||
if (s.Global.isMustTcp || s.Target.Port == 853) && !s.targetIsInterfaceAdders() {
|
||||
if s.Global.disableTCP {
|
||||
return
|
||||
|
||||
if !loop.IsFilterConn(s.Conn) {
|
||||
if s.Global.disableTCP {
|
||||
return
|
||||
}
|
||||
}
|
||||
s.NoRepairHttp = true
|
||||
//开启了强制走TCP,则按TCP流程处理
|
||||
@@ -2570,8 +2569,10 @@ func (s *Sunny) handleClientConn(conn net.Conn) {
|
||||
}
|
||||
}
|
||||
if s.isMustTcp && !req.targetIsInterfaceAdders() {
|
||||
if s.disableTCP {
|
||||
return
|
||||
if !loop.IsFilterConn(req.Conn) {
|
||||
if s.disableTCP {
|
||||
return
|
||||
}
|
||||
}
|
||||
//如果开启了强制走TCP ,则按TCP处理流程处理
|
||||
req.MustTcpProcessing(public.TagMustTCP)
|
||||
|
||||
@@ -3,16 +3,13 @@ package httpClient
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/qtgolang/SunnyNet/src/SunnyProxy"
|
||||
tls "github.com/qtgolang/SunnyNet/src/crypto/tls"
|
||||
"github.com/qtgolang/SunnyNet/src/dns"
|
||||
"github.com/qtgolang/SunnyNet/src/http"
|
||||
"github.com/qtgolang/SunnyNet/src/loop"
|
||||
"github.com/qtgolang/SunnyNet/src/public"
|
||||
@@ -36,178 +33,97 @@ func init() {
|
||||
}
|
||||
}()
|
||||
}
|
||||
func Do(req *http.Request, RequestProxy *SunnyProxy.Proxy, CheckRedirect bool, config *tls.Config, outTime time.Duration, GetTLSValues func() []uint16, MConn net.Conn) (Response *http.Response, Conn net.Conn, err error, Close func()) {
|
||||
if req.ProtoMajor == 2 {
|
||||
switch req.Method {
|
||||
case public.HttpMethodHEAD, public.HttpMethodGET, public.HttpMethodTRACE, public.HttpMethodOPTIONS:
|
||||
if req.Body != nil {
|
||||
_ = req.Body.Close()
|
||||
req.Body = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
if req != nil && req.Header != nil {
|
||||
Cookies := req.Header.GetArray("Cookie")
|
||||
if len(Cookies) > 1 {
|
||||
req.Header.Set("Cookie", strings.Join(Cookies, "; "))
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg := config.Clone()
|
||||
if req.URL != nil && req.URL.Scheme != "http" {
|
||||
if cfg == nil {
|
||||
cfg = &tls.Config{}
|
||||
}
|
||||
cfg.InsecureSkipVerify = true
|
||||
}
|
||||
_hashCode := public.SumHashCode(req.Host)
|
||||
_mustHTTP11_lock.Lock()
|
||||
if _mustHTTP11[_hashCode] != nil {
|
||||
cfg.NextProtos = public.HTTP1NextProtos
|
||||
x := time.Now()
|
||||
_mustHTTP11[_hashCode] = &x
|
||||
}
|
||||
_mustHTTP11_lock.Unlock()
|
||||
handshakeCount := 0
|
||||
for {
|
||||
if cfg != nil && GetTLSValues != nil {
|
||||
tv := GetTLSValues()
|
||||
if len(tv) > 0 {
|
||||
cfg.CipherSuites = tv
|
||||
}
|
||||
}
|
||||
Response, Conn, err, Close = do(req, RequestProxy, CheckRedirect, cfg, outTime, MConn)
|
||||
if err != nil {
|
||||
if Conn != nil {
|
||||
_ = Conn.Close()
|
||||
|
||||
func Do(req *http.Request, RequestProxy *SunnyProxy.Proxy, CheckRedirect bool, config *tls.Config, outTime time.Duration, GetTLSValues func() []uint16, MConn net.Conn) Result {
|
||||
return DoOptions(req, Options{
|
||||
RequestProxy: RequestProxy,
|
||||
CheckRedirect: CheckRedirect,
|
||||
TLSConfig: config,
|
||||
OutTime: outTime,
|
||||
GetTLSValues: GetTLSValues,
|
||||
MConn: MConn,
|
||||
})
|
||||
}
|
||||
|
||||
// DoOptions 优化参数/返回值与拆分函数
|
||||
func DoOptions(req *http.Request, opt Options) (r Result) {
|
||||
normalizeHTTP2Body(req) // HTTP/2 下清理不允许携带 Body 的方法
|
||||
normalizeCookieHeader(req) // 合并多个 Cookie 头为一个
|
||||
|
||||
cfg := buildTLSConfig(req, Options(opt)) // 克隆并构建 TLS 配置
|
||||
_hashCode := applyMustHTTP11(req.Host, cfg) // 根据 host 命中缓存时强制 HTTP/1.1
|
||||
|
||||
handshakeCount := 0 // 握手/连接类错误计数
|
||||
for { // 重试循环开始
|
||||
applyTLSValues(cfg, opt) // 动态设置 CipherSuites
|
||||
|
||||
dr := do(doArgs{ // 调用底层 do 执行一次请求
|
||||
req: req, // 请求对象
|
||||
RequestProxy: opt.RequestProxy, // 代理配置
|
||||
CheckRedirect: opt.CheckRedirect, // 是否允许重定向
|
||||
config: cfg, // TLS 配置
|
||||
outTime: opt.OutTime, // 超时时间
|
||||
MConn: opt.MConn, // 客户端连接
|
||||
Event: opt.Event,
|
||||
})
|
||||
r.Response, r.Conn, r.Err, r.Close = dr.resp, dr.conn, dr.err, dr.closeFn // 拆包结果
|
||||
|
||||
if r.Err != nil { // 如果请求出错
|
||||
closeConnOnErr(r.Conn) // 出错时关闭底层连接
|
||||
|
||||
if needDowngradeHTTP11(r.Err, cfg, _hashCode) { // HTTP2 stream error 降级 HTTP/1.1
|
||||
continue // 继续下一次重试
|
||||
}
|
||||
|
||||
ers := err.Error()
|
||||
|
||||
if strings.Contains(ers, "stream error: stream ID") && len(cfg.NextProtos) == 2 {
|
||||
cfg.NextProtos = public.HTTP1NextProtos
|
||||
_mustHTTP11_lock.Lock()
|
||||
x := time.Now()
|
||||
_mustHTTP11[_hashCode] = &x
|
||||
_mustHTTP11_lock.Unlock()
|
||||
continue
|
||||
shouldRetry, shouldReturn := handleRetryableHandshakeError(req, r.Err, &handshakeCount) // 处理握手/连接/EOF 错误
|
||||
if shouldReturn { // 达到最大重试次数
|
||||
r.Close = nil // 禁用 Close 回调
|
||||
return // 直接返回
|
||||
}
|
||||
|
||||
//Get "https://www.zjwubei.com:520/": stream error: stream ID 1; CANCEL; received from peer
|
||||
|
||||
if strings.Contains(ers, "handshake") || strings.Contains(ers, "connection") || strings.Contains(ers, "EOF") {
|
||||
handshakeCount++
|
||||
if handshakeCount > 10 {
|
||||
Close = nil
|
||||
return
|
||||
}
|
||||
if strings.Contains(ers, "EOF") && handshakeCount > 3 {
|
||||
if req.IsSetHTTP2Config() {
|
||||
req.SetHTTP2Config(nil)
|
||||
}
|
||||
}
|
||||
continue
|
||||
if shouldRetry { // 仍可重试
|
||||
continue // 进入下一轮
|
||||
}
|
||||
}
|
||||
return
|
||||
return // 成功或不可重试错误直接返回
|
||||
}
|
||||
}
|
||||
func do(req *http.Request, RequestProxy *SunnyProxy.Proxy, CheckRedirect bool, config *tls.Config, outTime time.Duration, MConn net.Conn) (*http.Response, net.Conn, error, func()) {
|
||||
if req != nil {
|
||||
if req.Header != nil {
|
||||
//请求时,删除协议头中的长度,请求时会自动添加
|
||||
ContentLengthName := ""
|
||||
var ContentLengthValue []string
|
||||
sName := "Content-Length"
|
||||
for k, v := range req.Header {
|
||||
if strings.EqualFold(k, sName) {
|
||||
ContentLengthName = k //保留原本的大小写名称
|
||||
ContentLengthValue = v
|
||||
break
|
||||
}
|
||||
}
|
||||
if ContentLengthName != "" {
|
||||
req.Header.Del(sName)
|
||||
defer func() {
|
||||
req.Header.Del(sName)
|
||||
req.Header.SetArray(sName, ContentLengthValue)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func do(a doArgs) (r doResult) {
|
||||
req := a.req // 请求对象
|
||||
RequestProxy := a.RequestProxy // 代理配置
|
||||
CheckRedirect := a.CheckRedirect // 是否允许重定向
|
||||
config := a.config // TLS 配置
|
||||
outTime := a.outTime // 超时时间
|
||||
MConn := a.MConn // 客户端连接
|
||||
|
||||
if restore := stripContentLengthHeader(req); restore != nil { // 移除 Content-Length 头
|
||||
defer restore() // 函数返回前恢复 Content-Length
|
||||
}
|
||||
SendTimeout := 30 * 1000 * time.Millisecond
|
||||
outTime = SendTimeout
|
||||
client := httpClientGet(req, RequestProxy, config, outTime)
|
||||
if CheckRedirect {
|
||||
client.Client.CheckRedirect = public.HTTPAllowRedirect
|
||||
} else {
|
||||
client.Client.CheckRedirect = public.HTTPBanRedirect
|
||||
if outTime < 100*time.Millisecond { // 超时时间过小
|
||||
outTime = 30 * time.Second // 使用默认 30 秒
|
||||
}
|
||||
if MConn != nil {
|
||||
//防止客户端与 SunnyNet 断开连接,但是 SunnyNet 与 目标服务器 一直交互
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
stop := make(chan struct{}) // 退出信号
|
||||
var mu sync.WaitGroup
|
||||
mu.Add(1) // 提前加 1,确保 Done() 被执行
|
||||
Cancel := req.WithCancel()
|
||||
go func() {
|
||||
defer mu.Done()
|
||||
ms := make([]byte, 1)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_ = MConn.SetReadDeadline(time.Now().Add(1 * time.Millisecond))
|
||||
_, er := MConn.Read(ms)
|
||||
if er != nil {
|
||||
if strings.Contains(er.Error(), "close") {
|
||||
if client.Conn != nil {
|
||||
Conn := client.Conn
|
||||
_ = Conn.Close()
|
||||
} else {
|
||||
Cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-stop: // 监听退出信号
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
close(stop)
|
||||
mu.Wait()
|
||||
_ = MConn.SetDeadline(time.Time{})
|
||||
}()
|
||||
|
||||
client := httpClientGet(req, RequestProxy, config, outTime, a.Event) // 获取 HTTP 客户端
|
||||
applyRedirectPolicy(client, CheckRedirect) // 设置重定向策略
|
||||
|
||||
cleanup := watchClientConn(req, client, MConn) // 启动客户端连接监控
|
||||
defer cleanup() // 函数返回时停止监控并清理
|
||||
|
||||
stripTEForHTTP2(client, req) // HTTP/2 场景下删除 TE 头
|
||||
|
||||
r.resp, r.err = client.Client.Do(req) // 发起 HTTP 请求
|
||||
if errors.Is(r.err, context.Canceled) { // 判断是否为取消错误
|
||||
r.err = httpCancel // 转换为内部取消错误
|
||||
}
|
||||
if client.h2 && req != nil {
|
||||
//部分HTTP2服务器不支持此协议头,导致出现协议错误
|
||||
req.Header.Del("TE")
|
||||
}
|
||||
reqs, err := client.Client.Do(req)
|
||||
if errors.Is(err, context.Canceled) {
|
||||
err = httpCancel
|
||||
}
|
||||
var rConn net.Conn
|
||||
if client.Conn != nil {
|
||||
rConn = client.Conn
|
||||
}
|
||||
address, proxy, _ := net.SplitHostPort(client.RequestProxy.DialAddr)
|
||||
if req != nil {
|
||||
ip := net.ParseIP(address)
|
||||
if ip == nil {
|
||||
req.SetContext(public.SunnyNetServerIpTags, client.RequestProxy.DialAddr)
|
||||
} else {
|
||||
req.SetContext(public.SunnyNetServerIpTags, SunnyProxy.FormatIP(ip, proxy))
|
||||
}
|
||||
}
|
||||
return reqs, rConn, err, func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
httpClientPop(client)
|
||||
|
||||
if client.Conn != nil { // 如果底层连接存在
|
||||
r.conn = client.Conn // 返回该连接
|
||||
}
|
||||
|
||||
setServerIPTag(req, client.RequestProxy.DialAddr) // 写入服务端 IP 信息到 context
|
||||
|
||||
r.closeFn = buildCloseFn(client, r.err) // 构建 Close 回调函数
|
||||
return // 返回 doResult
|
||||
}
|
||||
|
||||
var httpCancel = errors.New("客户端取消请求")
|
||||
@@ -216,237 +132,38 @@ var httpClientMap map[uint32]clientList
|
||||
|
||||
type clientList map[uintptr]*clientPart
|
||||
|
||||
func httpClientGet(req *http.Request, Proxy *SunnyProxy.Proxy, cfg *tls.Config, timeout time.Duration) *clientPart {
|
||||
func httpClientGet(req *http.Request, Proxy *SunnyProxy.Proxy, cfg *tls.Config, timeout time.Duration, event Event) *clientPart {
|
||||
httpLock.Lock()
|
||||
defer httpLock.Unlock()
|
||||
outRouterIP, _ := req.Context().Value(public.OutRouterIPKey).(*net.TCPAddr)
|
||||
s := dns.GetDnsServer()
|
||||
if outRouterIP != nil {
|
||||
s += outRouterIP.String() + "|"
|
||||
} else {
|
||||
s += "|"
|
||||
}
|
||||
if req != nil && req.URL != nil {
|
||||
s += req.URL.Host + "|" + req.Proto + "|" + req.URL.Scheme
|
||||
}
|
||||
s += "|" + Proxy.String() + "|"
|
||||
if cfg != nil {
|
||||
if len(cfg.NextProtos) < 1 {
|
||||
cfg.NextProtos = []string{http.H11Proto, http.H2Proto}
|
||||
}
|
||||
s += strings.Join(cfg.NextProtos, "-")
|
||||
}
|
||||
hash := public.SumHashCode(s)
|
||||
if clients, ok := httpClientMap[hash]; ok {
|
||||
if len(clients) > 0 {
|
||||
for key, client := range clients {
|
||||
delete(clients, key)
|
||||
var nproxy *SunnyProxy.Proxy
|
||||
if Proxy != nil {
|
||||
nproxy = Proxy.Clone()
|
||||
} else {
|
||||
nproxy = new(SunnyProxy.Proxy)
|
||||
}
|
||||
if client.RequestProxy != nil {
|
||||
nproxy.DialAddr = client.RequestProxy.DialAddr
|
||||
}
|
||||
client.RequestProxy = nproxy
|
||||
if client.Conn != nil {
|
||||
Conn := client.Conn
|
||||
if timeout == 0 {
|
||||
_ = Conn.SetDeadline(time.Time{})
|
||||
_ = Conn.SetWriteDeadline(time.Time{})
|
||||
_ = Conn.SetDeadline(time.Time{})
|
||||
} else {
|
||||
_ = Conn.SetDeadline(time.Now().Add(timeout))
|
||||
_ = Conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_ = Conn.SetDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
client.Client.Timeout = 24 * time.Hour
|
||||
client.Transport.ResponseHeaderTimeout = 24 * time.Hour // 读取响应头超时
|
||||
client.Transport.IdleConnTimeout = 24 * time.Hour // 空闲连接超时
|
||||
client.Transport.TLSHandshakeTimeout = 24 * time.Hour // TLS 握手超时
|
||||
}
|
||||
return client
|
||||
}
|
||||
}
|
||||
}
|
||||
if cfg != nil {
|
||||
if len(cfg.NextProtos) > 0 {
|
||||
cfg.GetConfigForServer = func(info *tls.ServerHelloMsg) error {
|
||||
for _, proto := range cfg.NextProtos {
|
||||
if proto == http.H2Proto && info.SupportedVersion == 772 {
|
||||
return nil // 如果支持,则返回 nil
|
||||
}
|
||||
if proto == http.H11Proto && (info.SupportedVersion == 0 || info.Vers == 771) {
|
||||
return nil // 如果支持,则返回 nil
|
||||
}
|
||||
}
|
||||
ver := info.SupportedVersion
|
||||
if ver == 0 {
|
||||
ver = info.Vers
|
||||
}
|
||||
Proto, _ := http.ProtoVersions[info.Vers]
|
||||
if Proto == "" {
|
||||
return fmt.Errorf("服务器不支持您所选HTTP协议版本")
|
||||
}
|
||||
return fmt.Errorf("服务器不支持您所选HTTP协议版本: 需要协议[%s],请检查您的配置", strings.ToUpper(Proto))
|
||||
}
|
||||
}
|
||||
}
|
||||
Tr := &http.Transport{TLSClientConfig: cfg}
|
||||
if timeout == 0 {
|
||||
Tr.ResponseHeaderTimeout = 60 * time.Second // 读取响应头超时
|
||||
Tr.IdleConnTimeout = 60 * time.Second // 空闲连接超时
|
||||
Tr.TLSHandshakeTimeout = 60 * time.Second // TLS 握手超时
|
||||
} else {
|
||||
Tr.ResponseHeaderTimeout = timeout // 读取响应头超时
|
||||
Tr.IdleConnTimeout = timeout // 空闲连接超时
|
||||
Tr.TLSHandshakeTimeout = timeout // TLS 握手超时
|
||||
}
|
||||
h2 := false
|
||||
if cfg != nil {
|
||||
if len(cfg.NextProtos) < 1 {
|
||||
configureHTTP2Transport(Tr, cfg)
|
||||
h2 = true
|
||||
} else {
|
||||
for _, proto := range cfg.NextProtos {
|
||||
if proto == http.H2Proto {
|
||||
configureHTTP2Transport(Tr, cfg)
|
||||
h2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var ips []net.IP
|
||||
var isLookupIP bool
|
||||
var ProxyHost string
|
||||
var LookupIPdial func(network string, addr string, OutRouterIP *net.TCPAddr) (net.Conn, error)
|
||||
var nproxy *SunnyProxy.Proxy
|
||||
var LookupIPproxy *SunnyProxy.Proxy
|
||||
if Proxy != nil {
|
||||
nproxy = Proxy.Clone()
|
||||
LookupIPproxy = Proxy.Clone()
|
||||
LookupIPdial = LookupIPproxy.Dial
|
||||
ProxyHost = Proxy.Host
|
||||
} else {
|
||||
nproxy = new(SunnyProxy.Proxy)
|
||||
LookupIPdial = LookupIPproxy.Dial
|
||||
}
|
||||
cc := http.Client{Transport: Tr, Timeout: timeout}
|
||||
res := &clientPart{Client: cc, key: hash, RequestProxy: nproxy, Transport: Tr, h2: h2}
|
||||
if outRouterIP != nil {
|
||||
res.outRouterIP = &net.TCPAddr{IP: outRouterIP.IP}
|
||||
}
|
||||
Tr.DialContext = func(ctx context.Context, network, addr string) (cnn net.Conn, _ error) {
|
||||
defer func() {
|
||||
if cnn != nil {
|
||||
res.Conn = cnn
|
||||
loop.Add(cnn)
|
||||
if timeout != 0 {
|
||||
_ = cnn.SetDeadline(time.Now().Add(timeout))
|
||||
_ = cnn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_ = cnn.SetDeadline(time.Now().Add(timeout))
|
||||
} else {
|
||||
_ = cnn.SetDeadline(time.Time{})
|
||||
_ = cnn.SetWriteDeadline(time.Time{})
|
||||
_ = cnn.SetDeadline(time.Time{})
|
||||
}
|
||||
Tr.ResponseHeaderTimeout = 24 * time.Hour // 读取响应头超时
|
||||
Tr.IdleConnTimeout = 24 * time.Hour // 空闲连接超时
|
||||
Tr.TLSHandshakeTimeout = 24 * time.Hour // TLS 握手超时
|
||||
cc.Timeout = 24 * time.Hour
|
||||
}
|
||||
}()
|
||||
serveripFunc, ok := req.Context().Value(public.Connect_Raw_Address).(func() string)
|
||||
if ok && serveripFunc != nil {
|
||||
_serverIP_ := serveripFunc()
|
||||
if _serverIP_ != "" {
|
||||
address2, _, err2 := net.SplitHostPort(_serverIP_)
|
||||
if err2 == nil {
|
||||
ip := net.ParseIP(address2)
|
||||
if ip != nil {
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, _serverIP_, 3*time.Second, res.outRouterIP)
|
||||
if conn != nil {
|
||||
return conn, er
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if dns.IsRemoteDnsServer() {
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, addr, 3*time.Second, res.outRouterIP)
|
||||
return conn, er
|
||||
}
|
||||
address, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := net.ParseIP(address)
|
||||
if i != nil {
|
||||
if len(i) == net.IPv4len {
|
||||
return res.RequestProxy.Dial(network, i.String()+":"+port, res.outRouterIP)
|
||||
}
|
||||
return res.RequestProxy.Dial(network, fmt.Sprintf("[%s]:%s", address, port), res.outRouterIP)
|
||||
}
|
||||
if strings.ToLower(address) == "localhost" {
|
||||
return res.RequestProxy.Dial(network, "127.0.0.1:"+port, res.outRouterIP)
|
||||
}
|
||||
var retries bool
|
||||
for {
|
||||
if !isLookupIP {
|
||||
isLookupIP = true
|
||||
first := dns.GetFirstIP(address, ProxyHost)
|
||||
if first != nil {
|
||||
if first.To4() != nil {
|
||||
return res.RequestProxy.Dial(network, fmt.Sprintf("%s:%s", first.String(), port), res.outRouterIP)
|
||||
} else {
|
||||
return res.RequestProxy.Dial(network, fmt.Sprintf("[%s]:%s", first.String(), port), res.outRouterIP)
|
||||
}
|
||||
}
|
||||
ips, _ = dns.LookupIP(address, ProxyHost, res.outRouterIP, LookupIPdial)
|
||||
if len(ips) < 1 {
|
||||
return nil, noIP
|
||||
}
|
||||
}
|
||||
if len(ips) < 1 {
|
||||
dns.SetFirstIP(address, ProxyHost, nil)
|
||||
if retries {
|
||||
return nil, connectionFailed
|
||||
}
|
||||
isLookupIP = false
|
||||
retries = true
|
||||
continue
|
||||
}
|
||||
var AllLocalIP = true
|
||||
for _, ip := range ips {
|
||||
if ip.String() != "127.0.0.1" {
|
||||
AllLocalIP = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if AllLocalIP && len(ips) != 0 {
|
||||
return nil, errors.New(fmt.Sprintf("Address [%s] points to 127.0.0.1", address))
|
||||
}
|
||||
ip := extractAndRemoveIP(&ips)
|
||||
if ip != nil && ip.String() != "127.0.0.1" {
|
||||
if ip.To4() != nil {
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, fmt.Sprintf("%s:%s", ip.String(), port), 2*time.Second, res.outRouterIP)
|
||||
if conn != nil {
|
||||
dns.SetFirstIP(address, ProxyHost, ip)
|
||||
return conn, er
|
||||
}
|
||||
continue
|
||||
}
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, fmt.Sprintf("[%s]:%s", ip.String(), port), 2*time.Second, res.outRouterIP)
|
||||
if conn != nil {
|
||||
dns.SetFirstIP(address, ProxyHost, ip)
|
||||
return conn, er
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outRouterIP := getOutRouterIP(req) //从上下文取出出口路由IP
|
||||
keyStr := buildHTTPClientKey(req, Proxy, cfg, outRouterIP) //构建客户端缓存key字符串
|
||||
hash := public.SumHashCode(keyStr) //计算hash
|
||||
|
||||
if c := tryPopCachedClient(hash, Proxy, timeout); c != nil { //尝试从缓存取出可复用客户端
|
||||
return c
|
||||
}
|
||||
|
||||
applyTLSProtoCheck(cfg) //设置TLS协议版本校验逻辑
|
||||
|
||||
Tr := newTransport(cfg, timeout) //创建Transport并设置超时
|
||||
h2 := configureH2IfNeeded(Tr, cfg) //按NextProtos配置HTTP2
|
||||
nproxy, lookupProxy, lookupDial, proxyHost := buildProxySet(Proxy) //构建代理对象与LookupIP拨号器
|
||||
|
||||
cc := http.Client{Transport: Tr, Timeout: timeout} //创建HTTP客户端
|
||||
res := newClientPart(cc, Tr, hash, nproxy, h2, outRouterIP) //创建clientPart
|
||||
|
||||
bindDialContext(dialCtxArgs{
|
||||
Tr: Tr,
|
||||
Req: req,
|
||||
Res: res,
|
||||
Timeout: timeout,
|
||||
Event: event,
|
||||
ProxyHost: proxyHost,
|
||||
LookupDial: lookupDial,
|
||||
})
|
||||
_ = lookupProxy //保持原有变量结构,不改变逻辑意图
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -484,7 +201,7 @@ func configureHTTP2Transport(Tr *http.Transport, cfg *tls.Config) {
|
||||
|
||||
// 如果找到了 HTTP/2.0 协议,则配置 HTTP/2.0 传输
|
||||
if protoFound || len(cfg.NextProtos) == 0 {
|
||||
http.HTTP2configureTransport(Tr)
|
||||
_, _ = http.HTTP2configureTransport(Tr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
636
src/httpClient/utlis.go
Normal file
636
src/httpClient/utlis.go
Normal file
@@ -0,0 +1,636 @@
|
||||
package httpClient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qtgolang/SunnyNet/src/SunnyProxy"
|
||||
"github.com/qtgolang/SunnyNet/src/crypto/tls"
|
||||
"github.com/qtgolang/SunnyNet/src/dns"
|
||||
"github.com/qtgolang/SunnyNet/src/http"
|
||||
"github.com/qtgolang/SunnyNet/src/loop"
|
||||
"github.com/qtgolang/SunnyNet/src/public"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Connection func(conn net.Conn)
|
||||
Read func(conn net.Conn, bs []byte)
|
||||
Write func(conn net.Conn, bs []byte)
|
||||
Close func(conn net.Conn)
|
||||
}
|
||||
type hConn struct {
|
||||
net.Conn
|
||||
Event
|
||||
}
|
||||
|
||||
func (h hConn) Read(b []byte) (n int, err error) {
|
||||
n, e := h.Conn.Read(b)
|
||||
if h.Event.Read != nil {
|
||||
h.Event.Read(h.Conn, b[:n])
|
||||
}
|
||||
return n, e
|
||||
}
|
||||
|
||||
func (h hConn) Write(b []byte) (n int, err error) {
|
||||
if h.Event.Write != nil {
|
||||
h.Event.Write(h.Conn, b)
|
||||
}
|
||||
return h.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (h hConn) Close() error {
|
||||
if h.Event.Close != nil {
|
||||
h.Event.Close(h.Conn)
|
||||
}
|
||||
return h.Conn.Close()
|
||||
}
|
||||
|
||||
func (h hConn) LocalAddr() net.Addr {
|
||||
return h.Conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (h hConn) RemoteAddr() net.Addr {
|
||||
return h.Conn.RemoteAddr()
|
||||
}
|
||||
|
||||
func newHConn(event Event, conn net.Conn) net.Conn {
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
if event.Connection != nil {
|
||||
event.Connection(conn)
|
||||
}
|
||||
return &hConn{Event: event, Conn: conn}
|
||||
}
|
||||
|
||||
// Options 收敛DoOptions的参数
|
||||
type Options struct {
|
||||
RequestProxy *SunnyProxy.Proxy //代理配置
|
||||
CheckRedirect bool //是否允许重定向
|
||||
TLSConfig *tls.Config //TLS配置
|
||||
OutTime time.Duration //超时(注意: do里会强制覆盖成30s)
|
||||
GetTLSValues func() []uint16 //动态CipherSuites
|
||||
MConn net.Conn //客户端连接(用于探测断开)
|
||||
Event Event
|
||||
}
|
||||
|
||||
// Result 收敛DoOptions的返回值
|
||||
type Result struct {
|
||||
Response *http.Response //响应
|
||||
Conn net.Conn //底层连接
|
||||
Err error //错误
|
||||
Close func() //成功时归还连接池
|
||||
}
|
||||
|
||||
// doArgs 收敛do的参数
|
||||
type doArgs struct {
|
||||
req *http.Request //请求
|
||||
RequestProxy *SunnyProxy.Proxy //代理配置
|
||||
CheckRedirect bool //是否允许重定向
|
||||
config *tls.Config //TLS配置
|
||||
outTime time.Duration //超时(会在do里覆盖)
|
||||
MConn net.Conn //客户端连接
|
||||
Event Event
|
||||
}
|
||||
|
||||
// doResult 收敛do的返回值
|
||||
type doResult struct {
|
||||
resp *http.Response //响应
|
||||
conn net.Conn //连接
|
||||
err error //错误
|
||||
closeFn func() //成功时归还
|
||||
}
|
||||
|
||||
// 移除Content-Length并在defer中恢复(保持原逻辑)
|
||||
func stripContentLengthHeader(req *http.Request) func() {
|
||||
if req == nil || req.Header == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ContentLengthName := ""
|
||||
var ContentLengthValue []string
|
||||
sName := "Content-Length"
|
||||
|
||||
for k, v := range req.Header {
|
||||
if strings.EqualFold(k, sName) {
|
||||
ContentLengthName = k //保留原本的大小写名称
|
||||
ContentLengthValue = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ContentLengthName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
req.Header.Del(sName)
|
||||
return func() {
|
||||
req.Header.Del(sName)
|
||||
req.Header.SetArray(sName, ContentLengthValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置重定向策略(保持原逻辑)
|
||||
func applyRedirectPolicy(client *clientPart, checkRedirect bool) {
|
||||
if checkRedirect {
|
||||
client.Client.CheckRedirect = public.HTTPAllowRedirect
|
||||
} else {
|
||||
client.Client.CheckRedirect = public.HTTPBanRedirect
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP2下删除TE头(保持原逻辑)
|
||||
func stripTEForHTTP2(client *clientPart, req *http.Request) {
|
||||
if client.h2 && req != nil {
|
||||
req.Header.Del("TE")
|
||||
}
|
||||
}
|
||||
|
||||
// 写入SunnyNetServerIpTags(保持原逻辑)
|
||||
func setServerIPTag(req *http.Request, dialAddr string) {
|
||||
address, proxy, _ := net.SplitHostPort(dialAddr)
|
||||
if req != nil {
|
||||
ip := net.ParseIP(address)
|
||||
if ip == nil {
|
||||
req.SetContext(public.SunnyNetServerIpTags, dialAddr)
|
||||
} else {
|
||||
req.SetContext(public.SunnyNetServerIpTags, SunnyProxy.FormatIP(ip, proxy))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监控MConn断开并清理资源(保持原逻辑)
|
||||
func watchClientConn(req *http.Request, client *clientPart, MConn net.Conn) func() {
|
||||
if MConn == nil {
|
||||
return func() {}
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
stop := make(chan struct{}) // 退出信号
|
||||
var mu sync.WaitGroup
|
||||
mu.Add(1) // 提前加 1,确保 Done() 被执行
|
||||
Cancel := req.WithCancel()
|
||||
|
||||
go func() {
|
||||
defer mu.Done()
|
||||
ms := make([]byte, 1)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_ = MConn.SetReadDeadline(time.Now().Add(1 * time.Millisecond))
|
||||
_, er := MConn.Read(ms)
|
||||
if er != nil {
|
||||
if strings.Contains(er.Error(), "close") {
|
||||
if client.Conn != nil {
|
||||
Conn := client.Conn
|
||||
_ = Conn.Close()
|
||||
} else {
|
||||
Cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-stop: // 监听退出信号
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
ticker.Stop()
|
||||
close(stop)
|
||||
mu.Wait()
|
||||
_ = MConn.SetDeadline(time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
// 构建do的closeFn(保持原逻辑)
|
||||
func buildCloseFn(client *clientPart, err error) func() {
|
||||
return func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
httpClientPop(client)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理HTTP2下某些方法的Body清理
|
||||
func normalizeHTTP2Body(req *http.Request) {
|
||||
if req.ProtoMajor == 2 {
|
||||
switch req.Method {
|
||||
case public.HttpMethodHEAD, public.HttpMethodGET, public.HttpMethodTRACE, public.HttpMethodOPTIONS:
|
||||
if req.Body != nil {
|
||||
_ = req.Body.Close()
|
||||
req.Body = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 合并重复的Cookie头
|
||||
func normalizeCookieHeader(req *http.Request) {
|
||||
if req != nil && req.Header != nil {
|
||||
Cookies := req.Header.GetArray("Cookie")
|
||||
if len(Cookies) > 1 {
|
||||
req.Header.Set("Cookie", strings.Join(Cookies, "; "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 克隆并按Scheme调整TLS配置
|
||||
func buildTLSConfig(req *http.Request, opt Options) *tls.Config {
|
||||
cfg := opt.TLSConfig.Clone()
|
||||
if req.URL != nil && req.URL.Scheme != "http" {
|
||||
if cfg == nil {
|
||||
cfg = &tls.Config{}
|
||||
}
|
||||
cfg.InsecureSkipVerify = true
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// 按mustHTTP11缓存强制HTTP/1.1并刷新时间
|
||||
func applyMustHTTP11(host string, cfg *tls.Config) uint32 {
|
||||
_hashCode := public.SumHashCode(host)
|
||||
_mustHTTP11_lock.Lock()
|
||||
if _mustHTTP11[_hashCode] != nil {
|
||||
cfg.NextProtos = public.HTTP1NextProtos
|
||||
x := time.Now()
|
||||
_mustHTTP11[_hashCode] = &x
|
||||
}
|
||||
_mustHTTP11_lock.Unlock()
|
||||
return _hashCode
|
||||
}
|
||||
|
||||
// 每次循环动态刷新CipherSuites
|
||||
func applyTLSValues(cfg *tls.Config, opt Options) {
|
||||
if cfg != nil && opt.GetTLSValues != nil {
|
||||
tv := opt.GetTLSValues()
|
||||
if len(tv) > 0 {
|
||||
cfg.CipherSuites = tv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理错误:是否需要HTTP2->HTTP/1.1降级并重试
|
||||
func needDowngradeHTTP11(err error, cfg *tls.Config, hashCode uint32) bool {
|
||||
ers := err.Error()
|
||||
if strings.Contains(ers, "stream error: stream ID") && len(cfg.NextProtos) == 2 {
|
||||
cfg.NextProtos = public.HTTP1NextProtos
|
||||
_mustHTTP11_lock.Lock()
|
||||
x := time.Now()
|
||||
_mustHTTP11[hashCode] = &x
|
||||
_mustHTTP11_lock.Unlock()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理握手/连接/EOF类错误:是否需要继续重试,以及是否清理HTTP2Config
|
||||
func handleRetryableHandshakeError(req *http.Request, err error, handshakeCount *int) (shouldRetry bool, shouldReturn bool) {
|
||||
ers := err.Error()
|
||||
if strings.Contains(ers, "handshake") || strings.Contains(ers, "connection") || strings.Contains(ers, "EOF") {
|
||||
*handshakeCount++
|
||||
if *handshakeCount > 10 {
|
||||
return false, true
|
||||
}
|
||||
if strings.Contains(ers, "EOF") && *handshakeCount > 3 {
|
||||
if req.IsSetHTTP2Config() {
|
||||
req.SetHTTP2Config(nil)
|
||||
}
|
||||
}
|
||||
return true, false
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// 请求失败时关闭连接
|
||||
func closeConnOnErr(conn net.Conn) {
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
func getOutRouterIP(req *http.Request) *net.TCPAddr { //读取出口路由IP
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
outRouterIP, _ := req.Context().Value(public.OutRouterIPKey).(*net.TCPAddr)
|
||||
return outRouterIP
|
||||
}
|
||||
|
||||
func buildHTTPClientKey(req *http.Request, Proxy *SunnyProxy.Proxy, cfg *tls.Config, outRouterIP *net.TCPAddr) string { //构建缓存key
|
||||
s := dns.GetDnsServer()
|
||||
if outRouterIP != nil {
|
||||
s += outRouterIP.String() + "|"
|
||||
} else {
|
||||
s += "|"
|
||||
}
|
||||
if req != nil && req.URL != nil {
|
||||
s += req.URL.Host + "|" + req.Proto + "|" + req.URL.Scheme
|
||||
}
|
||||
s += "|" + Proxy.String() + "|"
|
||||
if cfg != nil {
|
||||
if len(cfg.NextProtos) < 1 {
|
||||
cfg.NextProtos = []string{http.H11Proto, http.H2Proto}
|
||||
}
|
||||
s += strings.Join(cfg.NextProtos, "-")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func tryPopCachedClient(hash uint32, Proxy *SunnyProxy.Proxy, timeout time.Duration) *clientPart { //从缓存取client
|
||||
if clients, ok := httpClientMap[hash]; ok {
|
||||
if len(clients) > 0 {
|
||||
for key, client := range clients {
|
||||
delete(clients, key)
|
||||
refreshClientProxy(client, Proxy) //刷新代理参数
|
||||
refreshClientConnDeadline(client, timeout) //刷新连接deadline与transport超时
|
||||
return client
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func refreshClientProxy(client *clientPart, Proxy *SunnyProxy.Proxy) { //刷新RequestProxy
|
||||
var nproxy *SunnyProxy.Proxy
|
||||
if Proxy != nil {
|
||||
nproxy = Proxy.Clone()
|
||||
} else {
|
||||
nproxy = new(SunnyProxy.Proxy)
|
||||
}
|
||||
if client.RequestProxy != nil {
|
||||
nproxy.DialAddr = client.RequestProxy.DialAddr
|
||||
}
|
||||
client.RequestProxy = nproxy
|
||||
}
|
||||
|
||||
func refreshClientConnDeadline(client *clientPart, timeout time.Duration) { //刷新连接deadline与transport超时
|
||||
if client.Conn != nil {
|
||||
Conn := client.Conn
|
||||
if timeout == 0 {
|
||||
_ = Conn.SetDeadline(time.Time{})
|
||||
_ = Conn.SetWriteDeadline(time.Time{})
|
||||
_ = Conn.SetDeadline(time.Time{})
|
||||
} else {
|
||||
_ = Conn.SetDeadline(time.Now().Add(timeout))
|
||||
_ = Conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_ = Conn.SetDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
client.Client.Timeout = 24 * time.Hour
|
||||
client.Transport.ResponseHeaderTimeout = 24 * time.Hour // 读取响应头超时
|
||||
client.Transport.IdleConnTimeout = 24 * time.Hour // 空闲连接超时
|
||||
client.Transport.TLSHandshakeTimeout = 24 * time.Hour // TLS 握手超时
|
||||
}
|
||||
}
|
||||
|
||||
func applyTLSProtoCheck(cfg *tls.Config) { //设置TLS协议版本校验
|
||||
if cfg != nil {
|
||||
if len(cfg.NextProtos) > 0 {
|
||||
cfg.GetConfigForServer = func(info *tls.ServerHelloMsg) error {
|
||||
for _, proto := range cfg.NextProtos {
|
||||
if proto == http.H2Proto && info.SupportedVersion == 772 {
|
||||
return nil // 如果支持,则返回 nil
|
||||
}
|
||||
if proto == http.H11Proto && (info.SupportedVersion == 0 || info.Vers == 771) {
|
||||
return nil // 如果支持,则返回 nil
|
||||
}
|
||||
}
|
||||
ver := info.SupportedVersion
|
||||
if ver == 0 {
|
||||
ver = info.Vers
|
||||
}
|
||||
Proto, _ := http.ProtoVersions[info.Vers]
|
||||
if Proto == "" {
|
||||
return fmt.Errorf("服务器不支持您所选HTTP协议版本")
|
||||
}
|
||||
return fmt.Errorf("服务器不支持您所选HTTP协议版本: 需要协议[%s],请检查您的配置", strings.ToUpper(Proto))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTransport(cfg *tls.Config, timeout time.Duration) *http.Transport { //创建Transport并设置超时
|
||||
Tr := &http.Transport{TLSClientConfig: cfg}
|
||||
if timeout == 0 {
|
||||
Tr.ResponseHeaderTimeout = 60 * time.Second // 读取响应头超时
|
||||
Tr.IdleConnTimeout = 60 * time.Second // 空闲连接超时
|
||||
Tr.TLSHandshakeTimeout = 60 * time.Second // TLS 握手超时
|
||||
} else {
|
||||
Tr.ResponseHeaderTimeout = timeout // 读取响应头超时
|
||||
Tr.IdleConnTimeout = timeout // 空闲连接超时
|
||||
Tr.TLSHandshakeTimeout = timeout // TLS 握手超时
|
||||
}
|
||||
return Tr
|
||||
}
|
||||
|
||||
func configureH2IfNeeded(Tr *http.Transport, cfg *tls.Config) bool { //按NextProtos配置HTTP2
|
||||
h2 := false
|
||||
if cfg != nil {
|
||||
if len(cfg.NextProtos) < 1 {
|
||||
configureHTTP2Transport(Tr, cfg)
|
||||
h2 = true
|
||||
} else {
|
||||
for _, proto := range cfg.NextProtos {
|
||||
if proto == http.H2Proto {
|
||||
configureHTTP2Transport(Tr, cfg)
|
||||
h2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return h2
|
||||
}
|
||||
|
||||
func buildProxySet(Proxy *SunnyProxy.Proxy) (nproxy *SunnyProxy.Proxy, lookupProxy *SunnyProxy.Proxy, lookupDial func(network string, addr string, OutRouterIP *net.TCPAddr) (net.Conn, error), proxyHost string) { //构建代理与LookupIP拨号
|
||||
if Proxy != nil {
|
||||
nproxy = Proxy.Clone()
|
||||
lookupProxy = Proxy.Clone()
|
||||
lookupDial = lookupProxy.Dial
|
||||
proxyHost = Proxy.Host
|
||||
} else {
|
||||
nproxy = new(SunnyProxy.Proxy)
|
||||
lookupDial = lookupProxy.Dial
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newClientPart(cc http.Client, Tr *http.Transport, hash uint32, nproxy *SunnyProxy.Proxy, h2 bool, outRouterIP *net.TCPAddr) *clientPart { //创建clientPart
|
||||
res := &clientPart{Client: cc, key: hash, RequestProxy: nproxy, Transport: Tr, h2: h2}
|
||||
if outRouterIP != nil {
|
||||
res.outRouterIP = &net.TCPAddr{IP: outRouterIP.IP}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// dialCtxArgs 收敛bindDialContext入参
|
||||
type dialCtxArgs struct {
|
||||
Tr *http.Transport //Transport对象
|
||||
Req *http.Request //请求对象
|
||||
Res *clientPart //客户端结构
|
||||
Timeout time.Duration //超时
|
||||
Event Event //事件回调
|
||||
ProxyHost string //代理Host
|
||||
LookupDial func(network string, addr string, OutRouterIP *net.TCPAddr) (net.Conn, error) //DNS回源拨号
|
||||
}
|
||||
|
||||
// bindDialContext 参数收敛版(不改内部逻辑)
|
||||
func bindDialContext(a dialCtxArgs) { //绑定拨号逻辑
|
||||
var ips []net.IP
|
||||
var isLookupIP bool
|
||||
var retries bool
|
||||
|
||||
Tr := a.Tr
|
||||
req := a.Req
|
||||
res := a.Res
|
||||
timeout := a.Timeout
|
||||
event := a.Event
|
||||
proxyHost := a.ProxyHost
|
||||
lookupDial := a.LookupDial
|
||||
|
||||
Tr.DialContext = func(ctx context.Context, network, addr string) (cnn net.Conn, _ error) {
|
||||
defer func() {
|
||||
if cnn != nil {
|
||||
attachConnAndTimeout(res, Tr, &res.Client, cnn, timeout) //绑定连接与超时设置
|
||||
}
|
||||
}()
|
||||
|
||||
if conn, er, ok := tryDialRawServerIP(req, res, network, event); ok { //优先按上下文指定IP连接
|
||||
return conn, er
|
||||
}
|
||||
|
||||
if dns.IsRemoteDnsServer() { //远程DNS直接拨号
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, addr, 3*time.Second, res.outRouterIP)
|
||||
return newHConn(event, conn), er
|
||||
}
|
||||
|
||||
address, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conn, er, ok := tryDialDirectIP(res, network, address, port, event); ok { //addr就是IP时直接拨号
|
||||
return conn, er
|
||||
}
|
||||
|
||||
if strings.ToLower(address) == "localhost" { //localhost强制走127.0.0.1
|
||||
r, e := res.RequestProxy.Dial(network, "127.0.0.1:"+port, res.outRouterIP)
|
||||
return newHConn(event, r), e
|
||||
}
|
||||
|
||||
for { //域名解析后尝试多IP
|
||||
if !isLookupIP {
|
||||
isLookupIP = true
|
||||
first := dns.GetFirstIP(address, proxyHost)
|
||||
if first != nil {
|
||||
if first.To4() != nil {
|
||||
r, e := res.RequestProxy.Dial(network, fmt.Sprintf("%s:%s", first.String(), port), res.outRouterIP)
|
||||
return newHConn(event, r), e
|
||||
} else {
|
||||
r, e := res.RequestProxy.Dial(network, fmt.Sprintf("[%s]:%s", first.String(), port), res.outRouterIP)
|
||||
return newHConn(event, r), e
|
||||
}
|
||||
}
|
||||
ips, _ = dns.LookupIP(address, proxyHost, res.outRouterIP, lookupDial)
|
||||
if len(ips) < 1 {
|
||||
return nil, noIP
|
||||
}
|
||||
}
|
||||
|
||||
if len(ips) < 1 {
|
||||
dns.SetFirstIP(address, proxyHost, nil)
|
||||
if retries {
|
||||
return nil, connectionFailed
|
||||
}
|
||||
isLookupIP = false
|
||||
retries = true
|
||||
continue
|
||||
}
|
||||
|
||||
if allLocal127(ips) && len(ips) != 0 {
|
||||
return nil, errors.New(fmt.Sprintf("Address [%s] points to 127.0.0.1", address))
|
||||
}
|
||||
|
||||
ip := extractAndRemoveIP(&ips)
|
||||
if ip != nil && ip.String() != "127.0.0.1" {
|
||||
if ip.To4() != nil {
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, fmt.Sprintf("%s:%s", ip.String(), port), 2*time.Second, res.outRouterIP)
|
||||
if conn != nil {
|
||||
dns.SetFirstIP(address, proxyHost, ip)
|
||||
return newHConn(event, conn), er
|
||||
}
|
||||
continue
|
||||
}
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, fmt.Sprintf("[%s]:%s", ip.String(), port), 2*time.Second, res.outRouterIP)
|
||||
if conn != nil {
|
||||
dns.SetFirstIP(address, proxyHost, ip)
|
||||
return newHConn(event, conn), er
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func attachConnAndTimeout(res *clientPart, Tr *http.Transport, cc *http.Client, cnn net.Conn, timeout time.Duration) { //连接成功后设置超时并缓存
|
||||
res.Conn = cnn
|
||||
loop.Add(cnn)
|
||||
if timeout != 0 {
|
||||
_ = cnn.SetDeadline(time.Now().Add(timeout))
|
||||
_ = cnn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
_ = cnn.SetDeadline(time.Now().Add(timeout))
|
||||
} else {
|
||||
_ = cnn.SetDeadline(time.Time{})
|
||||
_ = cnn.SetWriteDeadline(time.Time{})
|
||||
_ = cnn.SetDeadline(time.Time{})
|
||||
}
|
||||
Tr.ResponseHeaderTimeout = 24 * time.Hour // 读取响应头超时
|
||||
Tr.IdleConnTimeout = 24 * time.Hour // 空闲连接超时
|
||||
Tr.TLSHandshakeTimeout = 24 * time.Hour // TLS 握手超时
|
||||
cc.Timeout = 24 * time.Hour
|
||||
}
|
||||
|
||||
func tryDialRawServerIP(req *http.Request, res *clientPart, network string, event Event) (net.Conn, error, bool) { //按上下文指定IP连接
|
||||
serveripFunc, ok := req.Context().Value(public.Connect_Raw_Address).(func() string)
|
||||
if ok && serveripFunc != nil {
|
||||
_serverIP_ := serveripFunc()
|
||||
if _serverIP_ != "" {
|
||||
address2, _, err2 := net.SplitHostPort(_serverIP_)
|
||||
if err2 == nil {
|
||||
ip := net.ParseIP(address2)
|
||||
if ip != nil {
|
||||
conn, er := res.RequestProxy.DialWithTimeout(network, _serverIP_, 3*time.Second, res.outRouterIP)
|
||||
if conn != nil {
|
||||
return newHConn(event, conn), er, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
func tryDialDirectIP(res *clientPart, network, address, port string, event Event) (net.Conn, error, bool) { //addr本身是IP时直接拨号
|
||||
i := net.ParseIP(address)
|
||||
if i == nil {
|
||||
return nil, nil, false
|
||||
}
|
||||
if len(i) == net.IPv4len {
|
||||
r, e := res.RequestProxy.Dial(network, i.String()+":"+port, res.outRouterIP)
|
||||
return newHConn(event, r), e, true
|
||||
}
|
||||
r, e := res.RequestProxy.Dial(network, fmt.Sprintf("[%s]:%s", address, port), res.outRouterIP)
|
||||
return newHConn(event, r), e, true
|
||||
}
|
||||
|
||||
func allLocal127(ips []net.IP) bool { //判断是否全是127.0.0.1
|
||||
for _, ip := range ips {
|
||||
if ip.String() != "127.0.0.1" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
123
src/loop/loop.go
123
src/loop/loop.go
@@ -1,75 +1,116 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"errors" //错误定义
|
||||
"net" //网络连接
|
||||
"sync" //并发锁
|
||||
)
|
||||
|
||||
// 读写锁用于保护端口映射表
|
||||
// 错误定义
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
portMap = map[uint16]uint16{} // key: 本地端口,value: 远端端口
|
||||
errNotTCP = errors.New("connection is not TCP") //非TCP连接错误
|
||||
)
|
||||
|
||||
// 端口映射与过滤表
|
||||
var (
|
||||
mu sync.RWMutex //读写锁保护共享map
|
||||
portMap = make(map[uint16]uint16) //key: 本地端口 value: 远端端口
|
||||
filter = make(map[uint16]bool) //需要过滤的端口集合
|
||||
)
|
||||
|
||||
// extractPorts 从连接中提取本地端口和远端端口
|
||||
func extractPorts(conn net.Conn) (uint16, uint16, error) {
|
||||
tcpLocal, ok1 := conn.LocalAddr().(*net.TCPAddr) // 本地 TCP 地址
|
||||
tcpRemote, ok2 := conn.RemoteAddr().(*net.TCPAddr) // 对端 TCP 地址
|
||||
if !ok1 || !ok2 {
|
||||
return 0, 0, errors.New("connection is not TCP")
|
||||
func extractPorts(conn net.Conn) (local uint16, remote uint16, err error) {
|
||||
if conn == nil { //连接为空直接报错
|
||||
return 0, 0, errNotTCP
|
||||
}
|
||||
tcpLocal, ok1 := conn.LocalAddr().(*net.TCPAddr) //本地TCP地址
|
||||
tcpRemote, ok2 := conn.RemoteAddr().(*net.TCPAddr) //对端TCP地址
|
||||
if !ok1 || !ok2 { //非TCP连接
|
||||
return 0, 0, errNotTCP
|
||||
}
|
||||
return uint16(tcpLocal.Port), uint16(tcpRemote.Port), nil
|
||||
}
|
||||
|
||||
// Add 记录一条新的连接端口映射(local -> remote)
|
||||
// Add 记录一条新的连接端口映射(local -> remote)
|
||||
func Add(conn net.Conn) {
|
||||
localPort, remotePort, err := extractPorts(conn)
|
||||
localPort, remotePort, err := extractPorts(conn) //提取端口
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
portMap[localPort] = remotePort
|
||||
mu.Unlock()
|
||||
mu.Lock() //加写锁
|
||||
portMap[localPort] = remotePort //写入映射
|
||||
mu.Unlock() //解锁
|
||||
}
|
||||
|
||||
// Un 移除一条连接的端口映射
|
||||
func Un(conn net.Conn) {
|
||||
localPort, _, err := extractPorts(conn)
|
||||
localPort, _, err := extractPorts(conn) //提取本地端口
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
delete(portMap, localPort)
|
||||
mu.Unlock()
|
||||
mu.Lock() //加写锁
|
||||
delete(portMap, localPort) //删除映射
|
||||
mu.Unlock() //解锁
|
||||
}
|
||||
|
||||
// Check 检测当前连接是否形成“反向环路”
|
||||
// 规则:
|
||||
//
|
||||
// 已记录过一条 remote -> local
|
||||
// 当前出现 local -> remote
|
||||
// 并且其中一端是 ServerPort(用于限定只检测从服务器端口发起的回连)
|
||||
// 则判定为环路
|
||||
func Check(conn net.Conn, ServerPort uint16) (rs bool) {
|
||||
localPort, remotePort, err := extractPorts(conn)
|
||||
// AddLoopFilter 加入过滤集合
|
||||
func AddLoopFilter(t uint16) {
|
||||
mu.Lock() //加写锁
|
||||
filter[t] = true //记录远端端口
|
||||
mu.Unlock() //解锁
|
||||
}
|
||||
|
||||
// UnLoopFilter 从过滤集合移除
|
||||
func UnLoopFilter(t uint16) {
|
||||
mu.Lock() //加写锁
|
||||
delete(filter, t) //删除远端端口
|
||||
mu.Unlock() //解锁
|
||||
}
|
||||
|
||||
// IsFiltered 判断端口是否在过滤集合中
|
||||
func IsFiltered(port uint16) bool {
|
||||
mu.RLock() //加读锁
|
||||
_, ok := filter[port] //查询端口
|
||||
mu.RUnlock() //解锁
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsFilterConn 判断端口是否在过滤集合中
|
||||
func IsFilterConn(conn net.Conn) bool {
|
||||
_, remotePort, err := extractPorts(conn) //提取本地端口
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
mu.RLock() //加读锁
|
||||
ok, _ := filter[remotePort] //查询端口
|
||||
mu.RUnlock() //解锁
|
||||
return ok
|
||||
}
|
||||
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
// Check 检测当前连接是否形成“反向环路”
|
||||
// 判定条件:
|
||||
// 1) 已记录过 remote -> local 的映射
|
||||
// 2) 当前出现 local -> remote(端口对调)
|
||||
// 3) localPort == ServerPort(只限定从服务器端口发起的回连)
|
||||
func Check(conn net.Conn, ServerPort uint16) bool {
|
||||
localPort, remotePort, err := extractPorts(conn) //提取端口
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
mu.RLock() //加读锁
|
||||
mapped, ok := portMap[remotePort] //查找是否存在remotePort作为本地端口的旧映射
|
||||
isFilter := filter[remotePort]
|
||||
mu.RUnlock() //解锁
|
||||
|
||||
// 查找是否存在 remotePort 作为本地端口的旧映射
|
||||
if mapped, ok := portMap[remotePort]; ok {
|
||||
// 要求 mapped == localPort 表示出现端口对调
|
||||
// 再额外要求 localPort == ServerPort 避免端口误判
|
||||
if mapped == localPort && localPort == ServerPort {
|
||||
return true
|
||||
}
|
||||
if !ok { //未命中映射
|
||||
return false
|
||||
}
|
||||
if isFilter || mapped != localPort {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
if localPort != ServerPort { //不是限定的服务器端口
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/qtgolang/SunnyNet/src/websocket"
|
||||
)
|
||||
|
||||
const SunnyVersion = "2025-12-23"
|
||||
const SunnyVersion = "2025-12-25"
|
||||
const Information = `
|
||||
------------------------------------------------------
|
||||
欢迎使用 SunnyNet 网络中间件 - V` + SunnyVersion + `
|
||||
|
||||
Reference in New Issue
Block a user