fix(starnet): 重构请求执行链路并补齐代理/重试/trace边界
- 分离 Request 的配置态与执行态,修复二次 Do、raw 模式网络配置失效和 body 来源互斥问题 - 新增 starnet trace 抽象,补齐 DNS/连接/TLS/重试事件,并优化动态 transport 缓存与代理解析路径 - 收紧非法代理为 fail-fast,多目标目标回退仅限幂等请求,修复 Host/TLS/SNI 等语义边界 - 补充防御性拷贝、专项回归测试、本地代理/TLS 用例与 README 行为说明
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package tlssniffercore
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func ComposeServerTLSConfig(base, selected *tls.Config) *tls.Config {
|
||||
if base == nil {
|
||||
return selected
|
||||
}
|
||||
if selected == nil {
|
||||
return base
|
||||
}
|
||||
|
||||
out := base.Clone()
|
||||
ApplyServerTLSOverrides(out, selected)
|
||||
return out
|
||||
}
|
||||
|
||||
func ApplyServerTLSOverrides(dst, src *tls.Config) {
|
||||
if dst == nil || src == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if src.Rand != nil {
|
||||
dst.Rand = src.Rand
|
||||
}
|
||||
if src.Time != nil {
|
||||
dst.Time = src.Time
|
||||
}
|
||||
if len(src.Certificates) > 0 {
|
||||
dst.Certificates = append([]tls.Certificate(nil), src.Certificates...)
|
||||
}
|
||||
if len(src.NameToCertificate) > 0 {
|
||||
copied := make(map[string]*tls.Certificate, len(src.NameToCertificate))
|
||||
for name, cert := range src.NameToCertificate {
|
||||
copied[name] = cert
|
||||
}
|
||||
dst.NameToCertificate = copied
|
||||
}
|
||||
if src.GetCertificate != nil {
|
||||
dst.GetCertificate = src.GetCertificate
|
||||
}
|
||||
if src.GetClientCertificate != nil {
|
||||
dst.GetClientCertificate = src.GetClientCertificate
|
||||
}
|
||||
if src.GetConfigForClient != nil {
|
||||
dst.GetConfigForClient = src.GetConfigForClient
|
||||
}
|
||||
if src.VerifyPeerCertificate != nil {
|
||||
dst.VerifyPeerCertificate = src.VerifyPeerCertificate
|
||||
}
|
||||
if src.VerifyConnection != nil {
|
||||
dst.VerifyConnection = src.VerifyConnection
|
||||
}
|
||||
if src.RootCAs != nil {
|
||||
dst.RootCAs = src.RootCAs
|
||||
}
|
||||
if len(src.NextProtos) > 0 {
|
||||
dst.NextProtos = append([]string(nil), src.NextProtos...)
|
||||
}
|
||||
if src.ServerName != "" {
|
||||
dst.ServerName = src.ServerName
|
||||
}
|
||||
if src.ClientAuth > dst.ClientAuth {
|
||||
dst.ClientAuth = src.ClientAuth
|
||||
}
|
||||
if src.ClientCAs != nil {
|
||||
dst.ClientCAs = src.ClientCAs
|
||||
}
|
||||
if src.InsecureSkipVerify {
|
||||
dst.InsecureSkipVerify = true
|
||||
}
|
||||
if len(src.CipherSuites) > 0 {
|
||||
dst.CipherSuites = append([]uint16(nil), src.CipherSuites...)
|
||||
}
|
||||
if src.PreferServerCipherSuites {
|
||||
dst.PreferServerCipherSuites = true
|
||||
}
|
||||
if src.SessionTicketsDisabled {
|
||||
dst.SessionTicketsDisabled = true
|
||||
}
|
||||
if src.SessionTicketKey != ([32]byte{}) {
|
||||
dst.SessionTicketKey = src.SessionTicketKey
|
||||
}
|
||||
if src.ClientSessionCache != nil {
|
||||
dst.ClientSessionCache = src.ClientSessionCache
|
||||
}
|
||||
if src.UnwrapSession != nil {
|
||||
dst.UnwrapSession = src.UnwrapSession
|
||||
}
|
||||
if src.WrapSession != nil {
|
||||
dst.WrapSession = src.WrapSession
|
||||
}
|
||||
if src.MinVersion != 0 && (dst.MinVersion == 0 || src.MinVersion > dst.MinVersion) {
|
||||
dst.MinVersion = src.MinVersion
|
||||
}
|
||||
if src.MaxVersion != 0 && (dst.MaxVersion == 0 || src.MaxVersion < dst.MaxVersion) {
|
||||
dst.MaxVersion = src.MaxVersion
|
||||
}
|
||||
if len(src.CurvePreferences) > 0 {
|
||||
dst.CurvePreferences = append([]tls.CurveID(nil), src.CurvePreferences...)
|
||||
}
|
||||
if src.DynamicRecordSizingDisabled {
|
||||
dst.DynamicRecordSizingDisabled = true
|
||||
}
|
||||
if src.Renegotiation != 0 {
|
||||
dst.Renegotiation = src.Renegotiation
|
||||
}
|
||||
if src.KeyLogWriter != nil {
|
||||
dst.KeyLogWriter = src.KeyLogWriter
|
||||
}
|
||||
if len(src.EncryptedClientHelloConfigList) > 0 {
|
||||
dst.EncryptedClientHelloConfigList = append([]byte(nil), src.EncryptedClientHelloConfigList...)
|
||||
}
|
||||
if src.EncryptedClientHelloRejectionVerify != nil {
|
||||
dst.EncryptedClientHelloRejectionVerify = src.EncryptedClientHelloRejectionVerify
|
||||
}
|
||||
if src.GetEncryptedClientHelloKeys != nil {
|
||||
dst.GetEncryptedClientHelloKeys = src.GetEncryptedClientHelloKeys
|
||||
}
|
||||
if len(src.EncryptedClientHelloKeys) > 0 {
|
||||
dst.EncryptedClientHelloKeys = append([]tls.EncryptedClientHelloKey(nil), src.EncryptedClientHelloKeys...)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package tlssniffercore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ClientHelloMeta struct {
|
||||
ServerName string
|
||||
LocalAddr net.Addr
|
||||
RemoteAddr net.Addr
|
||||
SupportedProtos []string
|
||||
SupportedVersions []uint16
|
||||
CipherSuites []uint16
|
||||
}
|
||||
|
||||
type SniffResult struct {
|
||||
IsTLS bool
|
||||
ClientHello *ClientHelloMeta
|
||||
Buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
type Sniffer struct{}
|
||||
|
||||
func (s Sniffer) Sniff(conn net.Conn, maxBytes int) (SniffResult, error) {
|
||||
if maxBytes <= 0 {
|
||||
maxBytes = 64 * 1024
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
limited := &io.LimitedReader{R: conn, N: int64(maxBytes)}
|
||||
meta, isTLS := sniffClientHello(limited, &buf, conn)
|
||||
|
||||
out := SniffResult{
|
||||
IsTLS: isTLS,
|
||||
Buffer: bytes.NewBuffer(append([]byte(nil), buf.Bytes()...)),
|
||||
}
|
||||
if isTLS {
|
||||
out.ClientHello = meta
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func sniffClientHello(reader io.Reader, buf *bytes.Buffer, conn net.Conn) (*ClientHelloMeta, bool) {
|
||||
meta := &ClientHelloMeta{
|
||||
LocalAddr: conn.LocalAddr(),
|
||||
RemoteAddr: conn.RemoteAddr(),
|
||||
}
|
||||
|
||||
header, complete := readTLSRecordHeader(reader, buf)
|
||||
if len(header) < 3 {
|
||||
return nil, false
|
||||
}
|
||||
isTLS := header[0] == 0x16 && header[1] == 0x03
|
||||
if !isTLS {
|
||||
return nil, false
|
||||
}
|
||||
if len(header) < 5 || !complete {
|
||||
return meta, true
|
||||
}
|
||||
|
||||
recordLen := int(binary.BigEndian.Uint16(header[3:5]))
|
||||
recordBody, bodyOK := readBufferedBytes(reader, buf, recordLen)
|
||||
if !bodyOK {
|
||||
return meta, true
|
||||
}
|
||||
if len(recordBody) < 4 || recordBody[0] != 0x01 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
helloLen := int(recordBody[1])<<16 | int(recordBody[2])<<8 | int(recordBody[3])
|
||||
helloBytes := append([]byte(nil), recordBody[4:]...)
|
||||
for len(helloBytes) < helloLen {
|
||||
nextHeader, ok := readTLSRecordHeader(reader, buf)
|
||||
if len(nextHeader) < 5 || !ok {
|
||||
return meta, true
|
||||
}
|
||||
if nextHeader[0] != 0x16 || nextHeader[1] != 0x03 {
|
||||
return meta, true
|
||||
}
|
||||
nextLen := int(binary.BigEndian.Uint16(nextHeader[3:5]))
|
||||
nextBody, bodyOK := readBufferedBytes(reader, buf, nextLen)
|
||||
if !bodyOK {
|
||||
return meta, true
|
||||
}
|
||||
helloBytes = append(helloBytes, nextBody...)
|
||||
}
|
||||
|
||||
parseClientHelloBody(meta, helloBytes[:helloLen])
|
||||
return meta, true
|
||||
}
|
||||
|
||||
func readTLSRecordHeader(reader io.Reader, buf *bytes.Buffer) ([]byte, bool) {
|
||||
return readBufferedBytes(reader, buf, 5)
|
||||
}
|
||||
|
||||
func readBufferedBytes(reader io.Reader, buf *bytes.Buffer, count int) ([]byte, bool) {
|
||||
if count <= 0 {
|
||||
return nil, true
|
||||
}
|
||||
tmp := make([]byte, count)
|
||||
readN, err := io.ReadFull(reader, tmp)
|
||||
if readN > 0 {
|
||||
buf.Write(tmp[:readN])
|
||||
}
|
||||
return append([]byte(nil), tmp[:readN]...), err == nil
|
||||
}
|
||||
|
||||
func parseClientHelloBody(meta *ClientHelloMeta, body []byte) {
|
||||
if meta == nil || len(body) < 34 {
|
||||
return
|
||||
}
|
||||
|
||||
offset := 2 + 32
|
||||
sessionIDLen := int(body[offset])
|
||||
offset++
|
||||
if offset+sessionIDLen > len(body) {
|
||||
return
|
||||
}
|
||||
offset += sessionIDLen
|
||||
|
||||
if offset+2 > len(body) {
|
||||
return
|
||||
}
|
||||
cipherSuitesLen := int(binary.BigEndian.Uint16(body[offset : offset+2]))
|
||||
offset += 2
|
||||
if offset+cipherSuitesLen > len(body) {
|
||||
return
|
||||
}
|
||||
for index := 0; index+1 < cipherSuitesLen; index += 2 {
|
||||
meta.CipherSuites = append(meta.CipherSuites, binary.BigEndian.Uint16(body[offset+index:offset+index+2]))
|
||||
}
|
||||
offset += cipherSuitesLen
|
||||
|
||||
if offset >= len(body) {
|
||||
return
|
||||
}
|
||||
compressionMethodsLen := int(body[offset])
|
||||
offset++
|
||||
if offset+compressionMethodsLen > len(body) {
|
||||
return
|
||||
}
|
||||
offset += compressionMethodsLen
|
||||
|
||||
if offset+2 > len(body) {
|
||||
return
|
||||
}
|
||||
extensionsLen := int(binary.BigEndian.Uint16(body[offset : offset+2]))
|
||||
offset += 2
|
||||
if offset+extensionsLen > len(body) {
|
||||
return
|
||||
}
|
||||
|
||||
parseClientHelloExtensions(meta, body[offset:offset+extensionsLen])
|
||||
}
|
||||
|
||||
func parseClientHelloExtensions(meta *ClientHelloMeta, exts []byte) {
|
||||
for offset := 0; offset+4 <= len(exts); {
|
||||
extType := binary.BigEndian.Uint16(exts[offset : offset+2])
|
||||
extLen := int(binary.BigEndian.Uint16(exts[offset+2 : offset+4]))
|
||||
offset += 4
|
||||
if offset+extLen > len(exts) {
|
||||
return
|
||||
}
|
||||
extData := exts[offset : offset+extLen]
|
||||
offset += extLen
|
||||
|
||||
switch extType {
|
||||
case 0:
|
||||
parseServerNameExtension(meta, extData)
|
||||
case 16:
|
||||
parseALPNExtension(meta, extData)
|
||||
case 43:
|
||||
parseSupportedVersionsExtension(meta, extData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseServerNameExtension(meta *ClientHelloMeta, data []byte) {
|
||||
if len(data) < 2 {
|
||||
return
|
||||
}
|
||||
listLen := int(binary.BigEndian.Uint16(data[:2]))
|
||||
if listLen == 0 || 2+listLen > len(data) {
|
||||
return
|
||||
}
|
||||
list := data[2 : 2+listLen]
|
||||
for offset := 0; offset+3 <= len(list); {
|
||||
nameType := list[offset]
|
||||
nameLen := int(binary.BigEndian.Uint16(list[offset+1 : offset+3]))
|
||||
offset += 3
|
||||
if offset+nameLen > len(list) {
|
||||
return
|
||||
}
|
||||
if nameType == 0 {
|
||||
meta.ServerName = string(list[offset : offset+nameLen])
|
||||
return
|
||||
}
|
||||
offset += nameLen
|
||||
}
|
||||
}
|
||||
|
||||
func parseALPNExtension(meta *ClientHelloMeta, data []byte) {
|
||||
if len(data) < 2 {
|
||||
return
|
||||
}
|
||||
listLen := int(binary.BigEndian.Uint16(data[:2]))
|
||||
if listLen == 0 || 2+listLen > len(data) {
|
||||
return
|
||||
}
|
||||
list := data[2 : 2+listLen]
|
||||
for offset := 0; offset < len(list); {
|
||||
nameLen := int(list[offset])
|
||||
offset++
|
||||
if offset+nameLen > len(list) {
|
||||
return
|
||||
}
|
||||
meta.SupportedProtos = append(meta.SupportedProtos, string(list[offset:offset+nameLen]))
|
||||
offset += nameLen
|
||||
}
|
||||
}
|
||||
|
||||
func parseSupportedVersionsExtension(meta *ClientHelloMeta, data []byte) {
|
||||
if len(data) < 1 {
|
||||
return
|
||||
}
|
||||
listLen := int(data[0])
|
||||
if listLen == 0 || 1+listLen > len(data) {
|
||||
return
|
||||
}
|
||||
list := data[1 : 1+listLen]
|
||||
for offset := 0; offset+1 < len(list); offset += 2 {
|
||||
meta.SupportedVersions = append(meta.SupportedVersions, binary.BigEndian.Uint16(list[offset:offset+2]))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user