瀏覽代碼

Improve tls writer

世界 3 年之前
父節點
當前提交
1173fdea64

+ 206 - 0
common/badtls/badtls.go

@@ -0,0 +1,206 @@
+//go:build go1.19 && !go1.20
+
+package badtls
+
+import (
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/tls"
+	"encoding/binary"
+	"io"
+	"net"
+	"reflect"
+	"sync"
+	"sync/atomic"
+	"unsafe"
+
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/buf"
+	"github.com/sagernet/sing/common/bufio"
+	E "github.com/sagernet/sing/common/exceptions"
+	N "github.com/sagernet/sing/common/network"
+)
+
+type Conn struct {
+	*tls.Conn
+	writer           N.ExtendedWriter
+	activeCall       *int32
+	closeNotifySent  *bool
+	version          *uint16
+	rand             io.Reader
+	halfAccess       *sync.Mutex
+	halfError        *error
+	cipher           cipher.AEAD
+	explicitNonceLen int
+	halfPtr          uintptr
+	halfSeq          []byte
+	halfScratchBuf   []byte
+}
+
+func Create(conn *tls.Conn) (TLSConn, error) {
+	if !handshakeComplete(conn) {
+		return nil, E.New("handshake not finished")
+	}
+	rawConn := reflect.Indirect(reflect.ValueOf(conn))
+	rawActiveCall := rawConn.FieldByName("activeCall")
+	if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
+		return nil, E.New("badtls: invalid active call")
+	}
+	activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
+	rawHalfConn := rawConn.FieldByName("out")
+	if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid half conn")
+	}
+	rawVersion := rawConn.FieldByName("vers")
+	if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
+		return nil, E.New("badtls: invalid version")
+	}
+	version := (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
+	rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
+	if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
+		return nil, E.New("badtls: invalid notify")
+	}
+	closeNotifySent := (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
+	rawConfig := reflect.Indirect(rawConn.FieldByName("config"))
+	if !rawConfig.IsValid() || rawConfig.Kind() != reflect.Struct {
+		return nil, E.New("badtls: bad config")
+	}
+	config := (*tls.Config)(unsafe.Pointer(rawConfig.UnsafeAddr()))
+	randReader := config.Rand
+	if randReader == nil {
+		randReader = rand.Reader
+	}
+	rawHalfMutex := rawHalfConn.FieldByName("Mutex")
+	if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid half mutex")
+	}
+	halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
+	rawHalfError := rawHalfConn.FieldByName("err")
+	if !rawHalfError.IsValid() || rawHalfError.Kind() != reflect.Interface {
+		return nil, E.New("badtls: invalid half error")
+	}
+	halfError := (*error)(unsafe.Pointer(rawHalfError.UnsafeAddr()))
+	rawHalfCipherInterface := rawHalfConn.FieldByName("cipher")
+	if !rawHalfCipherInterface.IsValid() || rawHalfCipherInterface.Kind() != reflect.Interface {
+		return nil, E.New("badtls: invalid cipher interface")
+	}
+	rawHalfCipher := rawHalfCipherInterface.Elem()
+	aeadCipher, loaded := valueInterface(rawHalfCipher, false).(cipher.AEAD)
+	if !loaded {
+		return nil, E.New("badtls: invalid AEAD cipher")
+	}
+	var explicitNonceLen int
+	switch cipherName := reflect.Indirect(rawHalfCipher).Type().String(); cipherName {
+	case "tls.prefixNonceAEAD":
+		explicitNonceLen = aeadCipher.NonceSize()
+	case "tls.xorNonceAEAD":
+	default:
+		return nil, E.New("badtls: unknown cipher type: ", cipherName)
+	}
+	rawHalfSeq := rawHalfConn.FieldByName("seq")
+	if !rawHalfSeq.IsValid() || rawHalfSeq.Kind() != reflect.Array {
+		return nil, E.New("badtls: invalid seq")
+	}
+	halfSeq := rawHalfSeq.Bytes()
+	rawHalfScratchBuf := rawHalfConn.FieldByName("scratchBuf")
+	if !rawHalfScratchBuf.IsValid() || rawHalfScratchBuf.Kind() != reflect.Array {
+		return nil, E.New("badtls: invalid scratchBuf")
+	}
+	halfScratchBuf := rawHalfScratchBuf.Bytes()
+	return &Conn{
+		Conn:             conn,
+		writer:           bufio.NewExtendedWriter(conn.NetConn()),
+		activeCall:       activeCall,
+		closeNotifySent:  closeNotifySent,
+		version:          version,
+		halfAccess:       halfAccess,
+		halfError:        halfError,
+		cipher:           aeadCipher,
+		explicitNonceLen: explicitNonceLen,
+		rand:             randReader,
+		halfPtr:          rawHalfConn.UnsafeAddr(),
+		halfSeq:          halfSeq,
+		halfScratchBuf:   halfScratchBuf,
+	}, nil
+}
+
+func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
+	if buffer.Len() > maxPlaintext {
+		defer buffer.Release()
+		return common.Error(c.Write(buffer.Bytes()))
+	}
+	for {
+		x := atomic.LoadInt32(c.activeCall)
+		if x&1 != 0 {
+			return net.ErrClosed
+		}
+		if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
+			break
+		}
+	}
+	defer atomic.AddInt32(c.activeCall, -2)
+	c.halfAccess.Lock()
+	defer c.halfAccess.Unlock()
+	if err := *c.halfError; err != nil {
+		return err
+	}
+	if *c.closeNotifySent {
+		return errShutdown
+	}
+	dataLen := buffer.Len()
+	dataBytes := buffer.Bytes()
+	outBuf := buffer.ExtendHeader(recordHeaderLen + c.explicitNonceLen)
+	outBuf[0] = 23
+	version := *c.version
+	if version == 0 {
+		version = tls.VersionTLS10
+	} else if version == tls.VersionTLS13 {
+		version = tls.VersionTLS12
+	}
+	binary.BigEndian.PutUint16(outBuf[1:], version)
+	var nonce []byte
+	if c.explicitNonceLen > 0 {
+		nonce = outBuf[5 : 5+c.explicitNonceLen]
+		if c.explicitNonceLen < 16 {
+			copy(nonce, c.halfSeq)
+		} else {
+			if _, err := io.ReadFull(c.rand, nonce); err != nil {
+				return err
+			}
+		}
+	}
+	if len(nonce) == 0 {
+		nonce = c.halfSeq
+	}
+	if *c.version == tls.VersionTLS13 {
+		buffer.FreeBytes()[0] = 23
+		binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+1+c.cipher.Overhead()))
+		c.cipher.Seal(outBuf, nonce, outBuf[recordHeaderLen:recordHeaderLen+c.explicitNonceLen+dataLen+1], outBuf[:recordHeaderLen])
+		buffer.Extend(1 + c.cipher.Overhead())
+	} else {
+		binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen))
+		additionalData := append(c.halfScratchBuf[:0], c.halfSeq...)
+		additionalData = append(additionalData, outBuf[:recordHeaderLen]...)
+		c.cipher.Seal(outBuf, nonce, dataBytes, additionalData)
+		buffer.Extend(c.cipher.Overhead())
+		binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
+	}
+	incSeq(c.halfPtr)
+	return c.writer.WriteBuffer(buffer)
+}
+
+func (c *Conn) FrontHeadroom() int {
+	return recordHeaderLen + c.explicitNonceLen
+}
+
+func (c *Conn) RearHeadroom() int {
+	return 1 + c.cipher.Overhead()
+}
+
+func (c *Conn) WriterMTU() int {
+	return maxPlaintext
+}
+
+func (c *Conn) Upstream() any {
+	return c.NetConn()
+}

+ 12 - 0
common/badtls/badtls_stub.go

@@ -0,0 +1,12 @@
+//go:build !go1.19 || go1.20
+
+package badtls
+
+import (
+	"crypto/tls"
+	"os"
+)
+
+func Create(conn *tls.Conn) (TLSConn, error) {
+	return nil, os.ErrInvalid
+}

+ 13 - 0
common/badtls/conn.go

@@ -0,0 +1,13 @@
+package badtls
+
+import (
+	"context"
+	"crypto/tls"
+	"net"
+)
+
+type TLSConn interface {
+	net.Conn
+	HandshakeContext(ctx context.Context) error
+	ConnectionState() tls.ConnectionState
+}

+ 26 - 0
common/badtls/link.go

@@ -0,0 +1,26 @@
+//go:build go1.19 && !go.1.20
+
+package badtls
+
+import (
+	"crypto/tls"
+	"reflect"
+	_ "unsafe"
+)
+
+const (
+	maxPlaintext    = 16384 // maximum plaintext payload length
+	recordHeaderLen = 5     // record header length
+)
+
+//go:linkname errShutdown crypto/tls.errShutdown
+var errShutdown error
+
+//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
+func handshakeComplete(conn *tls.Conn) bool
+
+//go:linkname incSeq crypto/tls.(*halfConn).incSeq
+func incSeq(conn uintptr)
+
+//go:linkname valueInterface reflect.valueInterface
+func valueInterface(v reflect.Value, safe bool) any

+ 13 - 1
common/tls/client.go

@@ -2,10 +2,12 @@ package tls
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/tls"
 	"net"
 	"net"
 	"os"
 	"os"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/badtls"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-box/option"
 	M "github.com/sagernet/sing/common/metadata"
 	M "github.com/sagernet/sing/common/metadata"
@@ -35,7 +37,17 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
 	ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
 	ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
 	defer cancel()
 	defer cancel()
 	err := tlsConn.HandshakeContext(ctx)
 	err := tlsConn.HandshakeContext(ctx)
-	return tlsConn, err
+	if err != nil {
+		return nil, err
+	}
+	if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
+		var badConn badtls.TLSConn
+		badConn, err = badtls.Create(stdConn)
+		if err == nil {
+			return badConn, nil
+		}
+	}
+	return tlsConn, nil
 }
 }
 
 
 type Dialer struct {
 type Dialer struct {

+ 22 - 0
common/tls/server.go

@@ -2,7 +2,11 @@ package tls
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/tls"
+	"net"
 
 
+	"github.com/sagernet/sing-box/common/badtls"
+	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-box/option"
 )
 )
@@ -10,3 +14,21 @@ import (
 func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
 func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
 	return newSTDServer(ctx, logger, options)
 	return newSTDServer(ctx, logger, options)
 }
 }
+
+func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
+	tlsConn := config.Server(conn)
+	ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
+	defer cancel()
+	err := tlsConn.HandshakeContext(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
+		var badConn badtls.TLSConn
+		badConn, err = badtls.Create(stdConn)
+		if err == nil {
+			return badConn, nil
+		}
+	}
+	return tlsConn, nil
+}

+ 1 - 1
go.mod

@@ -23,7 +23,7 @@ require (
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/refraction-networking/utls v1.1.2
 	github.com/refraction-networking/utls v1.1.2
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb
-	github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186
+	github.com/sagernet/sing v0.0.0-20221001030341-348376220066
 	github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3
 	github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/sagernet/sing-tun v0.0.0-20220929163559-a93592a9b581
 	github.com/sagernet/sing-tun v0.0.0-20220929163559-a93592a9b581

+ 2 - 2
go.sum

@@ -145,8 +145,8 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
-github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186 h1:ZDlgH6dTozS3ODaYq1GxCj+H8NvYESaex90iX72gadw=
-github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
+github.com/sagernet/sing v0.0.0-20221001030341-348376220066 h1:kQSd+x9ZLBcjl2+VjCLlkxzlD8VO5Q9X3FHDb7OGoN4=
+github.com/sagernet/sing v0.0.0-20221001030341-348376220066/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
 github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3 h1:AEdyJxEDFq38z0pBX/0MpikQapGMIch+1ADe9k1bJqU=
 github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3 h1:AEdyJxEDFq38z0pBX/0MpikQapGMIch+1ADe9k1bJqU=
 github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
 github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=

+ 5 - 1
inbound/http.go

@@ -72,8 +72,12 @@ func (h *HTTP) Close() error {
 }
 }
 
 
 func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	var err error
 	if h.tlsConfig != nil {
 	if h.tlsConfig != nil {
-		conn = h.tlsConfig.Server(conn)
+		conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
+		if err != nil {
+			return err
+		}
 	}
 	}
 	return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
 	return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
 }
 }

+ 5 - 1
inbound/trojan.go

@@ -150,8 +150,12 @@ func (h *Trojan) newTransportConnection(ctx context.Context, conn net.Conn, meta
 }
 }
 
 
 func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	var err error
 	if h.tlsConfig != nil && h.transport == nil {
 	if h.tlsConfig != nil && h.transport == nil {
-		conn = h.tlsConfig.Server(conn)
+		conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
+		if err != nil {
+			return err
+		}
 	}
 	}
 	return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
 	return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
 }
 }

+ 5 - 1
inbound/vmess.go

@@ -130,8 +130,12 @@ func (h *VMess) newTransportConnection(ctx context.Context, conn net.Conn, metad
 }
 }
 
 
 func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	var err error
 	if h.tlsConfig != nil && h.transport == nil {
 	if h.tlsConfig != nil && h.transport == nil {
-		conn = h.tlsConfig.Server(conn)
+		conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
+		if err != nil {
+			return err
+		}
 	}
 	}
 	return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
 	return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
 }
 }

+ 8 - 8
test/go.mod

@@ -10,12 +10,12 @@ require (
 	github.com/docker/docker v20.10.18+incompatible
 	github.com/docker/docker v20.10.18+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/docker/go-connections v0.4.0
 	github.com/gofrs/uuid v4.3.0+incompatible
 	github.com/gofrs/uuid v4.3.0+incompatible
-	github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f
+	github.com/sagernet/sing v0.0.0-20220930130214-cb9b17d6a4a7
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/spyzhov/ajson v0.7.1
 	github.com/spyzhov/ajson v0.7.1
 	github.com/stretchr/testify v1.8.0
 	github.com/stretchr/testify v1.8.0
 	go.uber.org/goleak v1.2.0
 	go.uber.org/goleak v1.2.0
-	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
+	golang.org/x/net v0.0.0-20220927171203-f486391704dc
 )
 )
 
 
 //replace github.com/sagernet/sing => ../../sing
 //replace github.com/sagernet/sing => ../../sing
@@ -67,9 +67,9 @@ require (
 	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
 	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
-	github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b // indirect
-	github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704 // indirect
-	github.com/sagernet/sing-vmess v0.0.0-20220923035311-d70afe4ab2c9 // indirect
+	github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3 // indirect
+	github.com/sagernet/sing-tun v0.0.0-20220929163559-a93592a9b581 // indirect
+	github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 // indirect
 	github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
 	github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
 	github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
 	github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
@@ -78,11 +78,11 @@ require (
 	go.uber.org/atomic v1.10.0 // indirect
 	go.uber.org/atomic v1.10.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.22.0 // indirect
 	go.uber.org/zap v1.22.0 // indirect
-	go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d // indirect
-	golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
+	go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab // indirect
+	golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
-	golang.org/x/sys v0.0.0-20220913120320-3275c407cedc // indirect
+	golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
 	golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect

+ 16 - 16
test/go.sum

@@ -165,16 +165,16 @@ github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTY
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
-github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f h1:GX416thAwyc0vHBOal/qplvdhFgYO2dHD5GqADCJ0Ig=
-github.com/sagernet/sing v0.0.0-20220921101604-86d7d510231f/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
-github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b h1:cXCMNJ9heZ+c6l+qUcku60x9KyXo4SOAaJfg/6spOmU=
-github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
+github.com/sagernet/sing v0.0.0-20220930130214-cb9b17d6a4a7 h1:VQqdVA6/ctKVOeKHKy9ZYMeB7TqxnP0jvK/Ig9Y+pG8=
+github.com/sagernet/sing v0.0.0-20220930130214-cb9b17d6a4a7/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
+github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3 h1:AEdyJxEDFq38z0pBX/0MpikQapGMIch+1ADe9k1bJqU=
+github.com/sagernet/sing-dns v0.0.0-20220929010544-ee843807aae3/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
-github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704 h1:DOQQXQbB2gq4n2FuMHrL07HRs2naCCsuiu/9l1JFb9A=
-github.com/sagernet/sing-tun v0.0.0-20220922083325-80ee99472704/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
-github.com/sagernet/sing-vmess v0.0.0-20220923035311-d70afe4ab2c9 h1:w7JlEa4xHXuuPTL3Opb+Z5f/l66SYi54rSfobzuxSF8=
-github.com/sagernet/sing-vmess v0.0.0-20220923035311-d70afe4ab2c9/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
+github.com/sagernet/sing-tun v0.0.0-20220929163559-a93592a9b581 h1:ghpmEsGVdFQys5jxy01aVQvhbbUT2c4kJRZXHZBNerw=
+github.com/sagernet/sing-tun v0.0.0-20220929163559-a93592a9b581/go.mod h1:qbqV9lwcXJnj1Tw4we7oA6Z8zGE/kCXQBCzuhzRWVw8=
+github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 h1:AZzFNRR/ZwMTceUQ1b/mxx6oyKqmFymdMn/yleJmoVM=
+github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
@@ -213,16 +213,16 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
 go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
 go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
 go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
-go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d h1:ggxwEf5eu0l8v+87VhX1czFh8zJul3hK16Gmruxn7hw=
-go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
+go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab h1:+yW1yrZ09EYNu1spCUOHBBNRbrLnfmutwyhbhCv3b6Q=
+go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
-golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
+golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
@@ -257,8 +257,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
+golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -298,8 +298,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220913120320-3275c407cedc h1:dpclq5m2YrqPGStKmtw7IcNbKLfbIqKXvNxDJKdIKYc=
-golang.org/x/sys v0.0.0-20220913120320-3275c407cedc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

+ 1 - 1
transport/v2raywebsocket/client.go

@@ -74,7 +74,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
 	if c.maxEarlyData <= 0 {
 	if c.maxEarlyData <= 0 {
 		conn, response, err := c.dialer.DialContext(ctx, c.uri, c.headers)
 		conn, response, err := c.dialer.DialContext(ctx, c.uri, c.headers)
 		if err == nil {
 		if err == nil {
-			return &WebsocketConn{Conn: conn, Writer: &Writer{conn, false}}, nil
+			return &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}, nil
 		}
 		}
 		return nil, wrapDialError(response, err)
 		return nil, wrapDialError(response, err)
 	} else {
 	} else {

+ 3 - 3
transport/v2raywebsocket/conn.go

@@ -26,7 +26,7 @@ func NewServerConn(wsConn *websocket.Conn, remoteAddr net.Addr) *WebsocketConn {
 	return &WebsocketConn{
 	return &WebsocketConn{
 		Conn:       wsConn,
 		Conn:       wsConn,
 		remoteAddr: remoteAddr,
 		remoteAddr: remoteAddr,
-		Writer:     &Writer{wsConn, true},
+		Writer:     NewWriter(wsConn, true),
 	}
 	}
 }
 }
 
 
@@ -117,7 +117,7 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
 	if err != nil {
 	if err != nil {
 		return 0, wrapDialError(response, err)
 		return 0, wrapDialError(response, err)
 	}
 	}
-	c.conn = &WebsocketConn{Conn: conn, Writer: &Writer{conn, false}}
+	c.conn = &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}
 	close(c.create)
 	close(c.create)
 	if len(lateData) > 0 {
 	if len(lateData) > 0 {
 		_, err = c.conn.Write(lateData)
 		_, err = c.conn.Write(lateData)
@@ -160,7 +160,7 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
 	if err != nil {
 	if err != nil {
 		return wrapDialError(response, err)
 		return wrapDialError(response, err)
 	}
 	}
-	c.conn = &WebsocketConn{Conn: conn, Writer: &Writer{conn, false}}
+	c.conn = &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}
 	close(c.create)
 	close(c.create)
 	if len(lateData) > 0 {
 	if len(lateData) > 0 {
 		_, err = c.conn.Write(lateData)
 		_, err = c.conn.Write(lateData)

+ 20 - 4
transport/v2raywebsocket/writer.go

@@ -4,8 +4,9 @@ import (
 	"encoding/binary"
 	"encoding/binary"
 	"math/rand"
 	"math/rand"
 
 
-	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/buf"
+	"github.com/sagernet/sing/common/bufio"
+	N "github.com/sagernet/sing/common/network"
 	"github.com/sagernet/websocket"
 	"github.com/sagernet/websocket"
 )
 )
 
 
@@ -13,9 +14,18 @@ const frontHeadroom = 14
 
 
 type Writer struct {
 type Writer struct {
 	*websocket.Conn
 	*websocket.Conn
+	writer   N.ExtendedWriter
 	isServer bool
 	isServer bool
 }
 }
 
 
+func NewWriter(conn *websocket.Conn, isServer bool) *Writer {
+	return &Writer{
+		conn,
+		bufio.NewExtendedWriter(conn.NetConn()),
+		isServer,
+	}
+}
+
 func (w *Writer) Write(p []byte) (n int, err error) {
 func (w *Writer) Write(p []byte) (n int, err error) {
 	err = w.Conn.WriteMessage(websocket.BinaryMessage, p)
 	err = w.Conn.WriteMessage(websocket.BinaryMessage, p)
 	if err != nil {
 	if err != nil {
@@ -25,8 +35,6 @@ func (w *Writer) Write(p []byte) (n int, err error) {
 }
 }
 
 
 func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
 func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
-	defer buffer.Release()
-
 	var payloadBitLength int
 	var payloadBitLength int
 	dataLen := buffer.Len()
 	dataLen := buffer.Len()
 	data := buffer.Bytes()
 	data := buffer.Bytes()
@@ -69,5 +77,13 @@ func (w *Writer) WriteBuffer(buffer *buf.Buffer) error {
 		maskBytes(*(*[4]byte)(header[1+payloadBitLength:]), 0, data)
 		maskBytes(*(*[4]byte)(header[1+payloadBitLength:]), 0, data)
 	}
 	}
 
 
-	return common.Error(w.Conn.NetConn().Write(buffer.Bytes()))
+	return w.writer.WriteBuffer(buffer)
+}
+
+func (w *Writer) Upstream() any {
+	return w.Conn.NetConn()
+}
+
+func (w *Writer) FrontHeadroom() int {
+	return frontHeadroom
 }
 }