header-custom finalmask: Add UDP standalone handshake mode (#5945)

https://github.com/XTLS/Xray-core/commit/175502d8079aa5a151242ed911d01a1b90b98b28
This commit is contained in:
Иван
2026-04-15 23:21:23 +07:00
committed by GitHub
parent 175502d807
commit 05e259c8e4
13 changed files with 886 additions and 6 deletions
@@ -19,12 +19,22 @@ func (c *UDPConfig) UDP() {
}
func (c *UDPConfig) WrapPacketConnClient(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if c.Mode == "standalone" {
return NewConnClientUDPStandalone(c, raw)
}
return NewConnClientUDP(c, raw)
}
func (c *UDPConfig) WrapPacketConnServer(raw net.PacketConn, level int, levelCount int) (net.PacketConn, error) {
if c.Mode == "standalone" {
return NewConnServerUDPStandalone(c, raw)
}
return NewConnServerUDP(c, raw)
}
func (c *UDPConfig) HeaderConn() {
}
func (c *UDPConfig) UseHeaderConn() bool {
return c.Mode != "standalone"
}
@@ -511,6 +511,7 @@ type UDPConfig struct {
state protoimpl.MessageState `protogen:"open.v1"`
Client []*UDPItem `protobuf:"bytes,1,rep,name=client,proto3" json:"client,omitempty"`
Server []*UDPItem `protobuf:"bytes,2,rep,name=server,proto3" json:"server,omitempty"`
Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -559,6 +560,13 @@ func (x *UDPConfig) GetServer() []*UDPItem {
return nil
}
func (x *UDPConfig) GetMode() string {
if x != nil {
return x.Mode
}
return ""
}
var File_transport_internet_finalmask_header_custom_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_header_custom_config_proto_rawDesc = "" +
@@ -597,10 +605,11 @@ const file_transport_internet_finalmask_header_custom_config_proto_rawDesc = ""
"\x06packet\x18\x04 \x01(\fR\x06packet\x12\x12\n" +
"\x04save\x18\x05 \x01(\tR\x04save\x12\x10\n" +
"\x03var\x18\x06 \x01(\tR\x03var\x12I\n" +
"\x04expr\x18\a \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprR\x04expr\"\xaf\x01\n" +
"\x04expr\x18\a \x01(\v25.xray.transport.internet.finalmask.header.custom.ExprR\x04expr\"\xc3\x01\n" +
"\tUDPConfig\x12P\n" +
"\x06client\x18\x01 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06client\x12P\n" +
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06serverB\xaf\x01\n" +
"\x06server\x18\x02 \x03(\v28.xray.transport.internet.finalmask.header.custom.UDPItemR\x06server\x12\x12\n" +
"\x04mode\x18\x03 \x01(\tR\x04modeB\xaf\x01\n" +
"3com.xray.transport.internet.finalmask.header.customP\x01ZDgithub.com/xtls/xray-core/transport/internet/finalmask/header/custom\xaa\x02/Xray.Transport.Internet.Finalmask.Header.Customb\x06proto3"
var (
@@ -56,4 +56,5 @@ message UDPItem {
message UDPConfig {
repeated UDPItem client = 1;
repeated UDPItem server = 2;
string mode = 3;
}
@@ -398,9 +398,19 @@ func loadMetadata(dst map[string]evalValue, prefix string, addr net.Addr) {
func loadIPPortMetadata(dst map[string]evalValue, prefix string, ip net.IP, port int) {
portValue := uint64(port)
dst[prefix+"_port"] = evalValue{u64: &portValue}
if prefix == "remote" {
dst["src_port_u16"] = evalValue{u64: &portValue}
} else if prefix == "local" {
dst["dst_port_u16"] = evalValue{u64: &portValue}
}
if ip4 := ip.To4(); ip4 != nil {
ipValue := uint64(binary.BigEndian.Uint32(ip4))
dst[prefix+"_ip4_u32"] = evalValue{u64: &ipValue}
if prefix == "remote" {
dst["src_ip4_u32"] = evalValue{u64: &ipValue}
} else if prefix == "local" {
dst["dst_ip4_u32"] = evalValue{u64: &ipValue}
}
}
}
@@ -30,6 +30,100 @@ func TestMetadataEvaluatorRejectsUnknownName(t *testing.T) {
}
}
func TestMetadataAliasesExposeSrcAndDstNames(t *testing.T) {
ctx := newEvalContextWithAddrs(
&net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 3478},
&net.UDPAddr{IP: net.IPv4(203, 0, 113, 9), Port: 54321},
)
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "src_port_u16"}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be32",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "src_ip4_u32"}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItemsWithContext(items, ctx)
if err != nil {
t.Fatal(err)
}
want := []byte{0xD4, 0x31, 203, 0, 113, 9}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected alias output: got=%x want=%x", got, want)
}
}
func TestMetadataAliasesExposeDstNames(t *testing.T) {
ctx := newEvalContextWithAddrs(
&net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 3478},
&net.UDPAddr{IP: net.IPv4(203, 0, 113, 9), Port: 54321},
)
items := []*UDPItem{
{
Expr: &Expr{
Op: "concat",
Args: []*ExprArg{
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be16",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "dst_port_u16"}},
},
},
},
},
{
Value: &ExprArg_Expr{
Expr: &Expr{
Op: "be32",
Args: []*ExprArg{
{Value: &ExprArg_Metadata{Metadata: "dst_ip4_u32"}},
},
},
},
},
},
},
},
}
got, err := evaluateUDPItemsWithContext(items, ctx)
if err != nil {
t.Fatal(err)
}
want := []byte{0x0D, 0x96, 10, 0, 0, 1}
if !bytes.Equal(got, want) {
t.Fatalf("unexpected alias output: got=%x want=%x", got, want)
}
}
func TestMetadataUDPWriteUsesRemotePort(t *testing.T) {
cfg := &UDPConfig{
Client: []*UDPItem{
@@ -3,11 +3,14 @@ package custom
import (
"bytes"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/errors"
)
const udpStandaloneBufferSize = 4096
type udpCustomClient struct {
client []*UDPItem
server []*UDPItem
@@ -267,3 +270,233 @@ func udpStateKey(addr net.Addr) string {
}
return addr.String()
}
type udpCustomStandaloneClientConn struct {
net.PacketConn
client []*UDPItem
server []*UDPItem
state *stateStore
read int
mu sync.Mutex
once sync.Once
queue chan udpStandalonePacket
wait map[string]*udpStandaloneWaiter
}
type udpStandalonePacket struct {
data []byte
addr net.Addr
err error
}
type udpStandaloneWaiter struct {
vars map[string][]byte
done chan error
}
func NewConnClientUDPStandalone(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
clientSavedSizes := collectSavedUDPSizes(c.Client)
read, err := measureUDPItemsWithFallback(c.Server, clientSavedSizes)
if err != nil {
return nil, err
}
return &udpCustomStandaloneClientConn{
PacketConn: raw,
client: c.Client,
server: c.Server,
state: newStateStore(5 * time.Second),
read: read,
queue: make(chan udpStandalonePacket, 16),
wait: make(map[string]*udpStandaloneWaiter),
}, nil
}
func (c *udpCustomStandaloneClientConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
c.ensureReader()
packet, ok := <-c.queue
if !ok {
return 0, nil, net.ErrClosed
}
if packet.err != nil {
return 0, packet.addr, packet.err
}
if len(packet.data) > len(p) {
copy(p, packet.data[:len(p)])
return len(p), packet.addr, nil
}
copy(p, packet.data)
return len(packet.data), packet.addr, nil
}
func (c *udpCustomStandaloneClientConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
c.ensureReader()
key := udpStateKey(addr)
if _, ok := c.state.get(key); !ok {
var localAddr net.Addr
if c.PacketConn != nil {
localAddr = c.PacketConn.LocalAddr()
}
ctx := newEvalContextWithAddrs(localAddr, addr)
request, err := evaluateUDPItemsWithContext(c.client, ctx)
if err != nil {
return 0, err
}
waiter := c.registerWaiter(key, ctx.vars)
if _, err := c.PacketConn.WriteTo(request, addr); err != nil {
c.unregisterWaiter(key, waiter)
return 0, err
}
if err := <-waiter.done; err != nil {
return 0, err
}
}
return c.PacketConn.WriteTo(p, addr)
}
func (c *udpCustomStandaloneClientConn) ensureReader() {
c.once.Do(func() {
go c.readerLoop(c.queue)
})
}
func (c *udpCustomStandaloneClientConn) registerWaiter(key string, vars map[string][]byte) *udpStandaloneWaiter {
waiter := &udpStandaloneWaiter{
vars: cloneVars(vars),
done: make(chan error, 1),
}
c.mu.Lock()
c.wait[key] = waiter
c.mu.Unlock()
return waiter
}
func (c *udpCustomStandaloneClientConn) unregisterWaiter(key string, waiter *udpStandaloneWaiter) {
c.mu.Lock()
if c.wait[key] == waiter {
delete(c.wait, key)
}
c.mu.Unlock()
}
func (c *udpCustomStandaloneClientConn) readerLoop(queue chan udpStandalonePacket) {
buf := make([]byte, udpStandaloneBufferSize)
for {
n, addr, err := c.PacketConn.ReadFrom(buf)
if err != nil {
c.failWaiters(err)
queue <- udpStandalonePacket{addr: addr, err: err}
close(queue)
return
}
data := append([]byte(nil), buf[:n]...)
if c.tryCompleteHandshake(addr, data) {
continue
}
queue <- udpStandalonePacket{data: data, addr: addr}
}
}
func (c *udpCustomStandaloneClientConn) tryCompleteHandshake(addr net.Addr, data []byte) bool {
key := udpStateKey(addr)
c.mu.Lock()
waiter, ok := c.wait[key]
c.mu.Unlock()
if !ok || len(data) != c.read {
return false
}
vars, matched := matchUDPItems(c.server, data, c.read, waiter.vars)
if !matched {
return false
}
c.state.set(key, vars)
c.mu.Lock()
if c.wait[key] == waiter {
delete(c.wait, key)
}
c.mu.Unlock()
waiter.done <- nil
return true
}
func (c *udpCustomStandaloneClientConn) failWaiters(err error) {
c.mu.Lock()
waiters := c.wait
c.wait = make(map[string]*udpStandaloneWaiter)
c.mu.Unlock()
for _, waiter := range waiters {
waiter.done <- err
}
}
type udpCustomStandaloneServerConn struct {
net.PacketConn
client []*UDPItem
server []*UDPItem
state *stateStore
read int
}
func NewConnServerUDPStandalone(c *UDPConfig, raw net.PacketConn) (net.PacketConn, error) {
read, err := measureUDPItems(c.Client)
if err != nil {
return nil, err
}
return &udpCustomStandaloneServerConn{
PacketConn: raw,
client: c.Client,
server: c.Server,
state: newStateStore(5 * time.Second),
read: read,
}, nil
}
func (c *udpCustomStandaloneServerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buf := p
copyBack := false
if len(buf) < udpStandaloneBufferSize {
buf = make([]byte, udpStandaloneBufferSize)
copyBack = true
}
for {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil {
return 0, addr, err
}
if n == c.read {
vars, ok := matchUDPItems(c.client, buf[:n], c.read, nil)
if ok {
var localAddr net.Addr
if c.PacketConn != nil {
localAddr = c.PacketConn.LocalAddr()
}
ctx := newEvalContextWithAddrs(localAddr, addr)
ctx.vars = cloneVars(vars)
response, err := evaluateUDPItemsWithContext(c.server, ctx)
if err != nil {
return 0, addr, err
}
if _, err := c.PacketConn.WriteTo(response, addr); err != nil {
return 0, addr, err
}
c.state.set(udpStateKey(addr), ctx.vars)
continue
}
}
if copyBack {
copy(p, buf[:n])
}
return n, addr, nil
}
}
func (c *udpCustomStandaloneServerConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
return c.PacketConn.WriteTo(p, addr)
}