mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Add vless outbound and xudp
This commit is contained in:
parent
dfb8b5f2fa
commit
38088f28b0
|
@ -20,6 +20,7 @@ const (
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
|
TypeVLESS = "vless"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -22,6 +22,7 @@ type _Outbound struct {
|
||||||
SSHOptions SSHOutboundOptions `json:"-"`
|
SSHOptions SSHOutboundOptions `json:"-"`
|
||||||
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
|
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
|
||||||
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
|
||||||
|
VLESSOptions VLESSOutboundOptions `json:"-"`
|
||||||
SelectorOptions SelectorOutboundOptions `json:"-"`
|
SelectorOptions SelectorOutboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +57,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
|
||||||
v = h.ShadowTLSOptions
|
v = h.ShadowTLSOptions
|
||||||
case C.TypeShadowsocksR:
|
case C.TypeShadowsocksR:
|
||||||
v = h.ShadowsocksROptions
|
v = h.ShadowsocksROptions
|
||||||
|
case C.TypeVLESS:
|
||||||
|
v = h.VLESSOptions
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = h.SelectorOptions
|
v = h.SelectorOptions
|
||||||
default:
|
default:
|
||||||
|
@ -97,6 +100,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
|
||||||
v = &h.ShadowTLSOptions
|
v = &h.ShadowTLSOptions
|
||||||
case C.TypeShadowsocksR:
|
case C.TypeShadowsocksR:
|
||||||
v = &h.ShadowsocksROptions
|
v = &h.ShadowsocksROptions
|
||||||
|
case C.TypeVLESS:
|
||||||
|
v = &h.VLESSOptions
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
v = &h.SelectorOptions
|
v = &h.SelectorOptions
|
||||||
default:
|
default:
|
||||||
|
|
11
option/vless.go
Normal file
11
option/vless.go
Normal 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"`
|
||||||
|
}
|
|
@ -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)
|
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
||||||
case C.TypeShadowsocksR:
|
case C.TypeShadowsocksR:
|
||||||
return NewShadowsocksR(ctx, router, logger, options.Tag, options.ShadowsocksROptions)
|
return NewShadowsocksR(ctx, router, logger, options.Tag, options.ShadowsocksROptions)
|
||||||
|
case C.TypeVLESS:
|
||||||
|
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
||||||
case C.TypeSelector:
|
case C.TypeSelector:
|
||||||
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
|
||||||
default:
|
default:
|
||||||
|
|
146
outbound/vless.go
Normal file
146
outbound/vless.go
Normal 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)
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ const (
|
||||||
ImageNginx = "nginx:stable"
|
ImageNginx = "nginx:stable"
|
||||||
ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest"
|
ImageShadowTLS = "ghcr.io/ihciah/shadow-tls:latest"
|
||||||
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
|
ImageShadowsocksR = "teddysun/shadowsocks-r:latest"
|
||||||
|
ImageXRayCore = "teddysun/xray:latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allImages = []string{
|
var allImages = []string{
|
||||||
|
@ -51,6 +52,7 @@ var allImages = []string{
|
||||||
ImageNginx,
|
ImageNginx,
|
||||||
ImageShadowTLS,
|
ImageShadowTLS,
|
||||||
ImageShadowsocksR,
|
ImageShadowsocksR,
|
||||||
|
ImageXRayCore,
|
||||||
}
|
}
|
||||||
|
|
||||||
var localIP = netip.MustParseAddr("127.0.0.1")
|
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)}
|
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}
|
||||||
|
|
||||||
times := 50
|
times := 2
|
||||||
chunkSize := int64(1024)
|
chunkSize := int64(1024)
|
||||||
|
|
||||||
pingCh, pongCh, test := newLargeDataPair()
|
pingCh, pongCh, test := newLargeDataPair()
|
||||||
|
|
25
test/config/vless-server.json
Normal file
25
test/config/vless-server.json
Normal 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
120
test/vless_test.go
Normal 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)
|
||||||
|
}
|
|
@ -13,21 +13,24 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func newUUID() uuid.UUID {
|
||||||
|
user, _ := uuid.DefaultGenerator.NewV4()
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
func _TestVMessAuto(t *testing.T) {
|
func _TestVMessAuto(t *testing.T) {
|
||||||
security := "auto"
|
security := "auto"
|
||||||
user, err := uuid.DefaultGenerator.NewV4()
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Run("self", func(t *testing.T) {
|
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) {
|
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) {
|
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) {
|
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) {
|
func testVMess0(t *testing.T, security string) {
|
||||||
user, err := uuid.DefaultGenerator.NewV4()
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Run("self", func(t *testing.T) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
func testVMess1(t *testing.T, security string) {
|
||||||
user, err := uuid.DefaultGenerator.NewV4()
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Run("self", func(t *testing.T) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
func testVMess2(t *testing.T, security string) {
|
||||||
user, err := uuid.DefaultGenerator.NewV4()
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Run("self", func(t *testing.T) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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")
|
content, err := os.ReadFile("config/vmess-client.json")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
config, err := ajson.Unmarshal(content)
|
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 := config.MustKey("outbounds").MustIndex(0).MustKey("settings").MustKey("vnext").MustIndex(0)
|
||||||
outbound.MustKey("port").SetNumeric(float64(serverPort))
|
outbound.MustKey("port").SetNumeric(float64(serverPort))
|
||||||
user := outbound.MustKey("users").MustIndex(0)
|
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("alterId").SetNumeric(float64(alterId))
|
||||||
user.MustKey("security").SetString(security)
|
user.MustKey("security").SetString(security)
|
||||||
var experiments string
|
var experiments string
|
||||||
|
@ -193,7 +191,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
|
||||||
Users: []option.VMessUser{
|
Users: []option.VMessUser{
|
||||||
{
|
{
|
||||||
Name: "sekai",
|
Name: "sekai",
|
||||||
UUID: uuid.String(),
|
UUID: userId.String(),
|
||||||
AlterId: alterId,
|
AlterId: alterId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -205,7 +203,8 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, al
|
||||||
testSuitSimple(t, clientPort, testPort)
|
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")
|
content, err := os.ReadFile("config/vmess-server.json")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
config, err := ajson.Unmarshal(content)
|
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 := config.MustKey("inbounds").MustIndex(0)
|
||||||
inbound.MustKey("port").SetNumeric(float64(serverPort))
|
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))
|
inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("alterId").SetNumeric(float64(alterId))
|
||||||
|
|
||||||
content, err = ajson.Marshal(config)
|
content, err = ajson.Marshal(config)
|
||||||
|
@ -248,7 +247,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
|
||||||
ServerPort: serverPort,
|
ServerPort: serverPort,
|
||||||
},
|
},
|
||||||
Security: security,
|
Security: security,
|
||||||
UUID: uuid.String(),
|
UUID: user.String(),
|
||||||
GlobalPadding: globalPadding,
|
GlobalPadding: globalPadding,
|
||||||
AuthenticatedLength: authenticatedLength,
|
AuthenticatedLength: authenticatedLength,
|
||||||
AlterId: alterId,
|
AlterId: alterId,
|
||||||
|
@ -259,7 +258,8 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
|
||||||
testSuitSimple(t, clientPort, testPort)
|
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{
|
startInstance(t, option.Options{
|
||||||
Inbounds: []option.Inbound{
|
Inbounds: []option.Inbound{
|
||||||
{
|
{
|
||||||
|
@ -282,7 +282,7 @@ func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, g
|
||||||
Users: []option.VMessUser{
|
Users: []option.VMessUser{
|
||||||
{
|
{
|
||||||
Name: "sekai",
|
Name: "sekai",
|
||||||
UUID: uuid.String(),
|
UUID: user.String(),
|
||||||
AlterId: alterId,
|
AlterId: alterId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -302,7 +302,7 @@ func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, g
|
||||||
ServerPort: serverPort,
|
ServerPort: serverPort,
|
||||||
},
|
},
|
||||||
Security: security,
|
Security: security,
|
||||||
UUID: uuid.String(),
|
UUID: user.String(),
|
||||||
AlterId: alterId,
|
AlterId: alterId,
|
||||||
GlobalPadding: globalPadding,
|
GlobalPadding: globalPadding,
|
||||||
AuthenticatedLength: authenticatedLength,
|
AuthenticatedLength: authenticatedLength,
|
||||||
|
|
144
transport/vless/client.go
Normal file
144
transport/vless/client.go
Normal 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
104
transport/vless/protocol.go
Normal 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
174
transport/vless/xudp.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue