mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttpupgrade"
|
||||
"github.com/sagernet/sing-box/transport/v2raykcp"
|
||||
"github.com/sagernet/sing-box/transport/v2raywebsocket"
|
||||
xhttp "github.com/sagernet/sing-box/transport/v2rayxhttp"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -42,6 +43,8 @@ func NewServerTransport(ctx context.Context, logger logger.ContextLogger, option
|
||||
return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler)
|
||||
case C.V2RayTransportTypeXHTTP:
|
||||
return xhttp.NewServer(ctx, logger, options.XHTTPOptions, tlsConfig, handler)
|
||||
case C.V2RayTransportTypeKCP:
|
||||
return v2raykcp.NewServer(ctx, logger, options.KCPOptions, tlsConfig, handler)
|
||||
default:
|
||||
return nil, E.New("unknown transport type: " + options.Type)
|
||||
}
|
||||
@@ -67,6 +70,8 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
|
||||
return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)
|
||||
case C.V2RayTransportTypeXHTTP:
|
||||
return xhttp.NewClient(ctx, dialer, serverAddr, options.XHTTPOptions, tlsConfig)
|
||||
case C.V2RayTransportTypeKCP:
|
||||
return v2raykcp.NewClient(ctx, dialer, serverAddr, options.KCPOptions, tlsConfig)
|
||||
default:
|
||||
return nil, E.New("unknown transport type: " + options.Type)
|
||||
}
|
||||
|
||||
@@ -265,3 +265,14 @@ func DupContext(ctx context.Context) context.Context {
|
||||
}
|
||||
return log.ContextWithID(context.Background(), id)
|
||||
}
|
||||
|
||||
func HWIDContext(ctx context.Context, headers http.Header) context.Context {
|
||||
for key, values := range headers {
|
||||
if strings.ToLower(key) == "x-hwid" {
|
||||
if len(values) != 0 {
|
||||
return context.WithValue(ctx, "hwid", values[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if requestBody != nil {
|
||||
conn = bufio.NewCachedConn(conn, requestBody)
|
||||
}
|
||||
s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
|
||||
s.handler.NewConnectionEx(HWIDContext(DupContext(request.Context()), request.Header), conn, source, M.Socksaddr{}, nil)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
done := make(chan struct{})
|
||||
@@ -141,7 +141,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
NewHTTPConn(request.Body, writer),
|
||||
writer.(http.Flusher),
|
||||
})
|
||||
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) {
|
||||
s.handler.NewConnectionEx(HWIDContext(request.Context(), request.Header), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) {
|
||||
close(done)
|
||||
}))
|
||||
<-done
|
||||
|
||||
@@ -112,7 +112,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
|
||||
return
|
||||
}
|
||||
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
|
||||
s.handler.NewConnectionEx(v2rayhttp.HWIDContext(v2rayhttp.DupContext(request.Context()), request.Header), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
|
||||
}
|
||||
|
||||
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
|
||||
|
||||
128
transport/v2raykcp/config.go
Normal file
128
transport/v2raykcp/config.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
// Config stores the configurations for KCP transport
|
||||
type Config struct {
|
||||
MTU uint32
|
||||
TTI uint32
|
||||
UplinkCapacity uint32
|
||||
DownlinkCapacity uint32
|
||||
Congestion bool
|
||||
ReadBufferSize uint32
|
||||
WriteBufferSize uint32
|
||||
HeaderType string
|
||||
Seed string
|
||||
}
|
||||
|
||||
// NewConfig creates a new Config from options
|
||||
func NewConfig(options option.V2RayKCPOptions) *Config {
|
||||
return &Config{
|
||||
MTU: options.GetMTU(),
|
||||
TTI: options.GetTTI(),
|
||||
UplinkCapacity: options.GetUplinkCapacity(),
|
||||
DownlinkCapacity: options.GetDownlinkCapacity(),
|
||||
Congestion: options.Congestion,
|
||||
ReadBufferSize: options.GetReadBufferSize(),
|
||||
WriteBufferSize: options.GetWriteBufferSize(),
|
||||
HeaderType: options.GetHeaderType(),
|
||||
Seed: options.Seed,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMTUValue returns the value of MTU settings.
|
||||
func (c *Config) GetMTUValue() uint32 {
|
||||
if c == nil || c.MTU == 0 {
|
||||
return 1350
|
||||
}
|
||||
return c.MTU
|
||||
}
|
||||
|
||||
// GetTTIValue returns the value of TTI settings.
|
||||
func (c *Config) GetTTIValue() uint32 {
|
||||
if c == nil || c.TTI == 0 {
|
||||
return 50
|
||||
}
|
||||
return c.TTI
|
||||
}
|
||||
|
||||
// GetUplinkCapacityValue returns the value of UplinkCapacity settings.
|
||||
func (c *Config) GetUplinkCapacityValue() uint32 {
|
||||
if c == nil || c.UplinkCapacity == 0 {
|
||||
return 12
|
||||
}
|
||||
return c.UplinkCapacity
|
||||
}
|
||||
|
||||
// GetDownlinkCapacityValue returns the value of DownlinkCapacity settings.
|
||||
func (c *Config) GetDownlinkCapacityValue() uint32 {
|
||||
if c == nil || c.DownlinkCapacity == 0 {
|
||||
return 100
|
||||
}
|
||||
return c.DownlinkCapacity
|
||||
}
|
||||
|
||||
// GetWriteBufferSize returns the size of WriterBuffer in bytes.
|
||||
func (c *Config) GetWriteBufferSize() uint32 {
|
||||
if c == nil || c.WriteBufferSize == 0 {
|
||||
return 2 * 1024 * 1024
|
||||
}
|
||||
return c.WriteBufferSize * 1024 * 1024
|
||||
}
|
||||
|
||||
// GetReadBufferSize returns the size of ReadBuffer in bytes.
|
||||
func (c *Config) GetReadBufferSize() uint32 {
|
||||
if c == nil || c.ReadBufferSize == 0 {
|
||||
return 2 * 1024 * 1024
|
||||
}
|
||||
return c.ReadBufferSize * 1024 * 1024
|
||||
}
|
||||
|
||||
// GetSecurity returns the security settings.
|
||||
func (c *Config) GetSecurity() (cipher.AEAD, error) {
|
||||
if c.Seed != "" {
|
||||
return NewAEADAESGCMBasedOnSeed(c.Seed), nil
|
||||
}
|
||||
return NewSimpleAuthenticator(), nil
|
||||
}
|
||||
|
||||
// GetHeaderType returns the header type
|
||||
func (c *Config) GetHeaderType() string {
|
||||
if c.HeaderType == "" {
|
||||
return "none"
|
||||
}
|
||||
return c.HeaderType
|
||||
}
|
||||
|
||||
// GetPacketHeader builds a new PacketHeader for this config.
|
||||
func (c *Config) GetPacketHeader() PacketHeader {
|
||||
return NewPacketHeader(c.GetHeaderType())
|
||||
}
|
||||
|
||||
func (c *Config) GetSendingInFlightSize() uint32 {
|
||||
size := c.GetUplinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue())
|
||||
if size < 8 {
|
||||
size = 8
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (c *Config) GetSendingBufferSize() uint32 {
|
||||
return c.GetWriteBufferSize() / c.GetMTUValue()
|
||||
}
|
||||
|
||||
func (c *Config) GetReceivingInFlightSize() uint32 {
|
||||
size := c.GetDownlinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue())
|
||||
if size < 8 {
|
||||
size = 8
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (c *Config) GetReceivingBufferSize() uint32 {
|
||||
return c.GetReadBufferSize() / c.GetMTUValue()
|
||||
}
|
||||
566
transport/v2raykcp/connection.go
Normal file
566
transport/v2raykcp/connection.go
Normal file
@@ -0,0 +1,566 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
// PacketWriter writes low-level UDP packets with obfuscating header and AEAD.
|
||||
// It mirrors v2ray-core's kcp.PacketWriter.
|
||||
type PacketWriter interface {
|
||||
Overhead() int
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// State of the connection
|
||||
type State int32
|
||||
|
||||
const (
|
||||
StateActive State = 0
|
||||
StateReadyToClose State = 1
|
||||
StatePeerClosed State = 2
|
||||
StateTerminating State = 3
|
||||
StatePeerTerminating State = 4
|
||||
StateTerminated State = 5
|
||||
)
|
||||
|
||||
// Is returns true if current State is one of the candidates.
|
||||
func (s State) Is(states ...State) bool {
|
||||
for _, state := range states {
|
||||
if s == state {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func nowMillisec() int64 {
|
||||
now := time.Now()
|
||||
return now.Unix()*1000 + int64(now.Nanosecond()/1000000)
|
||||
}
|
||||
|
||||
// RoundTripInfo stores round trip time information
|
||||
type RoundTripInfo struct {
|
||||
mu sync.RWMutex
|
||||
variation uint32
|
||||
srtt uint32
|
||||
rto uint32
|
||||
minRtt uint32
|
||||
updatedTimestamp uint32
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) UpdatePeerRTO(rto uint32, current uint32) {
|
||||
info.mu.Lock()
|
||||
defer info.mu.Unlock()
|
||||
|
||||
if current-info.updatedTimestamp < 3000 {
|
||||
return
|
||||
}
|
||||
info.updatedTimestamp = current
|
||||
info.rto = rto
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) Update(rtt uint32, current uint32) {
|
||||
if rtt > 0x7FFFFFFF {
|
||||
return
|
||||
}
|
||||
|
||||
info.mu.Lock()
|
||||
defer info.mu.Unlock()
|
||||
|
||||
if info.srtt == 0 {
|
||||
info.srtt = rtt
|
||||
info.variation = rtt / 2
|
||||
} else {
|
||||
delta := rtt - info.srtt
|
||||
if info.srtt > rtt {
|
||||
delta = info.srtt - rtt
|
||||
}
|
||||
info.variation = (3*info.variation + delta) / 4
|
||||
info.srtt = (7*info.srtt + rtt) / 8
|
||||
if info.srtt < info.minRtt {
|
||||
info.srtt = info.minRtt
|
||||
}
|
||||
}
|
||||
|
||||
var rto uint32
|
||||
if info.minRtt < 4*info.variation {
|
||||
rto = info.srtt + 4*info.variation
|
||||
} else {
|
||||
rto = info.srtt + info.variation
|
||||
}
|
||||
|
||||
if rto > 10000 {
|
||||
rto = 10000
|
||||
}
|
||||
info.rto = rto * 5 / 4
|
||||
info.updatedTimestamp = current
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) Timeout() uint32 {
|
||||
info.mu.RLock()
|
||||
defer info.mu.RUnlock()
|
||||
|
||||
if info.rto == 0 {
|
||||
return 100
|
||||
}
|
||||
return info.rto
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) SmoothedTime() uint32 {
|
||||
info.mu.RLock()
|
||||
defer info.mu.RUnlock()
|
||||
|
||||
return info.srtt
|
||||
}
|
||||
|
||||
// ConnMetadata stores connection metadata
|
||||
type ConnMetadata struct {
|
||||
LocalAddr net.Addr
|
||||
RemoteAddr net.Addr
|
||||
Conversation uint16
|
||||
}
|
||||
|
||||
// Connection represents a KCP connection
|
||||
type Connection struct {
|
||||
meta ConnMetadata
|
||||
closer io.Closer
|
||||
rd time.Time
|
||||
wd time.Time
|
||||
since int64
|
||||
dataInput chan struct{}
|
||||
dataOutput chan struct{}
|
||||
Config *Config
|
||||
state int32
|
||||
stateBeginTime uint32
|
||||
lastIncomingTime uint32
|
||||
lastPingTime uint32
|
||||
mss uint32
|
||||
roundTrip *RoundTripInfo
|
||||
receivingWorker *ReceivingWorker
|
||||
sendingWorker *SendingWorker
|
||||
output SegmentWriter
|
||||
dataUpdater *Updater
|
||||
pingUpdater *Updater
|
||||
}
|
||||
|
||||
func NewConnection(meta ConnMetadata, writer PacketWriter, closer io.Closer, config *Config) *Connection {
|
||||
conn := &Connection{
|
||||
meta: meta,
|
||||
closer: closer,
|
||||
since: nowMillisec(),
|
||||
dataInput: make(chan struct{}, 1),
|
||||
dataOutput: make(chan struct{}, 1),
|
||||
Config: config,
|
||||
output: NewSegmentWriter(writer),
|
||||
mss: config.GetMTUValue() - uint32(writer.Overhead()) - uint32(DataSegmentOverhead),
|
||||
roundTrip: &RoundTripInfo{
|
||||
rto: 100,
|
||||
minRtt: config.GetTTIValue(),
|
||||
},
|
||||
}
|
||||
|
||||
conn.receivingWorker = NewReceivingWorker(conn)
|
||||
conn.sendingWorker = NewSendingWorker(conn)
|
||||
|
||||
isTerminating := func() bool {
|
||||
return conn.State().Is(StateTerminating, StateTerminated)
|
||||
}
|
||||
isTerminated := func() bool {
|
||||
return conn.State() == StateTerminated
|
||||
}
|
||||
|
||||
conn.dataUpdater = NewUpdater(
|
||||
config.GetTTIValue(),
|
||||
func() bool {
|
||||
return !isTerminating() && (conn.sendingWorker.UpdateNecessary() || conn.receivingWorker.UpdateNecessary())
|
||||
},
|
||||
isTerminating,
|
||||
conn.updateTask)
|
||||
conn.pingUpdater = NewUpdater(
|
||||
5000,
|
||||
func() bool { return !isTerminated() },
|
||||
isTerminated,
|
||||
conn.updateTask)
|
||||
conn.pingUpdater.WakeUp()
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *Connection) Elapsed() uint32 {
|
||||
return uint32(nowMillisec() - c.since)
|
||||
}
|
||||
|
||||
func (c *Connection) State() State {
|
||||
return State(atomic.LoadInt32(&c.state))
|
||||
}
|
||||
|
||||
func (c *Connection) SetState(state State) {
|
||||
current := c.Elapsed()
|
||||
atomic.StoreInt32(&c.state, int32(state))
|
||||
atomic.StoreUint32(&c.stateBeginTime, current)
|
||||
|
||||
switch state {
|
||||
case StateReadyToClose:
|
||||
c.receivingWorker.CloseRead()
|
||||
case StatePeerClosed:
|
||||
c.sendingWorker.CloseWrite()
|
||||
case StateTerminating:
|
||||
c.receivingWorker.CloseRead()
|
||||
c.sendingWorker.CloseWrite()
|
||||
c.pingUpdater.SetInterval(time.Second)
|
||||
case StatePeerTerminating:
|
||||
c.sendingWorker.CloseWrite()
|
||||
c.pingUpdater.SetInterval(time.Second)
|
||||
case StateTerminated:
|
||||
c.receivingWorker.CloseRead()
|
||||
c.sendingWorker.CloseWrite()
|
||||
c.pingUpdater.SetInterval(time.Second)
|
||||
c.dataUpdater.WakeUp()
|
||||
c.pingUpdater.WakeUp()
|
||||
go c.Terminate()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) Terminate() {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(8 * time.Second)
|
||||
|
||||
if c.closer != nil {
|
||||
c.closer.Close()
|
||||
}
|
||||
if c.sendingWorker != nil {
|
||||
c.sendingWorker.Release()
|
||||
}
|
||||
if c.receivingWorker != nil {
|
||||
c.receivingWorker.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) HandleOption(opt SegmentOption) {
|
||||
if (opt & SegmentOptionClose) == SegmentOptionClose {
|
||||
c.OnPeerClosed()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) OnPeerClosed() {
|
||||
switch c.State() {
|
||||
case StateReadyToClose:
|
||||
c.SetState(StateTerminating)
|
||||
case StateActive:
|
||||
c.SetState(StatePeerClosed)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) Input(segments []Segment) {
|
||||
current := c.Elapsed()
|
||||
atomic.StoreUint32(&c.lastIncomingTime, current)
|
||||
|
||||
for _, s := range segments {
|
||||
if s.Conversation() != c.meta.Conversation {
|
||||
break
|
||||
}
|
||||
|
||||
switch seg := s.(type) {
|
||||
case *DataSegment:
|
||||
c.HandleOption(seg.Option)
|
||||
c.receivingWorker.ProcessSegment(seg)
|
||||
if c.receivingWorker.IsDataAvailable() {
|
||||
select {
|
||||
case c.dataInput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
c.dataUpdater.WakeUp()
|
||||
case *AckSegment:
|
||||
c.HandleOption(seg.Option)
|
||||
c.sendingWorker.ProcessSegment(current, seg, c.roundTrip.Timeout())
|
||||
select {
|
||||
case c.dataOutput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
c.dataUpdater.WakeUp()
|
||||
case *CmdOnlySegment:
|
||||
c.HandleOption(seg.Option)
|
||||
if seg.Command() == CommandTerminate {
|
||||
switch c.State() {
|
||||
case StateActive, StatePeerClosed:
|
||||
c.SetState(StatePeerTerminating)
|
||||
case StateReadyToClose:
|
||||
c.SetState(StateTerminating)
|
||||
case StateTerminating:
|
||||
c.SetState(StateTerminated)
|
||||
}
|
||||
}
|
||||
if seg.Option == SegmentOptionClose || seg.Command() == CommandTerminate {
|
||||
select {
|
||||
case c.dataInput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case c.dataOutput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
c.sendingWorker.ProcessReceivingNext(seg.ReceivingNext)
|
||||
c.receivingWorker.ProcessSendingNext(seg.SendingNext)
|
||||
c.roundTrip.UpdatePeerRTO(seg.PeerRTO, current)
|
||||
seg.Release()
|
||||
default:
|
||||
s.Release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) waitForDataInput() error {
|
||||
for i := 0; i < 16; i++ {
|
||||
select {
|
||||
case <-c.dataInput:
|
||||
return nil
|
||||
default:
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Second * 16
|
||||
if !c.rd.IsZero() {
|
||||
duration = time.Until(c.rd)
|
||||
if duration < 0 {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.dataInput:
|
||||
return nil
|
||||
case <-time.After(duration):
|
||||
if !c.rd.IsZero() && c.rd.Before(time.Now()) {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) Read(b []byte) (int, error) {
|
||||
if c == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
for {
|
||||
if c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
nBytes := c.receivingWorker.Read(b)
|
||||
if nBytes > 0 {
|
||||
c.dataUpdater.WakeUp()
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
if c.State() == StatePeerTerminating {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
if err := c.waitForDataInput(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) waitForDataOutput() error {
|
||||
for i := 0; i < 16; i++ {
|
||||
select {
|
||||
case <-c.dataOutput:
|
||||
return nil
|
||||
default:
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Second * 16
|
||||
if !c.wd.IsZero() {
|
||||
duration = time.Until(c.wd)
|
||||
if duration < 0 {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.dataOutput:
|
||||
return nil
|
||||
case <-time.After(duration):
|
||||
if !c.wd.IsZero() && c.wd.Before(time.Now()) {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) Write(b []byte) (int, error) {
|
||||
if c.State() != StateActive {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
totalWritten := 0
|
||||
reader := bytes.NewReader(b)
|
||||
|
||||
for reader.Len() > 0 {
|
||||
buffer := buf.New()
|
||||
n, _ := buffer.ReadFrom(io.LimitReader(reader, int64(c.mss)))
|
||||
if n == 0 {
|
||||
buffer.Release()
|
||||
break
|
||||
}
|
||||
|
||||
for !c.sendingWorker.Push(buffer) {
|
||||
if c.State() != StateActive {
|
||||
buffer.Release()
|
||||
return totalWritten, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
c.dataUpdater.WakeUp()
|
||||
|
||||
if err := c.waitForDataOutput(); err != nil {
|
||||
buffer.Release()
|
||||
return totalWritten, err
|
||||
}
|
||||
}
|
||||
|
||||
totalWritten += int(n)
|
||||
}
|
||||
|
||||
c.dataUpdater.WakeUp()
|
||||
return totalWritten, nil
|
||||
}
|
||||
|
||||
func (c *Connection) updateTask() {
|
||||
current := c.Elapsed()
|
||||
|
||||
if c.State() == StateTerminated {
|
||||
return
|
||||
}
|
||||
if c.State() == StateActive && current-atomic.LoadUint32(&c.lastIncomingTime) >= 30000 {
|
||||
_ = c.Close()
|
||||
}
|
||||
if c.State() == StateReadyToClose && c.sendingWorker.IsEmpty() {
|
||||
c.SetState(StateTerminating)
|
||||
}
|
||||
if c.State() == StateTerminating {
|
||||
if current-atomic.LoadUint32(&c.stateBeginTime) > 8000 {
|
||||
c.SetState(StateTerminated)
|
||||
} else {
|
||||
c.Ping(current, CommandTerminate)
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.State() == StatePeerTerminating && current-atomic.LoadUint32(&c.stateBeginTime) > 4000 {
|
||||
c.SetState(StateTerminating)
|
||||
}
|
||||
if c.State() == StateReadyToClose && current-atomic.LoadUint32(&c.stateBeginTime) > 15000 {
|
||||
c.SetState(StateTerminating)
|
||||
}
|
||||
|
||||
c.receivingWorker.Flush(current)
|
||||
c.sendingWorker.Flush(current)
|
||||
|
||||
if current-atomic.LoadUint32(&c.lastPingTime) >= 3000 {
|
||||
c.Ping(current, CommandPing)
|
||||
}
|
||||
|
||||
select {
|
||||
case c.dataOutput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) Close() error {
|
||||
if c == nil {
|
||||
return ErrClosedConnection
|
||||
}
|
||||
|
||||
select {
|
||||
case c.dataInput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case c.dataOutput <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
switch c.State() {
|
||||
case StateReadyToClose, StateTerminating, StateTerminated:
|
||||
return ErrClosedConnection
|
||||
case StateActive:
|
||||
c.SetState(StateReadyToClose)
|
||||
case StatePeerClosed:
|
||||
c.SetState(StateTerminating)
|
||||
case StatePeerTerminating:
|
||||
c.SetState(StateTerminated)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connection) LocalAddr() net.Addr {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.meta.LocalAddr
|
||||
}
|
||||
|
||||
func (c *Connection) RemoteAddr() net.Addr {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.meta.RemoteAddr
|
||||
}
|
||||
|
||||
func (c *Connection) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.SetWriteDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connection) SetReadDeadline(t time.Time) error {
|
||||
if c == nil {
|
||||
return ErrClosedConnection
|
||||
}
|
||||
c.rd = t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connection) SetWriteDeadline(t time.Time) error {
|
||||
if c == nil {
|
||||
return ErrClosedConnection
|
||||
}
|
||||
c.wd = t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Connection) Ping(current uint32, cmd Command) {
|
||||
seg := NewCmdOnlySegment()
|
||||
seg.Conv = c.meta.Conversation
|
||||
seg.Cmd = cmd
|
||||
seg.SendingNext = c.sendingWorker.FirstUnacknowledged()
|
||||
seg.ReceivingNext = c.receivingWorker.NextNumber()
|
||||
seg.PeerRTO = c.roundTrip.Timeout()
|
||||
if c.State() == StateReadyToClose {
|
||||
seg.Option = SegmentOptionClose
|
||||
}
|
||||
c.output.Write(seg)
|
||||
atomic.StoreUint32(&c.lastPingTime, current)
|
||||
seg.Release()
|
||||
}
|
||||
109
transport/v2raykcp/crypt.go
Normal file
109
transport/v2raykcp/crypt.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"hash/fnv"
|
||||
)
|
||||
|
||||
// SimpleAuthenticator is a legacy AEAD used for KCP encryption.
|
||||
type SimpleAuthenticator struct{}
|
||||
|
||||
// NewSimpleAuthenticator creates a new SimpleAuthenticator
|
||||
func NewSimpleAuthenticator() cipher.AEAD {
|
||||
return &SimpleAuthenticator{}
|
||||
}
|
||||
|
||||
// NonceSize implements cipher.AEAD.NonceSize().
|
||||
func (*SimpleAuthenticator) NonceSize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Overhead implements cipher.AEAD.Overhead().
|
||||
func (*SimpleAuthenticator) Overhead() int {
|
||||
return 6
|
||||
}
|
||||
|
||||
// Seal implements cipher.AEAD.Seal().
|
||||
func (a *SimpleAuthenticator) Seal(dst, nonce, plain, extra []byte) []byte {
|
||||
dst = append(dst, 0, 0, 0, 0, 0, 0) // 4 bytes for hash, and then 2 bytes for length
|
||||
binary.BigEndian.PutUint16(dst[4:], uint16(len(plain)))
|
||||
dst = append(dst, plain...)
|
||||
|
||||
fnvHash := fnv.New32a()
|
||||
fnvHash.Write(dst[4:])
|
||||
fnvHash.Sum(dst[:0])
|
||||
|
||||
dstLen := len(dst)
|
||||
xtra := 4 - dstLen%4
|
||||
if xtra != 4 {
|
||||
dst = append(dst, make([]byte, xtra)...)
|
||||
}
|
||||
xorfwd(dst)
|
||||
if xtra != 4 {
|
||||
dst = dst[:dstLen]
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Open implements cipher.AEAD.Open().
|
||||
func (a *SimpleAuthenticator) Open(dst, nonce, cipherText, extra []byte) ([]byte, error) {
|
||||
dst = append(dst, cipherText...)
|
||||
dstLen := len(dst)
|
||||
xtra := 4 - dstLen%4
|
||||
if xtra != 4 {
|
||||
dst = append(dst, make([]byte, xtra)...)
|
||||
}
|
||||
xorbkd(dst)
|
||||
if xtra != 4 {
|
||||
dst = dst[:dstLen]
|
||||
}
|
||||
|
||||
fnvHash := fnv.New32a()
|
||||
fnvHash.Write(dst[4:])
|
||||
if binary.BigEndian.Uint32(dst[:4]) != fnvHash.Sum32() {
|
||||
return nil, newError("invalid auth")
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint16(dst[4:6])
|
||||
if len(dst)-6 != int(length) {
|
||||
return nil, newError("invalid auth")
|
||||
}
|
||||
|
||||
return dst[6:], nil
|
||||
}
|
||||
|
||||
// xorfwd performs XOR forwards in words, x[i] ^= x[i-4], i from 0 to len.
|
||||
func xorfwd(b []byte) {
|
||||
for i := 4; i < len(b); i++ {
|
||||
b[i] ^= b[i-4]
|
||||
}
|
||||
}
|
||||
|
||||
// xorbkd performs XOR backwards in words, x[i] ^= x[i-4], i from len to 0.
|
||||
func xorbkd(b []byte) {
|
||||
for i := len(b) - 1; i >= 4; i-- {
|
||||
b[i] ^= b[i-4]
|
||||
}
|
||||
}
|
||||
|
||||
// NewAEADAESGCMBasedOnSeed creates a new AES-GCM AEAD based on a seed
|
||||
func NewAEADAESGCMBasedOnSeed(seed string) cipher.AEAD {
|
||||
// Use SHA256 to hash the seed
|
||||
hashedSeed := sha256.Sum256([]byte(seed))
|
||||
|
||||
// Use first 16 bytes as AES-128 key
|
||||
block, err := aes.NewCipher(hashedSeed[:16])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return gcm
|
||||
}
|
||||
231
transport/v2raykcp/dialer.go
Normal file
231
transport/v2raykcp/dialer.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayClientTransport = (*Client)(nil)
|
||||
|
||||
type Client struct {
|
||||
ctx context.Context
|
||||
dialer N.Dialer
|
||||
serverAddr M.Socksaddr
|
||||
config *Config
|
||||
tlsConfig tls.Config
|
||||
}
|
||||
|
||||
func NewClient(
|
||||
ctx context.Context,
|
||||
dialer N.Dialer,
|
||||
serverAddr M.Socksaddr,
|
||||
options option.V2RayKCPOptions,
|
||||
tlsConfig tls.Config,
|
||||
) (adapter.V2RayClientTransport, error) {
|
||||
return &Client{
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
config: NewConfig(options),
|
||||
tlsConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
// Dial UDP connection
|
||||
udpConn, err := c.dialer.DialContext(ctx, N.NetworkUDP, c.serverAddr)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "dial UDP")
|
||||
}
|
||||
|
||||
// Wrap as PacketConn
|
||||
packetConn := bufio.NewUnbindPacketConn(udpConn)
|
||||
|
||||
// Generate conversation ID
|
||||
var convID uint16
|
||||
binary.Read(rand.Reader, binary.BigEndian, &convID)
|
||||
|
||||
// Create KCP connection
|
||||
kcpConn, err := c.createConnection(ctx, packetConn, c.serverAddr.UDPAddr(), convID)
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
return nil, E.Cause(err, "create KCP connection")
|
||||
}
|
||||
|
||||
// Wrap with TLS if configured
|
||||
if c.tlsConfig != nil {
|
||||
tlsConn, err := tls.ClientHandshake(ctx, kcpConn, c.tlsConfig)
|
||||
if err != nil {
|
||||
kcpConn.Close()
|
||||
return nil, E.Cause(err, "TLS handshake")
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
return kcpConn, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) createConnection(ctx context.Context, conn N.PacketConn, remoteAddr *net.UDPAddr, convID uint16) (*Connection, error) {
|
||||
security, err := c.config.GetSecurity()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "get security")
|
||||
}
|
||||
|
||||
// Create packet header
|
||||
header := c.config.GetPacketHeader()
|
||||
|
||||
// Create packet writer
|
||||
writer := &kcpPacketWriter{
|
||||
conn: conn,
|
||||
remoteAddr: remoteAddr,
|
||||
header: header,
|
||||
security: security,
|
||||
}
|
||||
|
||||
// Create packet reader
|
||||
reader := &kcpPacketReader{
|
||||
security: security,
|
||||
headerSize: HeaderSize(c.config.GetHeaderType()),
|
||||
}
|
||||
|
||||
// Create connection metadata
|
||||
meta := ConnMetadata{
|
||||
LocalAddr: conn.LocalAddr(),
|
||||
RemoteAddr: remoteAddr,
|
||||
Conversation: convID,
|
||||
}
|
||||
|
||||
// Create KCP connection
|
||||
kcpConn := NewConnection(meta, writer, conn, c.config)
|
||||
|
||||
// Start reading goroutine
|
||||
go c.readLoop(ctx, conn, reader, kcpConn)
|
||||
|
||||
return kcpConn, nil
|
||||
}
|
||||
|
||||
func (c *Client) readLoop(ctx context.Context, conn N.PacketConn, reader *kcpPacketReader, kcpConn *Connection) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
buffer := buf.New()
|
||||
_, err := conn.ReadPacket(buffer)
|
||||
if err != nil {
|
||||
buffer.Release()
|
||||
return
|
||||
}
|
||||
|
||||
segments := reader.Read(buffer.Bytes())
|
||||
buffer.Release()
|
||||
|
||||
if len(segments) > 0 {
|
||||
kcpConn.Input(segments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type kcpPacketWriter struct {
|
||||
conn N.PacketConn
|
||||
remoteAddr *net.UDPAddr
|
||||
header PacketHeader
|
||||
security cipher.AEAD
|
||||
}
|
||||
|
||||
func (w *kcpPacketWriter) Overhead() int {
|
||||
overhead := 0
|
||||
if w.header != nil {
|
||||
overhead += w.header.Size()
|
||||
}
|
||||
if w.security != nil {
|
||||
overhead += w.security.Overhead()
|
||||
}
|
||||
return overhead
|
||||
}
|
||||
|
||||
func (w *kcpPacketWriter) Write(b []byte) (int, error) {
|
||||
packet := buf.New()
|
||||
defer packet.Release()
|
||||
|
||||
if w.header != nil {
|
||||
headerBytes := packet.Extend(w.header.Size())
|
||||
w.header.Serialize(headerBytes)
|
||||
}
|
||||
|
||||
if w.security != nil {
|
||||
nonceSize := w.security.NonceSize()
|
||||
nonce := packet.Extend(nonceSize)
|
||||
common.Must1(rand.Read(nonce))
|
||||
|
||||
encrypted := w.security.Seal(nil, nonce, b, nil)
|
||||
packet.Write(encrypted)
|
||||
} else {
|
||||
packet.Write(b)
|
||||
}
|
||||
|
||||
destAddr := M.SocksaddrFromNet(w.remoteAddr)
|
||||
err := w.conn.WritePacket(packet, destAddr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
type kcpPacketReader struct {
|
||||
security cipher.AEAD
|
||||
headerSize int
|
||||
}
|
||||
|
||||
func (r *kcpPacketReader) Read(b []byte) []Segment {
|
||||
if r.headerSize > 0 {
|
||||
if len(b) <= r.headerSize {
|
||||
return nil
|
||||
}
|
||||
b = b[r.headerSize:]
|
||||
}
|
||||
|
||||
if r.security != nil {
|
||||
nonceSize := r.security.NonceSize()
|
||||
overhead := r.security.Overhead()
|
||||
if len(b) <= nonceSize+overhead {
|
||||
return nil
|
||||
}
|
||||
out, err := r.security.Open(nil, b[:nonceSize], b[nonceSize:], nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
b = out
|
||||
}
|
||||
|
||||
var result []Segment
|
||||
for len(b) > 0 {
|
||||
seg, extra := ReadSegment(b)
|
||||
if seg == nil {
|
||||
break
|
||||
}
|
||||
result = append(result, seg)
|
||||
b = extra
|
||||
}
|
||||
return result
|
||||
}
|
||||
29
transport/v2raykcp/errors.go
Normal file
29
transport/v2raykcp/errors.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package v2raykcp
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrIOTimeout is returned when I/O operation times out
|
||||
ErrIOTimeout = errors.New("i/o timeout")
|
||||
// ErrClosedListener is returned when listener is closed
|
||||
ErrClosedListener = errors.New("listener closed")
|
||||
// ErrClosedConnection is returned when connection is closed
|
||||
ErrClosedConnection = errors.New("connection closed")
|
||||
)
|
||||
|
||||
func newError(values ...interface{}) error {
|
||||
return errors.New(toString(values...))
|
||||
}
|
||||
|
||||
func toString(values ...interface{}) string {
|
||||
result := ""
|
||||
for _, value := range values {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
result += v
|
||||
case error:
|
||||
result += v.Error()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
202
transport/v2raykcp/header.go
Normal file
202
transport/v2raykcp/header.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// used only by KCP to add an obfuscating header before encrypted payload.
|
||||
type PacketHeader interface {
|
||||
Size() int
|
||||
Serialize([]byte)
|
||||
}
|
||||
|
||||
// NewPacketHeader creates a new PacketHeader instance for the given header type.
|
||||
// Supported values: none, srtp, utp, wechat-video,
|
||||
// dtls, wireguard. Unknown types fall back to no header.
|
||||
func NewPacketHeader(headerType string) PacketHeader {
|
||||
switch headerType {
|
||||
case "srtp":
|
||||
return newSRTPHeader()
|
||||
case "utp":
|
||||
return newUTPHeader()
|
||||
case "wechat-video":
|
||||
return newWechatVideoHeader()
|
||||
case "dtls":
|
||||
return newDTLSHeader()
|
||||
case "wireguard":
|
||||
return newWireguardHeader()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// HeaderSize returns the byte size of the header for the given type.
|
||||
func HeaderSize(headerType string) int {
|
||||
switch headerType {
|
||||
case "srtp", "utp", "wireguard":
|
||||
return 4
|
||||
case "wechat-video", "dtls":
|
||||
return 13
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// ----- SRTP -----
|
||||
|
||||
type srtpHeader struct {
|
||||
header uint16
|
||||
number uint16
|
||||
}
|
||||
|
||||
func newSRTPHeader() *srtpHeader {
|
||||
return &srtpHeader{
|
||||
header: 0xB5E8,
|
||||
number: randomUint16(),
|
||||
}
|
||||
}
|
||||
|
||||
func (*srtpHeader) Size() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (s *srtpHeader) Serialize(b []byte) {
|
||||
s.number++
|
||||
binary.BigEndian.PutUint16(b, s.header)
|
||||
binary.BigEndian.PutUint16(b[2:], s.number)
|
||||
}
|
||||
|
||||
// ----- UTP -----
|
||||
|
||||
type utpHeader struct {
|
||||
header byte
|
||||
extension byte
|
||||
connectionID uint16
|
||||
}
|
||||
|
||||
func newUTPHeader() *utpHeader {
|
||||
return &utpHeader{
|
||||
header: 1,
|
||||
extension: 0,
|
||||
connectionID: randomUint16(),
|
||||
}
|
||||
}
|
||||
|
||||
func (*utpHeader) Size() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (u *utpHeader) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, u.connectionID)
|
||||
b[2] = u.header
|
||||
b[3] = u.extension
|
||||
}
|
||||
|
||||
// ----- WeChat Video -----
|
||||
|
||||
type wechatVideoHeader struct {
|
||||
sn uint32
|
||||
}
|
||||
|
||||
func newWechatVideoHeader() *wechatVideoHeader {
|
||||
return &wechatVideoHeader{
|
||||
sn: randomUint32(),
|
||||
}
|
||||
}
|
||||
|
||||
func (*wechatVideoHeader) Size() int {
|
||||
return 13
|
||||
}
|
||||
|
||||
func (vc *wechatVideoHeader) Serialize(b []byte) {
|
||||
vc.sn++
|
||||
b[0] = 0xa1
|
||||
b[1] = 0x08
|
||||
binary.BigEndian.PutUint32(b[2:], vc.sn)
|
||||
b[6] = 0x00
|
||||
b[7] = 0x10
|
||||
b[8] = 0x11
|
||||
b[9] = 0x18
|
||||
b[10] = 0x30
|
||||
b[11] = 0x22
|
||||
b[12] = 0x30
|
||||
}
|
||||
|
||||
// ----- DTLS -----
|
||||
|
||||
type dtlsHeader struct {
|
||||
epoch uint16
|
||||
length uint16
|
||||
sequence uint32
|
||||
}
|
||||
|
||||
func newDTLSHeader() *dtlsHeader {
|
||||
return &dtlsHeader{
|
||||
epoch: randomUint16(),
|
||||
sequence: 0,
|
||||
length: 17,
|
||||
}
|
||||
}
|
||||
|
||||
func (*dtlsHeader) Size() int {
|
||||
return 13
|
||||
}
|
||||
|
||||
func (d *dtlsHeader) Serialize(b []byte) {
|
||||
b[0] = 23 // application data
|
||||
b[1] = 254
|
||||
b[2] = 253
|
||||
b[3] = byte(d.epoch >> 8)
|
||||
b[4] = byte(d.epoch)
|
||||
b[5] = 0
|
||||
b[6] = 0
|
||||
b[7] = byte(d.sequence >> 24)
|
||||
b[8] = byte(d.sequence >> 16)
|
||||
b[9] = byte(d.sequence >> 8)
|
||||
b[10] = byte(d.sequence)
|
||||
d.sequence++
|
||||
b[11] = byte(d.length >> 8)
|
||||
b[12] = byte(d.length)
|
||||
d.length += 17
|
||||
if d.length > 100 {
|
||||
d.length -= 50
|
||||
}
|
||||
}
|
||||
|
||||
// ----- WireGuard -----
|
||||
|
||||
type wireguardHeader struct{}
|
||||
|
||||
func newWireguardHeader() *wireguardHeader {
|
||||
return &wireguardHeader{}
|
||||
}
|
||||
|
||||
func (*wireguardHeader) Size() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (*wireguardHeader) Serialize(b []byte) {
|
||||
b[0] = 0x04
|
||||
b[1] = 0x00
|
||||
b[2] = 0x00
|
||||
b[3] = 0x00
|
||||
}
|
||||
|
||||
// ----- helpers -----
|
||||
|
||||
func randomUint16() uint16 {
|
||||
var b [2]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return 0
|
||||
}
|
||||
return binary.BigEndian.Uint16(b[:])
|
||||
}
|
||||
|
||||
func randomUint32() uint32 {
|
||||
var b [4]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return 0
|
||||
}
|
||||
return binary.BigEndian.Uint32(b[:])
|
||||
}
|
||||
227
transport/v2raykcp/listener.go
Normal file
227
transport/v2raykcp/listener.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
config *Config
|
||||
tlsConfig tls.ServerConfig
|
||||
handler adapter.V2RayServerTransportHandler
|
||||
listener *net.UDPConn
|
||||
sessions sync.Map // map[ConnectionID]*Connection
|
||||
security cipher.AEAD
|
||||
headerSize int
|
||||
}
|
||||
|
||||
type ConnectionID struct {
|
||||
Remote string
|
||||
Port uint16
|
||||
Conv uint16
|
||||
}
|
||||
|
||||
func NewServer(
|
||||
ctx context.Context,
|
||||
logger logger.ContextLogger,
|
||||
options option.V2RayKCPOptions,
|
||||
tlsConfig tls.ServerConfig,
|
||||
handler adapter.V2RayServerTransportHandler,
|
||||
) (adapter.V2RayServerTransport, error) {
|
||||
config := NewConfig(options)
|
||||
security, err := config.GetSecurity()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "get security")
|
||||
}
|
||||
|
||||
return &Server{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
config: config,
|
||||
tlsConfig: tlsConfig,
|
||||
handler: handler,
|
||||
security: security,
|
||||
headerSize: HeaderSize(config.GetHeaderType()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Network() []string {
|
||||
return []string{N.NetworkUDP}
|
||||
}
|
||||
|
||||
func (s *Server) Serve(listener net.Listener) error {
|
||||
return E.New("KCP server requires ServePacket")
|
||||
}
|
||||
|
||||
func (s *Server) ServePacket(listener net.PacketConn) error {
|
||||
udpConn, ok := listener.(*net.UDPConn)
|
||||
if !ok {
|
||||
return E.New("KCP requires UDP listener")
|
||||
}
|
||||
|
||||
s.listener = udpConn
|
||||
s.logger.Info("KCP server started")
|
||||
|
||||
buffer := make([]byte, 2048)
|
||||
for {
|
||||
n, remoteAddr, err := udpConn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
if E.IsClosed(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
go s.handlePacket(buffer[:n], remoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handlePacket(data []byte, remoteAddr net.Addr) {
|
||||
reader := &kcpPacketReader{
|
||||
security: s.security,
|
||||
headerSize: s.headerSize,
|
||||
}
|
||||
|
||||
segments := reader.Read(data)
|
||||
if len(segments) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
firstSeg := segments[0]
|
||||
conv := firstSeg.Conversation()
|
||||
cmd := firstSeg.Command()
|
||||
|
||||
udpAddr, ok := remoteAddr.(*net.UDPAddr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
connID := ConnectionID{
|
||||
Remote: udpAddr.IP.String(),
|
||||
Port: uint16(udpAddr.Port),
|
||||
Conv: conv,
|
||||
}
|
||||
|
||||
value, exists := s.sessions.Load(connID)
|
||||
if !exists {
|
||||
if cmd == CommandTerminate {
|
||||
return
|
||||
}
|
||||
|
||||
// Create new connection
|
||||
writer := &serverPacketWriter{
|
||||
conn: s.listener,
|
||||
remoteAddr: udpAddr,
|
||||
server: s,
|
||||
connID: connID,
|
||||
header: s.config.GetPacketHeader(),
|
||||
security: s.security,
|
||||
}
|
||||
|
||||
meta := ConnMetadata{
|
||||
LocalAddr: s.listener.LocalAddr(),
|
||||
RemoteAddr: udpAddr,
|
||||
Conversation: conv,
|
||||
}
|
||||
|
||||
kcpConn := NewConnection(meta, writer, writer, s.config)
|
||||
s.sessions.Store(connID, kcpConn)
|
||||
|
||||
var netConn net.Conn = kcpConn
|
||||
if s.tlsConfig != nil {
|
||||
tlsConn, err := tls.ServerHandshake(s.ctx, kcpConn, s.tlsConfig)
|
||||
if err != nil {
|
||||
kcpConn.Close()
|
||||
s.sessions.Delete(connID)
|
||||
return
|
||||
}
|
||||
netConn = tlsConn
|
||||
}
|
||||
|
||||
source := M.SocksaddrFromNet(remoteAddr)
|
||||
go s.handler.NewConnectionEx(s.ctx, netConn, source, M.Socksaddr{}, nil)
|
||||
|
||||
kcpConn.Input(segments)
|
||||
} else {
|
||||
conn := value.(*Connection)
|
||||
conn.Input(segments)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
s.sessions.Range(func(key, value interface{}) bool {
|
||||
conn := value.(*Connection)
|
||||
conn.Close()
|
||||
return true
|
||||
})
|
||||
if s.listener != nil {
|
||||
return s.listener.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type serverPacketWriter struct {
|
||||
conn *net.UDPConn
|
||||
remoteAddr *net.UDPAddr
|
||||
server *Server
|
||||
connID ConnectionID
|
||||
header PacketHeader
|
||||
security cipher.AEAD
|
||||
}
|
||||
|
||||
func (w *serverPacketWriter) Overhead() int {
|
||||
overhead := 0
|
||||
if w.header != nil {
|
||||
overhead += w.header.Size()
|
||||
}
|
||||
if w.security != nil {
|
||||
overhead += w.security.Overhead()
|
||||
}
|
||||
return overhead
|
||||
}
|
||||
|
||||
func (w *serverPacketWriter) Write(b []byte) (int, error) {
|
||||
buffer := buf.New()
|
||||
defer buffer.Release()
|
||||
|
||||
if w.header != nil {
|
||||
headerBytes := buffer.Extend(w.header.Size())
|
||||
w.header.Serialize(headerBytes)
|
||||
}
|
||||
|
||||
if w.security != nil {
|
||||
nonceSize := w.security.NonceSize()
|
||||
nonce := buffer.Extend(nonceSize)
|
||||
common.Must1(rand.Read(nonce))
|
||||
|
||||
encrypted := w.security.Seal(nil, nonce, b, nil)
|
||||
buffer.Write(encrypted)
|
||||
} else {
|
||||
buffer.Write(b)
|
||||
}
|
||||
|
||||
_, err := w.conn.WriteTo(buffer.Bytes(), w.remoteAddr)
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (w *serverPacketWriter) Close() error {
|
||||
w.server.sessions.Delete(w.connID)
|
||||
return nil
|
||||
}
|
||||
52
transport/v2raykcp/multi_buffer.go
Normal file
52
transport/v2raykcp/multi_buffer.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package v2raykcp
|
||||
|
||||
import "github.com/sagernet/sing/common/buf"
|
||||
|
||||
// MultiBuffer is a list of buf.Buffer. The order of Buffer matters.
|
||||
type MultiBuffer []*buf.Buffer
|
||||
|
||||
// ReleaseMulti releases all content of the MultiBuffer and returns an empty MultiBuffer.
|
||||
func ReleaseMulti(mb MultiBuffer) MultiBuffer {
|
||||
for i := range mb {
|
||||
mb[i].Release()
|
||||
mb[i] = nil
|
||||
}
|
||||
return mb[:0]
|
||||
}
|
||||
|
||||
// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer.
|
||||
// It returns the new MultiBuffer leftover and number of bytes written into the input byte slice.
|
||||
func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) {
|
||||
totalBytes := 0
|
||||
endIndex := -1
|
||||
for i := range mb {
|
||||
pBuffer := mb[i]
|
||||
nBytes, _ := pBuffer.Read(b)
|
||||
totalBytes += nBytes
|
||||
b = b[nBytes:]
|
||||
if !pBuffer.IsEmpty() {
|
||||
endIndex = i
|
||||
break
|
||||
}
|
||||
pBuffer.Release()
|
||||
mb[i] = nil
|
||||
}
|
||||
|
||||
if endIndex == -1 {
|
||||
mb = mb[:0]
|
||||
} else {
|
||||
mb = mb[endIndex:]
|
||||
}
|
||||
|
||||
return mb, totalBytes
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the MultiBuffer has no content.
|
||||
func (mb MultiBuffer) IsEmpty() bool {
|
||||
for _, b := range mb {
|
||||
if !b.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
36
transport/v2raykcp/output.go
Normal file
36
transport/v2raykcp/output.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SegmentWriter interface {
|
||||
Write(Segment) error
|
||||
}
|
||||
|
||||
type SimpleSegmentWriter struct {
|
||||
sync.Mutex
|
||||
buffer []byte
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewSegmentWriter(writer io.Writer) SegmentWriter {
|
||||
return &SimpleSegmentWriter{
|
||||
buffer: make([]byte, 2048),
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SimpleSegmentWriter) Write(seg Segment) error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
segSize := seg.ByteSize()
|
||||
if int(segSize) > len(w.buffer) {
|
||||
w.buffer = make([]byte, segSize)
|
||||
}
|
||||
seg.Serialize(w.buffer[:segSize])
|
||||
_, err := w.writer.Write(w.buffer[:segSize])
|
||||
return err
|
||||
}
|
||||
254
transport/v2raykcp/receiving.go
Normal file
254
transport/v2raykcp/receiving.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package v2raykcp
|
||||
|
||||
import "sync"
|
||||
|
||||
type ReceivingWindow struct {
|
||||
cache map[uint32]*DataSegment
|
||||
}
|
||||
|
||||
func NewReceivingWindow() *ReceivingWindow {
|
||||
return &ReceivingWindow{
|
||||
cache: make(map[uint32]*DataSegment),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ReceivingWindow) Set(id uint32, value *DataSegment) bool {
|
||||
_, f := w.cache[id]
|
||||
if f {
|
||||
return false
|
||||
}
|
||||
w.cache[id] = value
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *ReceivingWindow) Has(id uint32) bool {
|
||||
_, f := w.cache[id]
|
||||
return f
|
||||
}
|
||||
|
||||
func (w *ReceivingWindow) Remove(id uint32) *DataSegment {
|
||||
v, f := w.cache[id]
|
||||
if !f {
|
||||
return nil
|
||||
}
|
||||
delete(w.cache, id)
|
||||
return v
|
||||
}
|
||||
|
||||
type AckList struct {
|
||||
writer SegmentWriter
|
||||
timestamps []uint32
|
||||
numbers []uint32
|
||||
nextFlush []uint32
|
||||
|
||||
flushCandidates []uint32
|
||||
dirty bool
|
||||
}
|
||||
|
||||
func NewAckList(writer SegmentWriter) *AckList {
|
||||
return &AckList{
|
||||
writer: writer,
|
||||
timestamps: make([]uint32, 0, 128),
|
||||
numbers: make([]uint32, 0, 128),
|
||||
nextFlush: make([]uint32, 0, 128),
|
||||
flushCandidates: make([]uint32, 0, 128),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AckList) Add(number uint32, timestamp uint32) {
|
||||
l.timestamps = append(l.timestamps, timestamp)
|
||||
l.numbers = append(l.numbers, number)
|
||||
l.nextFlush = append(l.nextFlush, 0)
|
||||
l.dirty = true
|
||||
}
|
||||
|
||||
func (l *AckList) Clear(una uint32) {
|
||||
count := 0
|
||||
for i := 0; i < len(l.numbers); i++ {
|
||||
if l.numbers[i] < una {
|
||||
continue
|
||||
}
|
||||
if i != count {
|
||||
l.numbers[count] = l.numbers[i]
|
||||
l.timestamps[count] = l.timestamps[i]
|
||||
l.nextFlush[count] = l.nextFlush[i]
|
||||
}
|
||||
count++
|
||||
}
|
||||
if count < len(l.numbers) {
|
||||
l.numbers = l.numbers[:count]
|
||||
l.timestamps = l.timestamps[:count]
|
||||
l.nextFlush = l.nextFlush[:count]
|
||||
l.dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AckList) Flush(current uint32, rto uint32) {
|
||||
l.flushCandidates = l.flushCandidates[:0]
|
||||
|
||||
seg := NewAckSegment()
|
||||
for i := 0; i < len(l.numbers); i++ {
|
||||
if l.nextFlush[i] > current {
|
||||
if len(l.flushCandidates) < cap(l.flushCandidates) {
|
||||
l.flushCandidates = append(l.flushCandidates, l.numbers[i])
|
||||
}
|
||||
continue
|
||||
}
|
||||
seg.PutNumber(l.numbers[i])
|
||||
seg.PutTimestamp(l.timestamps[i])
|
||||
timeout := rto / 2
|
||||
if timeout < 20 {
|
||||
timeout = 20
|
||||
}
|
||||
l.nextFlush[i] = current + timeout
|
||||
|
||||
if seg.IsFull() {
|
||||
l.writer.Write(seg)
|
||||
seg.Release()
|
||||
seg = NewAckSegment()
|
||||
l.dirty = false
|
||||
}
|
||||
}
|
||||
|
||||
if l.dirty || !seg.IsEmpty() {
|
||||
for _, number := range l.flushCandidates {
|
||||
if seg.IsFull() {
|
||||
break
|
||||
}
|
||||
seg.PutNumber(number)
|
||||
}
|
||||
l.writer.Write(seg)
|
||||
l.dirty = false
|
||||
}
|
||||
|
||||
seg.Release()
|
||||
}
|
||||
|
||||
type ReceivingWorker struct {
|
||||
sync.RWMutex
|
||||
conn *Connection
|
||||
leftOver MultiBuffer
|
||||
window *ReceivingWindow
|
||||
acklist *AckList
|
||||
nextNumber uint32
|
||||
windowSize uint32
|
||||
}
|
||||
|
||||
func NewReceivingWorker(kcp *Connection) *ReceivingWorker {
|
||||
worker := &ReceivingWorker{
|
||||
conn: kcp,
|
||||
window: NewReceivingWindow(),
|
||||
windowSize: kcp.Config.GetReceivingInFlightSize(),
|
||||
}
|
||||
worker.acklist = NewAckList(worker)
|
||||
return worker
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Release() {
|
||||
w.Lock()
|
||||
ReleaseMulti(w.leftOver)
|
||||
w.leftOver = nil
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) ProcessSendingNext(number uint32) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.acklist.Clear(number)
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) ProcessSegment(seg *DataSegment) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
number := seg.Number
|
||||
idx := number - w.nextNumber
|
||||
if idx >= w.windowSize {
|
||||
return
|
||||
}
|
||||
w.acklist.Clear(seg.SendingNext)
|
||||
w.acklist.Add(number, seg.Timestamp)
|
||||
|
||||
if !w.window.Set(seg.Number, seg) {
|
||||
seg.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) ReadMultiBuffer() MultiBuffer {
|
||||
if w.leftOver != nil {
|
||||
mb := w.leftOver
|
||||
w.leftOver = nil
|
||||
return mb
|
||||
}
|
||||
|
||||
mb := make(MultiBuffer, 0, 32)
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
for {
|
||||
seg := w.window.Remove(w.nextNumber)
|
||||
if seg == nil {
|
||||
break
|
||||
}
|
||||
w.nextNumber++
|
||||
mb = append(mb, seg.Detach())
|
||||
seg.Release()
|
||||
}
|
||||
|
||||
return mb
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Read(b []byte) int {
|
||||
mb := w.ReadMultiBuffer()
|
||||
if mb.IsEmpty() {
|
||||
return 0
|
||||
}
|
||||
mb, nBytes := SplitBytes(mb, b)
|
||||
if !mb.IsEmpty() {
|
||||
w.leftOver = mb
|
||||
}
|
||||
return nBytes
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) IsDataAvailable() bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
return w.window.Has(w.nextNumber)
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) NextNumber() uint32 {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return w.nextNumber
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Flush(current uint32) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.acklist.Flush(current, w.conn.roundTrip.Timeout())
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Write(seg Segment) error {
|
||||
ackSeg := seg.(*AckSegment)
|
||||
ackSeg.Conv = w.conn.meta.Conversation
|
||||
ackSeg.ReceivingNext = w.nextNumber
|
||||
ackSeg.ReceivingWindow = w.nextNumber + w.windowSize
|
||||
ackSeg.Option = 0
|
||||
if w.conn.State() == StateReadyToClose {
|
||||
ackSeg.Option = SegmentOptionClose
|
||||
}
|
||||
return w.conn.output.Write(ackSeg)
|
||||
}
|
||||
|
||||
func (*ReceivingWorker) CloseRead() {
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) UpdateNecessary() bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return len(w.acklist.numbers) > 0
|
||||
}
|
||||
312
transport/v2raykcp/segment.go
Normal file
312
transport/v2raykcp/segment.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
// Command is a KCP command that indicate the purpose of a Segment.
|
||||
type Command byte
|
||||
|
||||
const (
|
||||
// CommandACK indicates an AckSegment.
|
||||
CommandACK Command = 0
|
||||
// CommandData indicates a DataSegment.
|
||||
CommandData Command = 1
|
||||
// CommandTerminate indicates that peer terminates the connection.
|
||||
CommandTerminate Command = 2
|
||||
// CommandPing indicates a ping.
|
||||
CommandPing Command = 3
|
||||
)
|
||||
|
||||
type SegmentOption byte
|
||||
|
||||
const (
|
||||
SegmentOptionClose SegmentOption = 1
|
||||
)
|
||||
|
||||
type Segment interface {
|
||||
Release()
|
||||
Conversation() uint16
|
||||
Command() Command
|
||||
ByteSize() int32
|
||||
Serialize([]byte)
|
||||
parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte)
|
||||
}
|
||||
|
||||
const (
|
||||
DataSegmentOverhead = 18
|
||||
)
|
||||
|
||||
type DataSegment struct {
|
||||
Conv uint16
|
||||
Option SegmentOption
|
||||
Timestamp uint32
|
||||
Number uint32
|
||||
SendingNext uint32
|
||||
|
||||
payload *buf.Buffer
|
||||
timeout uint32
|
||||
transmit uint32
|
||||
}
|
||||
|
||||
func NewDataSegment() *DataSegment {
|
||||
return new(DataSegment)
|
||||
}
|
||||
|
||||
func (s *DataSegment) parse(conv uint16, cmd Command, opt SegmentOption, data []byte) (bool, []byte) {
|
||||
s.Conv = conv
|
||||
s.Option = opt
|
||||
if len(data) < 15 {
|
||||
return false, nil
|
||||
}
|
||||
s.Timestamp = binary.BigEndian.Uint32(data)
|
||||
data = data[4:]
|
||||
|
||||
s.Number = binary.BigEndian.Uint32(data)
|
||||
data = data[4:]
|
||||
|
||||
s.SendingNext = binary.BigEndian.Uint32(data)
|
||||
data = data[4:]
|
||||
|
||||
dataLen := int(binary.BigEndian.Uint16(data))
|
||||
data = data[2:]
|
||||
|
||||
if len(data) < dataLen {
|
||||
return false, nil
|
||||
}
|
||||
// Ensure we have a payload buffer
|
||||
if s.payload == nil {
|
||||
s.payload = buf.New()
|
||||
}
|
||||
// Clear and write data
|
||||
s.payload.Reset()
|
||||
s.payload.Write(data[:dataLen])
|
||||
data = data[dataLen:]
|
||||
|
||||
return true, data
|
||||
}
|
||||
|
||||
func (s *DataSegment) Conversation() uint16 {
|
||||
return s.Conv
|
||||
}
|
||||
|
||||
func (*DataSegment) Command() Command {
|
||||
return CommandData
|
||||
}
|
||||
|
||||
func (s *DataSegment) Detach() *buf.Buffer {
|
||||
r := s.payload
|
||||
s.payload = nil
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *DataSegment) Data() *buf.Buffer {
|
||||
if s.payload == nil {
|
||||
s.payload = buf.New()
|
||||
}
|
||||
return s.payload
|
||||
}
|
||||
|
||||
func (s *DataSegment) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, s.Conv)
|
||||
b[2] = byte(CommandData)
|
||||
b[3] = byte(s.Option)
|
||||
binary.BigEndian.PutUint32(b[4:], s.Timestamp)
|
||||
binary.BigEndian.PutUint32(b[8:], s.Number)
|
||||
binary.BigEndian.PutUint32(b[12:], s.SendingNext)
|
||||
binary.BigEndian.PutUint16(b[16:], uint16(s.payload.Len()))
|
||||
copy(b[18:], s.payload.Bytes())
|
||||
}
|
||||
|
||||
func (s *DataSegment) ByteSize() int32 {
|
||||
return int32(2 + 1 + 1 + 4 + 4 + 4 + 2 + s.payload.Len())
|
||||
}
|
||||
|
||||
func (s *DataSegment) Release() {
|
||||
if s.payload != nil {
|
||||
s.payload.Release()
|
||||
s.payload = nil
|
||||
}
|
||||
}
|
||||
|
||||
type AckSegment struct {
|
||||
Conv uint16
|
||||
Option SegmentOption
|
||||
ReceivingWindow uint32
|
||||
ReceivingNext uint32
|
||||
Timestamp uint32
|
||||
NumberList []uint32
|
||||
}
|
||||
|
||||
const ackNumberLimit = 128
|
||||
|
||||
func NewAckSegment() *AckSegment {
|
||||
return &AckSegment{
|
||||
NumberList: make([]uint32, 0, ackNumberLimit),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AckSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {
|
||||
s.Conv = conv
|
||||
s.Option = opt
|
||||
if len(buf) < 13 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s.ReceivingWindow = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.ReceivingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.Timestamp = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
count := int(buf[0])
|
||||
buf = buf[1:]
|
||||
|
||||
if len(buf) < count*4 {
|
||||
return false, nil
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
s.PutNumber(binary.BigEndian.Uint32(buf))
|
||||
buf = buf[4:]
|
||||
}
|
||||
|
||||
return true, buf
|
||||
}
|
||||
|
||||
func (s *AckSegment) Conversation() uint16 {
|
||||
return s.Conv
|
||||
}
|
||||
|
||||
func (*AckSegment) Command() Command {
|
||||
return CommandACK
|
||||
}
|
||||
|
||||
func (s *AckSegment) PutTimestamp(timestamp uint32) {
|
||||
if timestamp-s.Timestamp < 0x7FFFFFFF {
|
||||
s.Timestamp = timestamp
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AckSegment) PutNumber(number uint32) {
|
||||
s.NumberList = append(s.NumberList, number)
|
||||
}
|
||||
|
||||
func (s *AckSegment) IsFull() bool {
|
||||
return len(s.NumberList) == ackNumberLimit
|
||||
}
|
||||
|
||||
func (s *AckSegment) IsEmpty() bool {
|
||||
return len(s.NumberList) == 0
|
||||
}
|
||||
|
||||
func (s *AckSegment) ByteSize() int32 {
|
||||
return 2 + 1 + 1 + 4 + 4 + 4 + 1 + int32(len(s.NumberList)*4)
|
||||
}
|
||||
|
||||
func (s *AckSegment) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, s.Conv)
|
||||
b[2] = byte(CommandACK)
|
||||
b[3] = byte(s.Option)
|
||||
binary.BigEndian.PutUint32(b[4:], s.ReceivingWindow)
|
||||
binary.BigEndian.PutUint32(b[8:], s.ReceivingNext)
|
||||
binary.BigEndian.PutUint32(b[12:], s.Timestamp)
|
||||
b[16] = byte(len(s.NumberList))
|
||||
n := 17
|
||||
for _, number := range s.NumberList {
|
||||
binary.BigEndian.PutUint32(b[n:], number)
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AckSegment) Release() {}
|
||||
|
||||
type CmdOnlySegment struct {
|
||||
Conv uint16
|
||||
Cmd Command
|
||||
Option SegmentOption
|
||||
SendingNext uint32
|
||||
ReceivingNext uint32
|
||||
PeerRTO uint32
|
||||
}
|
||||
|
||||
func NewCmdOnlySegment() *CmdOnlySegment {
|
||||
return new(CmdOnlySegment)
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {
|
||||
s.Conv = conv
|
||||
s.Cmd = cmd
|
||||
s.Option = opt
|
||||
|
||||
if len(buf) < 12 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s.SendingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.ReceivingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.PeerRTO = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
return true, buf
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) Conversation() uint16 {
|
||||
return s.Conv
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) Command() Command {
|
||||
return s.Cmd
|
||||
}
|
||||
|
||||
func (*CmdOnlySegment) ByteSize() int32 {
|
||||
return 2 + 1 + 1 + 4 + 4 + 4
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, s.Conv)
|
||||
b[2] = byte(s.Cmd)
|
||||
b[3] = byte(s.Option)
|
||||
binary.BigEndian.PutUint32(b[4:], s.SendingNext)
|
||||
binary.BigEndian.PutUint32(b[8:], s.ReceivingNext)
|
||||
binary.BigEndian.PutUint32(b[12:], s.PeerRTO)
|
||||
}
|
||||
|
||||
func (*CmdOnlySegment) Release() {}
|
||||
|
||||
func ReadSegment(buf []byte) (Segment, []byte) {
|
||||
if len(buf) < 4 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
conv := binary.BigEndian.Uint16(buf)
|
||||
buf = buf[2:]
|
||||
|
||||
cmd := Command(buf[0])
|
||||
opt := SegmentOption(buf[1])
|
||||
buf = buf[2:]
|
||||
|
||||
var seg Segment
|
||||
switch cmd {
|
||||
case CommandData:
|
||||
seg = NewDataSegment()
|
||||
case CommandACK:
|
||||
seg = NewAckSegment()
|
||||
default:
|
||||
seg = NewCmdOnlySegment()
|
||||
}
|
||||
|
||||
valid, extra := seg.parse(conv, cmd, opt, buf)
|
||||
if !valid {
|
||||
return nil, nil
|
||||
}
|
||||
return seg, extra
|
||||
}
|
||||
361
transport/v2raykcp/sending.go
Normal file
361
transport/v2raykcp/sending.go
Normal file
@@ -0,0 +1,361 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
)
|
||||
|
||||
type SendingWindow struct {
|
||||
cache *list.List
|
||||
totalInFlightSize uint32
|
||||
writer SegmentWriter
|
||||
onPacketLoss func(uint32)
|
||||
}
|
||||
|
||||
func NewSendingWindow(writer SegmentWriter, onPacketLoss func(uint32)) *SendingWindow {
|
||||
return &SendingWindow{
|
||||
cache: list.New(),
|
||||
writer: writer,
|
||||
onPacketLoss: onPacketLoss,
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Release() {
|
||||
if sw == nil {
|
||||
return
|
||||
}
|
||||
for sw.cache.Len() > 0 {
|
||||
seg := sw.cache.Front().Value.(*DataSegment)
|
||||
seg.Release()
|
||||
sw.cache.Remove(sw.cache.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Len() uint32 {
|
||||
return uint32(sw.cache.Len())
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) IsEmpty() bool {
|
||||
return sw.cache.Len() == 0
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Push(number uint32, b *buf.Buffer) {
|
||||
seg := NewDataSegment()
|
||||
seg.Number = number
|
||||
seg.payload = b
|
||||
|
||||
sw.cache.PushBack(seg)
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) FirstNumber() uint32 {
|
||||
return sw.cache.Front().Value.(*DataSegment).Number
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Clear(una uint32) {
|
||||
for !sw.IsEmpty() {
|
||||
seg := sw.cache.Front().Value.(*DataSegment)
|
||||
if seg.Number >= una {
|
||||
break
|
||||
}
|
||||
seg.Release()
|
||||
sw.cache.Remove(sw.cache.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) HandleFastAck(number uint32, rto uint32) {
|
||||
if sw.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
sw.Visit(func(seg *DataSegment) bool {
|
||||
if number == seg.Number || number-seg.Number > 0x7FFFFFFF {
|
||||
return false
|
||||
}
|
||||
|
||||
if seg.transmit > 0 && seg.timeout > rto/3 {
|
||||
seg.timeout -= rto / 3
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Visit(visitor func(seg *DataSegment) bool) {
|
||||
if sw.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
for e := sw.cache.Front(); e != nil; e = e.Next() {
|
||||
seg := e.Value.(*DataSegment)
|
||||
if !visitor(seg) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Flush(current uint32, rto uint32, maxInFlightSize uint32) {
|
||||
if sw.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
var lost uint32
|
||||
var inFlightSize uint32
|
||||
|
||||
sw.Visit(func(segment *DataSegment) bool {
|
||||
if current-segment.timeout >= 0x7FFFFFFF {
|
||||
return true
|
||||
}
|
||||
if segment.transmit == 0 {
|
||||
sw.totalInFlightSize++
|
||||
} else {
|
||||
lost++
|
||||
}
|
||||
segment.timeout = current + rto
|
||||
|
||||
segment.Timestamp = current
|
||||
segment.transmit++
|
||||
sw.writer.Write(segment)
|
||||
inFlightSize++
|
||||
return inFlightSize < maxInFlightSize
|
||||
})
|
||||
|
||||
if sw.onPacketLoss != nil && inFlightSize > 0 && sw.totalInFlightSize != 0 {
|
||||
rate := lost * 100 / sw.totalInFlightSize
|
||||
sw.onPacketLoss(rate)
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Remove(number uint32) bool {
|
||||
if sw.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
|
||||
for e := sw.cache.Front(); e != nil; e = e.Next() {
|
||||
seg := e.Value.(*DataSegment)
|
||||
if seg.Number > number {
|
||||
return false
|
||||
} else if seg.Number == number {
|
||||
if sw.totalInFlightSize > 0 {
|
||||
sw.totalInFlightSize--
|
||||
}
|
||||
seg.Release()
|
||||
sw.cache.Remove(e)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type SendingWorker struct {
|
||||
sync.RWMutex
|
||||
conn *Connection
|
||||
window *SendingWindow
|
||||
firstUnacknowledged uint32
|
||||
nextNumber uint32
|
||||
remoteNextNumber uint32
|
||||
controlWindow uint32
|
||||
fastResend uint32
|
||||
windowSize uint32
|
||||
firstUnacknowledgedUpdated bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewSendingWorker(kcp *Connection) *SendingWorker {
|
||||
worker := &SendingWorker{
|
||||
conn: kcp,
|
||||
fastResend: 2,
|
||||
remoteNextNumber: 32,
|
||||
controlWindow: kcp.Config.GetSendingInFlightSize(),
|
||||
windowSize: kcp.Config.GetSendingBufferSize(),
|
||||
}
|
||||
worker.window = NewSendingWindow(worker, worker.OnPacketLoss)
|
||||
return worker
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Release() {
|
||||
w.Lock()
|
||||
w.window.Release()
|
||||
w.closed = true
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) ProcessReceivingNext(nextNumber uint32) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.ProcessReceivingNextWithoutLock(nextNumber)
|
||||
}
|
||||
|
||||
func (w *SendingWorker) ProcessReceivingNextWithoutLock(nextNumber uint32) {
|
||||
w.window.Clear(nextNumber)
|
||||
w.FindFirstUnacknowledged()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) FindFirstUnacknowledged() {
|
||||
first := w.firstUnacknowledged
|
||||
if !w.window.IsEmpty() {
|
||||
w.firstUnacknowledged = w.window.FirstNumber()
|
||||
} else {
|
||||
w.firstUnacknowledged = w.nextNumber
|
||||
}
|
||||
if first != w.firstUnacknowledged {
|
||||
w.firstUnacknowledgedUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) processAck(number uint32) bool {
|
||||
if number-w.firstUnacknowledged > 0x7FFFFFFF || number-w.nextNumber < 0x7FFFFFFF {
|
||||
return false
|
||||
}
|
||||
|
||||
removed := w.window.Remove(number)
|
||||
if removed {
|
||||
w.FindFirstUnacknowledged()
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
func (w *SendingWorker) ProcessSegment(current uint32, seg *AckSegment, rto uint32) {
|
||||
defer seg.Release()
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if w.closed {
|
||||
return
|
||||
}
|
||||
|
||||
if w.remoteNextNumber < seg.ReceivingWindow {
|
||||
w.remoteNextNumber = seg.ReceivingWindow
|
||||
}
|
||||
w.ProcessReceivingNextWithoutLock(seg.ReceivingNext)
|
||||
|
||||
if seg.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxack uint32
|
||||
var maxackRemoved bool
|
||||
for _, number := range seg.NumberList {
|
||||
removed := w.processAck(number)
|
||||
if maxack < number {
|
||||
maxack = number
|
||||
maxackRemoved = removed
|
||||
}
|
||||
}
|
||||
|
||||
if maxackRemoved {
|
||||
w.window.HandleFastAck(maxack, rto)
|
||||
if current-seg.Timestamp < 10000 {
|
||||
w.conn.roundTrip.Update(current-seg.Timestamp, current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Push(b *buf.Buffer) bool {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if w.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
if w.window.Len() > w.windowSize {
|
||||
return false
|
||||
}
|
||||
|
||||
w.window.Push(w.nextNumber, b)
|
||||
w.nextNumber++
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Write(seg Segment) error {
|
||||
dataSeg := seg.(*DataSegment)
|
||||
|
||||
dataSeg.Conv = w.conn.meta.Conversation
|
||||
dataSeg.SendingNext = w.firstUnacknowledged
|
||||
dataSeg.Option = 0
|
||||
if w.conn.State() == StateReadyToClose {
|
||||
dataSeg.Option = SegmentOptionClose
|
||||
}
|
||||
|
||||
return w.conn.output.Write(dataSeg)
|
||||
}
|
||||
|
||||
func (w *SendingWorker) OnPacketLoss(lossRate uint32) {
|
||||
if !w.conn.Config.Congestion || w.conn.roundTrip.Timeout() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if lossRate >= 15 {
|
||||
w.controlWindow = 3 * w.controlWindow / 4
|
||||
} else if lossRate <= 5 {
|
||||
w.controlWindow += w.controlWindow / 4
|
||||
}
|
||||
if w.controlWindow < 16 {
|
||||
w.controlWindow = 16
|
||||
}
|
||||
if w.controlWindow > 2*w.conn.Config.GetSendingInFlightSize() {
|
||||
w.controlWindow = 2 * w.conn.Config.GetSendingInFlightSize()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Flush(current uint32) {
|
||||
w.Lock()
|
||||
|
||||
if w.closed {
|
||||
w.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
cwnd := w.conn.Config.GetSendingInFlightSize()
|
||||
if cwnd > w.remoteNextNumber-w.firstUnacknowledged {
|
||||
cwnd = w.remoteNextNumber - w.firstUnacknowledged
|
||||
}
|
||||
if w.conn.Config.Congestion && cwnd > w.controlWindow {
|
||||
cwnd = w.controlWindow
|
||||
}
|
||||
|
||||
cwnd *= 20
|
||||
|
||||
if !w.window.IsEmpty() {
|
||||
w.window.Flush(current, w.conn.roundTrip.Timeout(), cwnd)
|
||||
w.firstUnacknowledgedUpdated = false
|
||||
}
|
||||
|
||||
updated := w.firstUnacknowledgedUpdated
|
||||
w.firstUnacknowledgedUpdated = false
|
||||
|
||||
w.Unlock()
|
||||
|
||||
if updated {
|
||||
w.conn.Ping(current, CommandPing)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) CloseWrite() {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.window.Clear(0xFFFFFFFF)
|
||||
}
|
||||
|
||||
func (w *SendingWorker) IsEmpty() bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return w.window.IsEmpty()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) UpdateNecessary() bool {
|
||||
return !w.IsEmpty()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) FirstUnacknowledged() uint32 {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return w.firstUnacknowledged
|
||||
}
|
||||
58
transport/v2raykcp/updater.go
Normal file
58
transport/v2raykcp/updater.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package v2raykcp
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
interval int64
|
||||
shouldContinue func() bool
|
||||
shouldTerminate func() bool
|
||||
updateFunc func()
|
||||
notifier chan struct{}
|
||||
}
|
||||
|
||||
func NewUpdater(interval uint32, shouldContinue func() bool, shouldTerminate func() bool, updateFunc func()) *Updater {
|
||||
u := &Updater{
|
||||
interval: int64(time.Duration(interval) * time.Millisecond),
|
||||
shouldContinue: shouldContinue,
|
||||
shouldTerminate: shouldTerminate,
|
||||
updateFunc: updateFunc,
|
||||
notifier: make(chan struct{}, 1),
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *Updater) WakeUp() {
|
||||
select {
|
||||
case u.notifier <- struct{}{}:
|
||||
go u.run()
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) run() {
|
||||
defer func() {
|
||||
<-u.notifier
|
||||
}()
|
||||
|
||||
if u.shouldTerminate() {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(u.Interval())
|
||||
defer ticker.Stop()
|
||||
|
||||
for u.shouldContinue() {
|
||||
u.updateFunc()
|
||||
<-ticker.C
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) Interval() time.Duration {
|
||||
return time.Duration(atomic.LoadInt64(&u.interval))
|
||||
}
|
||||
|
||||
func (u *Updater) SetInterval(d time.Duration) {
|
||||
atomic.StoreInt64(&u.interval, int64(d))
|
||||
}
|
||||
@@ -115,7 +115,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if len(earlyData) > 0 {
|
||||
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
|
||||
}
|
||||
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
|
||||
s.handler.NewConnectionEx(v2rayhttp.HWIDContext(v2rayhttp.DupContext(request.Context()), request.Header), conn, source, M.Socksaddr{}, nil)
|
||||
}
|
||||
|
||||
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
||||
qtls "github.com/sagernet/sing-quic"
|
||||
|
||||
// qtls "github.com/sagernet/sing-quic"
|
||||
@@ -265,7 +266,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if sessionId != "" { // if not stream-one
|
||||
conn.reader = currentSession.uploadQueue
|
||||
}
|
||||
s.handler.NewConnectionEx(request.Context(), &conn, sHttp.SourceAddress(request), M.Socksaddr{}, func(it error) {})
|
||||
s.handler.NewConnectionEx(v2rayhttp.HWIDContext(request.Context(), request.Header), &conn, sHttp.SourceAddress(request), M.Socksaddr{}, func(it error) {})
|
||||
// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned."
|
||||
select {
|
||||
case <-request.Context().Done():
|
||||
|
||||
Reference in New Issue
Block a user