Add vless outbound and xudp

This commit is contained in:
世界 2022-09-12 21:59:27 +08:00
parent dfb8b5f2fa
commit 38088f28b0
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
12 changed files with 783 additions and 49 deletions

View file

@ -20,6 +20,7 @@ const (
TypeSSH = "ssh"
TypeShadowTLS = "shadowtls"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
)
const (

View file

@ -22,6 +22,7 @@ type _Outbound struct {
SSHOptions SSHOutboundOptions `json:"-"`
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
VLESSOptions VLESSOutboundOptions `json:"-"`
SelectorOptions SelectorOutboundOptions `json:"-"`
}
@ -56,6 +57,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
v = h.ShadowTLSOptions
case C.TypeShadowsocksR:
v = h.ShadowsocksROptions
case C.TypeVLESS:
v = h.VLESSOptions
case C.TypeSelector:
v = h.SelectorOptions
default:
@ -97,6 +100,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
v = &h.ShadowTLSOptions
case C.TypeShadowsocksR:
v = &h.ShadowsocksROptions
case C.TypeVLESS:
v = &h.VLESSOptions
case C.TypeSelector:
v = &h.SelectorOptions
default:

11
option/vless.go Normal file
View file

@ -0,0 +1,11 @@
package option
type VLESSOutboundOptions struct {
DialerOptions
ServerOptions
UUID string `json:"uuid"`
Network NetworkList `json:"network,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"`
PacketEncoding string `json:"packet_encoding,omitempty"`
}

View file

@ -43,6 +43,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
case C.TypeShadowsocksR:
return NewShadowsocksR(ctx, router, logger, options.Tag, options.ShadowsocksROptions)
case C.TypeVLESS:
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
case C.TypeSelector:
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
default:

146
outbound/vless.go Normal file
View file

@ -0,0 +1,146 @@
package outbound
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls"
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/v2ray"
"github.com/sagernet/sing-box/transport/vless"
"github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ adapter.Outbound = (*VLESS)(nil)
type VLESS struct {
myOutboundAdapter
dialer N.Dialer
client *vless.Client
serverAddr M.Socksaddr
tlsConfig tls.Config
transport adapter.V2RayClientTransport
packetAddr bool
xudp bool
}
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) {
outbound := &VLESS{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeVLESS,
network: options.Network.Build(),
router: router,
logger: logger,
tag: tag,
},
dialer: dialer.New(router, options.DialerOptions),
serverAddr: options.ServerOptions.Build(),
}
var err error
if options.TLS != nil {
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
}
if options.Transport != nil {
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
if err != nil {
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
}
}
switch options.PacketEncoding {
case "":
case "packetaddr":
outbound.packetAddr = true
case "xudp":
outbound.xudp = true
default:
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
}
outbound.client, err = vless.NewClient(options.UUID)
if err != nil {
return nil, err
}
return outbound, nil
}
func (h *VLESS) Close() error {
return common.Close(h.transport)
}
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag
metadata.Destination = destination
var conn net.Conn
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
return nil, err
}
switch N.NetworkName(network) {
case N.NetworkTCP:
case N.NetworkUDP:
}
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.DialEarlyConn(conn, destination), nil
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return h.client.DialPacketConn(conn, destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag
metadata.Destination = destination
var conn net.Conn
var err error
if h.transport != nil {
conn, err = h.transport.DialContext(ctx)
} else {
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
if err == nil && h.tlsConfig != nil {
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
}
}
if err != nil {
return nil, err
}
if h.xudp {
return h.client.DialXUDPPacketConn(conn, destination), nil
} else if h.packetAddr {
return packetaddr.NewConn(h.client.DialPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
} else {
return h.client.DialPacketConn(conn, destination), nil
}
}
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewEarlyConnection(ctx, h, conn, metadata)
}
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewPacketConnection(ctx, h, conn, metadata)
}

View file

@ -38,6 +38,7 @@ const (
ImageNginx = "nginx:stable"
ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest"
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
ImageXRayCore = "teddysun/xray:latest"
)
var allImages = []string{
@ -51,6 +52,7 @@ var allImages = []string{
ImageNginx,
ImageShadowTLS,
ImageShadowsocksR,
ImageXRayCore,
}
var localIP = netip.MustParseAddr("127.0.0.1")
@ -379,7 +381,7 @@ func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.Pack
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}
times := 50
times := 2
chunkSize := int64(1024)
pingCh, pongCh, test := newLargeDataPair()

View file

@ -0,0 +1,25 @@
{
"log": {
"loglevel": "debug"
},
"inbounds": [
{
"listen": "0.0.0.0",
"port": 1234,
"protocol": "vless",
"settings": {
"decryption": "none",
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811"
}
]
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}

120
test/vless_test.go Normal file
View file

@ -0,0 +1,120 @@
package main
import (
"net/netip"
"os"
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/spyzhov/ajson"
"github.com/stretchr/testify/require"
)
func TestVLESS(t *testing.T) {
content, err := os.ReadFile("config/vless-server.json")
require.NoError(t, err)
config, err := ajson.Unmarshal(content)
require.NoError(t, err)
user := newUUID()
inbound := config.MustKey("inbounds").MustIndex(0)
inbound.MustKey("port").SetNumeric(float64(serverPort))
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
content, err = ajson.Marshal(config)
require.NoError(t, err)
startDockerContainer(t, DockerOptions{
Image: ImageV2RayCore,
Ports: []uint16{serverPort, testPort},
EntryPoint: "v2ray",
Stdin: content,
})
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeVLESS,
VLESSOptions: option.VLESSOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
UUID: user.String(),
},
},
},
})
testSuit(t, clientPort, testPort)
}
func TestVLESSXRay(t *testing.T) {
testVLESSXray(t, "")
}
func TestVLESSXUDP(t *testing.T) {
testVLESSXray(t, "xudp")
}
func testVLESSXray(t *testing.T, packetEncoding string) {
content, err := os.ReadFile("config/vless-server.json")
require.NoError(t, err)
config, err := ajson.Unmarshal(content)
require.NoError(t, err)
user := newUUID()
inbound := config.MustKey("inbounds").MustIndex(0)
inbound.MustKey("port").SetNumeric(float64(serverPort))
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
content, err = ajson.Marshal(config)
require.NoError(t, err)
startDockerContainer(t, DockerOptions{
Image: ImageXRayCore,
Ports: []uint16{serverPort, testPort},
EntryPoint: "xray",
Stdin: content,
})
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeVLESS,
VLESSOptions: option.VLESSOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
UUID: user.String(),
PacketEncoding: packetEncoding,
},
},
},
})
testSuit(t, clientPort, testPort)
}

View file

@ -13,21 +13,24 @@ import (
"github.com/stretchr/testify/require"
)
func newUUID() uuid.UUID {
user, _ := uuid.DefaultGenerator.NewV4()
return user
}
func _TestVMessAuto(t *testing.T) {
security := "auto"
user, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
t.Run("self", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, false)
testVMessSelf(t, security, 0, false, false, false)
})
t.Run("packetaddr", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, true)
testVMessSelf(t, security, 0, false, false, true)
})
t.Run("inbound", func(t *testing.T) {
testVMessInboundWithV2Ray(t, security, user, 0, false)
testVMessInboundWithV2Ray(t, security, 0, false)
})
t.Run("outbound", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
testVMessOutboundWithV2Ray(t, security, false, false, 0)
})
}
@ -56,102 +59,97 @@ func TestVMess(t *testing.T) {
}
func testVMess0(t *testing.T, security string) {
user, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
t.Run("self", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, false)
testVMessSelf(t, security, 0, false, false, false)
})
t.Run("self-legacy", func(t *testing.T) {
testVMessSelf(t, security, user, 1, false, false, false)
testVMessSelf(t, security, 1, false, false, false)
})
t.Run("packetaddr", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, true)
testVMessSelf(t, security, 0, false, false, true)
})
t.Run("outbound", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
testVMessOutboundWithV2Ray(t, security, false, false, 0)
})
t.Run("outbound-legacy", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
testVMessOutboundWithV2Ray(t, security, false, false, 1)
})
}
func testVMess1(t *testing.T, security string) {
user, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
t.Run("self", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, false)
testVMessSelf(t, security, 0, false, false, false)
})
t.Run("self-legacy", func(t *testing.T) {
testVMessSelf(t, security, user, 1, false, false, false)
testVMessSelf(t, security, 1, false, false, false)
})
t.Run("packetaddr", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, true)
testVMessSelf(t, security, 0, false, false, true)
})
t.Run("inbound", func(t *testing.T) {
testVMessInboundWithV2Ray(t, security, user, 0, false)
testVMessInboundWithV2Ray(t, security, 0, false)
})
t.Run("outbound", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
testVMessOutboundWithV2Ray(t, security, false, false, 0)
})
t.Run("outbound-legacy", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
testVMessOutboundWithV2Ray(t, security, false, false, 1)
})
}
func testVMess2(t *testing.T, security string) {
user, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err)
t.Run("self", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, false)
testVMessSelf(t, security, 0, false, false, false)
})
t.Run("self-padding", func(t *testing.T) {
testVMessSelf(t, security, user, 0, true, false, false)
testVMessSelf(t, security, 0, true, false, false)
})
t.Run("self-authid", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, true, false)
testVMessSelf(t, security, 0, false, true, false)
})
t.Run("self-padding-authid", func(t *testing.T) {
testVMessSelf(t, security, user, 0, true, true, false)
testVMessSelf(t, security, 0, true, true, false)
})
t.Run("self-legacy", func(t *testing.T) {
testVMessSelf(t, security, user, 1, false, false, false)
testVMessSelf(t, security, 1, false, false, false)
})
t.Run("self-legacy-padding", func(t *testing.T) {
testVMessSelf(t, security, user, 1, true, false, false)
testVMessSelf(t, security, 1, true, false, false)
})
t.Run("packetaddr", func(t *testing.T) {
testVMessSelf(t, security, user, 0, false, false, true)
testVMessSelf(t, security, 0, false, false, true)
})
t.Run("inbound", func(t *testing.T) {
testVMessInboundWithV2Ray(t, security, user, 0, false)
testVMessInboundWithV2Ray(t, security, 0, false)
})
t.Run("inbound-authid", func(t *testing.T) {
testVMessInboundWithV2Ray(t, security, user, 0, true)
testVMessInboundWithV2Ray(t, security, 0, true)
})
t.Run("inbound-legacy", func(t *testing.T) {
testVMessInboundWithV2Ray(t, security, user, 64, false)
testVMessInboundWithV2Ray(t, security, 64, false)
})
t.Run("outbound", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
testVMessOutboundWithV2Ray(t, security, false, false, 0)
})
t.Run("outbound-padding", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, true, false, 0)
testVMessOutboundWithV2Ray(t, security, true, false, 0)
})
t.Run("outbound-authid", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, true, 0)
testVMessOutboundWithV2Ray(t, security, false, true, 0)
})
t.Run("outbound-padding-authid", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, true, true, 0)
testVMessOutboundWithV2Ray(t, security, true, true, 0)
})
t.Run("outbound-legacy", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
testVMessOutboundWithV2Ray(t, security, false, false, 1)
})
t.Run("outbound-legacy-padding", func(t *testing.T) {
testVMessOutboundWithV2Ray(t, security, user, true, false, 1)
testVMessOutboundWithV2Ray(t, security, true, false, 1)
})
}
func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, alterId int, authenticatedLength bool) {
func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authenticatedLength bool) {
userId := newUUID()
content, err := os.ReadFile("config/vmess-client.json")
require.NoError(t, err)
config, err := ajson.Unmarshal(content)
@ -161,7 +159,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
outbound := config.MustKey("outbounds").MustIndex(0).MustKey("settings").MustKey("vnext").MustIndex(0)
outbound.MustKey("port").SetNumeric(float64(serverPort))
user := outbound.MustKey("users").MustIndex(0)
user.MustKey("id").SetString(uuid.String())
user.MustKey("id").SetString(userId.String())
user.MustKey("alterId").SetNumeric(float64(alterId))
user.MustKey("security").SetString(security)
var experiments string
@ -193,7 +191,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
Users: []option.VMessUser{
{
Name: "sekai",
UUID: uuid.String(),
UUID: userId.String(),
AlterId: alterId,
},
},
@ -205,7 +203,8 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
testSuitSimple(t, clientPort, testPort)
}
func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, globalPadding bool, authenticatedLength bool, alterId int) {
func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding bool, authenticatedLength bool, alterId int) {
user := newUUID()
content, err := os.ReadFile("config/vmess-server.json")
require.NoError(t, err)
config, err := ajson.Unmarshal(content)
@ -213,7 +212,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
inbound := config.MustKey("inbounds").MustIndex(0)
inbound.MustKey("port").SetNumeric(float64(serverPort))
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(uuid.String())
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("alterId").SetNumeric(float64(alterId))
content, err = ajson.Marshal(config)
@ -248,7 +247,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
ServerPort: serverPort,
},
Security: security,
UUID: uuid.String(),
UUID: user.String(),
GlobalPadding: globalPadding,
AuthenticatedLength: authenticatedLength,
AlterId: alterId,
@ -259,7 +258,8 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
testSuitSimple(t, clientPort, testPort)
}
func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {
func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {
user := newUUID()
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
@ -282,7 +282,7 @@ func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, g
Users: []option.VMessUser{
{
Name: "sekai",
UUID: uuid.String(),
UUID: user.String(),
AlterId: alterId,
},
},
@ -302,7 +302,7 @@ func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, g
ServerPort: serverPort,
},
Security: security,
UUID: uuid.String(),
UUID: user.String(),
AlterId: alterId,
GlobalPadding: globalPadding,
AuthenticatedLength: authenticatedLength,

144
transport/vless/client.go Normal file
View file

@ -0,0 +1,144 @@
package vless
import (
"encoding/binary"
"io"
"net"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
"github.com/gofrs/uuid"
)
type Client struct {
key []byte
}
func NewClient(userId string) (*Client, error) {
user := uuid.FromStringOrNil(userId)
if user == uuid.Nil {
user = uuid.NewV5(user, userId)
}
return &Client{key: user.Bytes()}, nil
}
func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) *Conn {
return &Conn{Conn: conn, key: c.key, destination: destination}
}
func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) *PacketConn {
return &PacketConn{Conn: conn, key: c.key, destination: destination}
}
func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) *XUDPConn {
return &XUDPConn{Conn: conn, key: c.key, destination: destination}
}
type Conn struct {
net.Conn
key []byte
destination M.Socksaddr
requestWritten bool
responseRead bool
}
func (c *Conn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = ReadResponse(c.Conn)
if err != nil {
return
}
c.responseRead = true
}
return c.Conn.Read(b)
}
func (c *Conn) Write(b []byte) (n int, err error) {
if !c.requestWritten {
err = WriteRequest(c.Conn, Request{c.key, CommandTCP, c.destination}, b)
if err == nil {
n = len(b)
}
c.requestWritten = true
return
}
return c.Conn.Write(b)
}
func (c *Conn) Upstream() any {
return c.Conn
}
type PacketConn struct {
net.Conn
key []byte
destination M.Socksaddr
requestWritten bool
responseRead bool
}
func (c *PacketConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = ReadResponse(c.Conn)
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.Conn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(b) < int(length) {
return 0, io.ErrShortBuffer
}
return io.ReadFull(c.Conn, b[:length])
}
func (c *PacketConn) Write(b []byte) (n int, err error) {
if !c.requestWritten {
err = WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, b)
if err == nil {
n = len(b)
}
c.requestWritten = true
return
}
err = binary.Write(c.Conn, binary.BigEndian, uint16(len(b)))
if err != nil {
return
}
return c.Conn.Write(b)
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
dataLen := buffer.Len()
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen))
if !c.requestWritten {
err := WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, buffer.Bytes())
c.requestWritten = true
return err
}
return common.Error(c.Conn.Write(buffer.Bytes()))
}
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, err = c.Read(p)
return
}
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return c.Write(p)
}
func (c *PacketConn) FrontHeadroom() int {
return 2
}
func (c *PacketConn) Upstream() any {
return c.Conn
}

104
transport/vless/protocol.go Normal file
View file

@ -0,0 +1,104 @@
package vless
import (
"encoding/binary"
"io"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/rw"
)
const (
Version = 0
CommandTCP = 1
CommandUDP = 2
CommandMux = 3
NetworkUDP = 2
)
var AddressSerializer = M.NewSerializer(
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
M.PortThenAddress(),
)
type Request struct {
UUID []byte
Command byte
Destination M.Socksaddr
}
func WriteRequest(writer io.Writer, request Request, payload []byte) error {
var requestLen int
requestLen += 1 // version
requestLen += 16 // uuid
requestLen += 1 // protobuf length
requestLen += 1 // command
requestLen += AddressSerializer.AddrPortLen(request.Destination)
requestLen += len(payload)
_buffer := buf.StackNewSize(requestLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(Version),
common.Error(buffer.Write(request.UUID)),
buffer.WriteByte(0),
buffer.WriteByte(CommandTCP),
AddressSerializer.WriteAddrPort(buffer, request.Destination),
common.Error(buffer.Write(payload)),
)
return common.Error(writer.Write(buffer.Bytes()))
}
func WritePacketRequest(writer io.Writer, request Request, payload []byte) error {
var requestLen int
requestLen += 1 // version
requestLen += 16 // uuid
requestLen += 1 // protobuf length
requestLen += 1 // command
requestLen += AddressSerializer.AddrPortLen(request.Destination)
if len(payload) > 0 {
requestLen += 2
requestLen += len(payload)
}
_buffer := buf.StackNewSize(requestLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(Version),
common.Error(buffer.Write(request.UUID)),
buffer.WriteByte(0),
buffer.WriteByte(CommandUDP),
AddressSerializer.WriteAddrPort(buffer, request.Destination),
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
return common.Error(writer.Write(buffer.Bytes()))
}
func ReadResponse(reader io.Reader) error {
version, err := rw.ReadByte(reader)
if err != nil {
return err
}
if version != Version {
return E.New("unknown version: ", version)
}
protobufLength, err := rw.ReadByte(reader)
if err != nil {
return err
}
if protobufLength > 0 {
err = rw.SkipN(reader, int(protobufLength))
if err != nil {
return err
}
}
return nil
}

174
transport/vless/xudp.go Normal file
View file

@ -0,0 +1,174 @@
package vless
import (
"encoding/binary"
"io"
"net"
"os"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
type XUDPConn struct {
net.Conn
key []byte
destination M.Socksaddr
requestWritten bool
responseRead bool
}
func (c *XUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
return 0, nil, os.ErrInvalid
}
func (c *XUDPConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
start := buffer.Start()
if !c.responseRead {
err = ReadResponse(c.Conn)
if err != nil {
return
}
c.responseRead = true
buffer.FullReset()
}
_, err = buffer.ReadFullFrom(c.Conn, 6)
if err != nil {
return
}
var length uint16
err = binary.Read(buffer, binary.BigEndian, &length)
if err != nil {
return
}
header, err := buffer.ReadBytes(4)
if err != nil {
return
}
switch header[2] {
case 1:
// frame new
return M.Socksaddr{}, E.New("unexpected frame new")
case 2:
// frame keep
if length != 4 {
_, err = buffer.ReadFullFrom(c.Conn, int(length)-2)
if err != nil {
return
}
buffer.Advance(1)
destination, err = AddressSerializer.ReadAddrPort(buffer)
if err != nil {
return
}
} else {
_, err = buffer.ReadFullFrom(c.Conn, 2)
if err != nil {
return
}
destination = c.destination
}
case 3:
// frame end
return M.Socksaddr{}, io.EOF
case 4:
// frame keep alive
default:
return M.Socksaddr{}, E.New("unexpected frame: ", buffer.Byte(2))
}
// option error
if header[3]&2 == 2 {
return M.Socksaddr{}, E.Cause(net.ErrClosed, "remote closed")
}
// option data
if header[3]&1 != 1 {
buffer.Resize(start, 0)
return c.ReadPacket(buffer)
} else {
err = binary.Read(buffer, binary.BigEndian, &length)
if err != nil {
return
}
buffer.Resize(start, 0)
_, err = buffer.ReadFullFrom(c.Conn, int(length))
return
}
}
func (c *XUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
destination := M.SocksaddrFromNet(addr)
headerLen := c.frontHeadroom(AddressSerializer.AddrPortLen(destination))
buffer := buf.NewSize(headerLen + len(p))
buffer.Advance(headerLen)
common.Must1(buffer.Write(p))
err = c.WritePacket(buffer, destination)
if err == nil {
n = len(p)
}
return
}
func (c *XUDPConn) frontHeadroom(addrLen int) int {
if !c.requestWritten {
var headerLen int
headerLen += 1 // version
headerLen += 16 // uuid
headerLen += 1 // protobuf length
headerLen += 1 // command
headerLen += 2 // frame len
headerLen += 5 // frame header
headerLen += addrLen
headerLen += 2 // payload len
return headerLen
} else {
return 7 + addrLen + 2
}
}
func (c *XUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
dataLen := buffer.Len()
addrLen := M.SocksaddrSerializer.AddrPortLen(destination)
if !c.requestWritten {
header := buf.With(buffer.ExtendHeader(c.frontHeadroom(addrLen)))
common.Must(
header.WriteByte(Version),
common.Error(header.Write(c.key)),
header.WriteByte(0),
header.WriteByte(CommandMux),
binary.Write(header, binary.BigEndian, uint16(5+addrLen)),
header.WriteByte(0),
header.WriteByte(0),
header.WriteByte(1), // frame type new
header.WriteByte(1), // option data
header.WriteByte(NetworkUDP),
AddressSerializer.WriteAddrPort(header, destination),
binary.Write(header, binary.BigEndian, uint16(dataLen)),
)
c.requestWritten = true
} else {
header := buffer.ExtendHeader(c.frontHeadroom(addrLen))
binary.BigEndian.PutUint16(header, uint16(5+addrLen))
header[2] = 0
header[3] = 0
header[4] = 2 // frame keep
header[5] = 1 // option data
header[6] = NetworkUDP
err := AddressSerializer.WriteAddrPort(buf.With(header[7:]), destination)
if err != nil {
return err
}
binary.BigEndian.PutUint16(header[7+addrLen:], uint16(dataLen))
}
return common.Error(c.Conn.Write(buffer.Bytes()))
}
func (c *XUDPConn) FrontHeadroom() int {
return c.frontHeadroom(M.MaxSocksaddrLength)
}
func (c *XUDPConn) Upstream() any {
return c.Conn
}