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 }