Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport

This commit is contained in:
Sergei Maklagin
2026-02-26 22:44:31 +03:00
parent 287fe834db
commit c0aa3480c5
115 changed files with 12582 additions and 301 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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) {

View 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()
}

View 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
View 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
}

View 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
}

View 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
}

View 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[:])
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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))
}

View File

@@ -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) {

View File

@@ -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():