Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
8ca03fa94e Clarify comment about switchToDirectCopy flag remaining true
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-01-11 10:36:28 +00:00
copilot-swe-agent[bot]
bdfd1d27b5 Fix occasional SSL protocol error in XTLS Vision by deferring direct copy when rawInput has pending data
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-01-11 10:33:21 +00:00
copilot-swe-agent[bot]
e9774fc237 Initial plan 2026-01-11 10:20:09 +00:00
3 changed files with 27 additions and 34 deletions

2
go.mod
View File

@@ -25,6 +25,7 @@ require (
golang.org/x/net v0.48.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.39.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.11
@@ -50,7 +51,6 @@ require (
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@@ -224,7 +224,8 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
switchToDirectCopy = &w.trafficState.Outbound.DownlinkReaderDirectCopy
}
if *switchToDirectCopy {
if *switchToDirectCopy && w.input == nil {
// Already switched to direct copy mode
if w.directReadCounter != nil {
w.directReadCounter.Add(int64(buffer.Len()))
}
@@ -257,11 +258,18 @@ func (w *VisionReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
if *switchToDirectCopy {
// XTLS Vision processes TLS-like conn's input and rawInput
// input contains decrypted application data - safe to merge
if inputBuffer, err := buf.ReadFrom(w.input); err == nil && !inputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, inputBuffer)
}
if rawInputBuffer, err := buf.ReadFrom(w.rawInput); err == nil && !rawInputBuffer.IsEmpty() {
buffer, _ = buf.MergeMulti(buffer, rawInputBuffer)
// rawInput may contain encrypted bytes for the next TLS record
// If rawInput is not empty, we should NOT switch to direct mode yet
// because those bytes need to be processed by the TLS layer first
if w.rawInput != nil && w.rawInput.Len() > 0 {
// rawInput has pending data - defer direct copy to next read
// *switchToDirectCopy remains true (unchanged), so we will retry on the next ReadMultiBuffer call
// This ensures we don't mix encrypted bytes with application data
return buffer, err
}
*w.input = bytes.Reader{} // release memory
w.input = nil

View File

@@ -290,10 +290,8 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509
// directly return success if pinned cert is leaf
// or add the CA to RootCAs if pinned cert is CA(and can be used in VerifyPeerCertInNames for Self signed CA)
RootCAs := r.RootCAs
var verifyResult verifyResult
var verifiedCert *x509.Certificate
if r.PinnedPeerCertSha256 != nil {
verifyResult, verifiedCert = verifyChain(certs, r.PinnedPeerCertSha256)
verifyResult, verifiedCert := verifyChain(certs, r.PinnedPeerCertSha256)
switch verifyResult {
case certNotFound:
return errors.New("peer cert is unrecognized")
@@ -307,39 +305,27 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509
}
}
if len(r.VerifyPeerCertInNames) > 0 {
opts := x509.VerifyOptions{
Roots: RootCAs,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
for _, opts.DNSName = range r.VerifyPeerCertInNames {
if _, err := certs[0].Verify(opts); err == nil {
return nil
if r.VerifyPeerCertInNames != nil {
if len(r.VerifyPeerCertInNames) > 0 {
opts := x509.VerifyOptions{
Roots: RootCAs,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
for _, opts.DNSName = range r.VerifyPeerCertInNames {
if _, err := certs[0].Verify(opts); err == nil {
return nil
}
}
}
} else if len(verifiedChains) == 0 && verifyResult == foundCA { // if found ca and verifiedChains is empty, we need to verify here
opts := x509.VerifyOptions{
Roots: RootCAs,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
DNSName: r.Config.ServerName,
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err == nil {
return nil
}
}
return nil
}
type RandCarrier struct {
Config *tls.Config
RootCAs *x509.CertPool
VerifyPeerCertInNames []string
PinnedPeerCertSha256 [][]byte
@@ -380,7 +366,6 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
SessionTicketsDisabled: !c.EnableSessionResumption,
VerifyPeerCertificate: randCarrier.verifyPeerCert,
}
randCarrier.Config = config
if len(c.VerifyPeerCertInNames) > 0 {
config.InsecureSkipVerify = true
} else {