382 lines
9.0 KiB
Go
382 lines
9.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
const (
|
|
nativeUDPMaxPacket = 65535
|
|
nativeUDPBufferSize = 64 * 1024
|
|
nativeUDPIdle = 2 * time.Minute
|
|
)
|
|
|
|
// nativeVLESSUDPTunnel implements VLESS UDP-over-stream framing for a normal
|
|
// VLESS CommandUDP request. Xray uses classic 2-byte length-prefixed packets
|
|
// for this command. Do not auto-detect XUDP here: real DNS queries often have
|
|
// bytes 2/3 equal to 0x01/0x00, which looked like our old loose XUDP metadata
|
|
// check and caused the server to block waiting for a fake second payload.
|
|
// XUDP belongs to VLESS CommandMux and is handled separately when Mux support
|
|
// is implemented.
|
|
func nativeVLESSUDPTunnel(client io.ReadWriteCloser, backend net.Conn, uuid, email string, up, down *rate.Limiter) {
|
|
xrayMgr.recordNativeConnect(uuid, email)
|
|
defer xrayMgr.recordNativeDisconnect(uuid, email)
|
|
|
|
upMeter := &trafficMeter{uuid: uuid, email: email, uplink: true}
|
|
downMeter := &trafficMeter{uuid: uuid, email: email, uplink: false}
|
|
|
|
var wg sync.WaitGroup
|
|
closeAll := func() {
|
|
_ = backend.Close()
|
|
_ = client.Close()
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
payload, err := readVLESSLengthPacket(client)
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
log.Printf("native xray: VLESS UDP client read failed: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if len(payload) == 0 {
|
|
continue
|
|
}
|
|
if err := waitNativeRate(up, len(payload)); err != nil {
|
|
return
|
|
}
|
|
n, err := backend.Write(payload)
|
|
if n > 0 {
|
|
upMeter.add(n)
|
|
}
|
|
if err != nil {
|
|
log.Printf("native xray: VLESS UDP backend write failed: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
buf := make([]byte, nativeUDPBufferSize)
|
|
for {
|
|
_ = backend.SetReadDeadline(time.Now().Add(nativeUDPIdle))
|
|
n, err := backend.Read(buf)
|
|
if err != nil {
|
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
|
return
|
|
}
|
|
if err != io.EOF {
|
|
log.Printf("native xray: VLESS UDP backend read failed: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if n <= 0 {
|
|
continue
|
|
}
|
|
if err := waitNativeRate(down, n); err != nil {
|
|
return
|
|
}
|
|
if err := writeVLESSLengthPacket(client, buf[:n]); err != nil {
|
|
log.Printf("native xray: VLESS UDP client write failed: %v", err)
|
|
return
|
|
}
|
|
downMeter.add(n)
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
upMeter.flush()
|
|
downMeter.flush()
|
|
closeAll()
|
|
}
|
|
|
|
type vlessUDPPacketCodec struct {
|
|
mu sync.RWMutex
|
|
decided bool
|
|
xudp bool
|
|
}
|
|
|
|
func (c *vlessUDPPacketCodec) setXUDP(v bool) {
|
|
c.mu.Lock()
|
|
if !c.decided {
|
|
c.decided = true
|
|
c.xudp = v
|
|
}
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *vlessUDPPacketCodec) useXUDP() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.decided && c.xudp
|
|
}
|
|
|
|
func (c *vlessUDPPacketCodec) Read(r io.Reader) ([]byte, error) {
|
|
if c.useXUDP() {
|
|
return readVLESSXUDPPacket(r)
|
|
}
|
|
return c.readAuto(r)
|
|
}
|
|
|
|
func (c *vlessUDPPacketCodec) Write(w io.Writer, payload []byte) error {
|
|
if c.useXUDP() {
|
|
return writeVLESSXUDPPacket(w, payload)
|
|
}
|
|
return writeVLESSLengthPacket(w, payload)
|
|
}
|
|
|
|
func (c *vlessUDPPacketCodec) readAuto(r io.Reader) ([]byte, error) {
|
|
var lenBuf [2]byte
|
|
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
n := int(binary.BigEndian.Uint16(lenBuf[:]))
|
|
if n == 0 {
|
|
c.setXUDP(false)
|
|
return []byte{}, nil
|
|
}
|
|
if n > nativeUDPMaxPacket {
|
|
return nil, fmt.Errorf("udp packet too large: %d", n)
|
|
}
|
|
|
|
// XUDP starts with a metadata frame length, not a payload length. Metadata is
|
|
// small and has command/option bytes at offsets 2/3 after the two-byte mux ID.
|
|
// Read a possible metadata frame once and fall back to normal length-prefixed
|
|
// UDP if it does not match the XUDP shape. This lets the native emulator work
|
|
// with clients whose default packet encoding is xudp while preserving classic
|
|
// VLESS UDP framing.
|
|
if n >= 4 && n <= 512 {
|
|
candidate := make([]byte, n)
|
|
if _, err := io.ReadFull(r, candidate); err != nil {
|
|
return nil, err
|
|
}
|
|
if isVLESSXUDPMetadata(candidate) {
|
|
c.setXUDP(true)
|
|
return readVLESSXUDPPayloadAfterMeta(r, candidate)
|
|
}
|
|
c.setXUDP(false)
|
|
return candidate, nil
|
|
}
|
|
|
|
c.setXUDP(false)
|
|
pkt := make([]byte, n)
|
|
_, err := io.ReadFull(r, pkt)
|
|
return pkt, err
|
|
}
|
|
|
|
func readVLESSLengthPacket(r io.Reader) ([]byte, error) {
|
|
var lenBuf [2]byte
|
|
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
n := int(binary.BigEndian.Uint16(lenBuf[:]))
|
|
if n == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
if n > nativeUDPMaxPacket {
|
|
return nil, fmt.Errorf("udp packet too large: %d", n)
|
|
}
|
|
pkt := make([]byte, n)
|
|
_, err := io.ReadFull(r, pkt)
|
|
return pkt, err
|
|
}
|
|
|
|
func writeVLESSLengthPacket(w io.Writer, payload []byte) error {
|
|
if len(payload) > nativeUDPMaxPacket {
|
|
return fmt.Errorf("udp packet too large: %d", len(payload))
|
|
}
|
|
var lenBuf [2]byte
|
|
binary.BigEndian.PutUint16(lenBuf[:], uint16(len(payload)))
|
|
if _, err := w.Write(lenBuf[:]); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(payload)
|
|
return err
|
|
}
|
|
|
|
func isVLESSXUDPMetadata(meta []byte) bool {
|
|
if len(meta) < 4 {
|
|
return false
|
|
}
|
|
cmd := meta[2]
|
|
opt := meta[3]
|
|
if cmd != 1 && cmd != 2 && cmd != 4 { // New, Keep, End/discard
|
|
return false
|
|
}
|
|
return opt == 0 || opt == 1
|
|
}
|
|
|
|
func readVLESSXUDPPacket(r io.Reader) ([]byte, error) {
|
|
for {
|
|
var lenBuf [2]byte
|
|
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
n := int(binary.BigEndian.Uint16(lenBuf[:]))
|
|
if n < 4 || n > 512 {
|
|
return nil, fmt.Errorf("bad xudp metadata length: %d", n)
|
|
}
|
|
meta := make([]byte, n)
|
|
if _, err := io.ReadFull(r, meta); err != nil {
|
|
return nil, err
|
|
}
|
|
if !isVLESSXUDPMetadata(meta) {
|
|
return nil, fmt.Errorf("bad xudp metadata command/option")
|
|
}
|
|
payload, err := readVLESSXUDPPayloadAfterMeta(r, meta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if payload != nil {
|
|
return payload, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func readVLESSXUDPPayloadAfterMeta(r io.Reader, meta []byte) ([]byte, error) {
|
|
if len(meta) < 4 {
|
|
return nil, io.ErrUnexpectedEOF
|
|
}
|
|
if meta[2] == 4 { // discard/end marker
|
|
return nil, nil
|
|
}
|
|
if meta[3] != 1 { // no payload attached
|
|
return nil, nil
|
|
}
|
|
var lenBuf [2]byte
|
|
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
n := int(binary.BigEndian.Uint16(lenBuf[:]))
|
|
if n == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
if n > nativeUDPMaxPacket {
|
|
return nil, fmt.Errorf("xudp payload too large: %d", n)
|
|
}
|
|
pkt := make([]byte, n)
|
|
_, err := io.ReadFull(r, pkt)
|
|
return pkt, err
|
|
}
|
|
|
|
func writeVLESSXUDPPacket(w io.Writer, payload []byte) error {
|
|
if len(payload) > nativeUDPMaxPacket {
|
|
return fmt.Errorf("udp packet too large: %d", len(payload))
|
|
}
|
|
// Metadata length 4, mux session id 0, command Keep, option payload-present.
|
|
// This is accepted by Xray's xudp.PacketReader for responses when the UDP
|
|
// destination is already known from the request header.
|
|
var header [8]byte
|
|
binary.BigEndian.PutUint16(header[0:2], 4)
|
|
header[2] = 0
|
|
header[3] = 0
|
|
header[4] = 2 // Keep
|
|
header[5] = 1 // Opt: payload follows
|
|
binary.BigEndian.PutUint16(header[6:8], uint16(len(payload)))
|
|
if _, err := w.Write(header[:]); err != nil {
|
|
return err
|
|
}
|
|
_, err := w.Write(payload)
|
|
return err
|
|
}
|
|
|
|
// nativeVMessUDPTunnel maps one VMess body chunk to one UDP datagram. VMess AEAD
|
|
// chunking already preserves packet boundaries, so no extra VLESS length prefix
|
|
// is added inside the encrypted body.
|
|
func nativeVMessUDPTunnel(client nativeVMessStream, backend net.Conn, uuid, email string, up, down *rate.Limiter) {
|
|
xrayMgr.recordNativeConnect(uuid, email)
|
|
defer xrayMgr.recordNativeDisconnect(uuid, email)
|
|
|
|
upMeter := &trafficMeter{uuid: uuid, email: email, uplink: true}
|
|
downMeter := &trafficMeter{uuid: uuid, email: email, uplink: false}
|
|
|
|
var wg sync.WaitGroup
|
|
closeAll := func() {
|
|
_ = backend.Close()
|
|
_ = client.Close()
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
pkt, err := client.ReadPacket()
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
log.Printf("native xray: VMess UDP client read failed: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if len(pkt) == 0 {
|
|
continue
|
|
}
|
|
if err := waitNativeRate(up, len(pkt)); err != nil {
|
|
return
|
|
}
|
|
n, err := backend.Write(pkt)
|
|
if n > 0 {
|
|
upMeter.add(n)
|
|
}
|
|
if err != nil {
|
|
log.Printf("native xray: VMess UDP backend write failed: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
buf := make([]byte, nativeUDPBufferSize)
|
|
for {
|
|
_ = backend.SetReadDeadline(time.Now().Add(nativeUDPIdle))
|
|
n, err := backend.Read(buf)
|
|
if err != nil {
|
|
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
|
return
|
|
}
|
|
if err != io.EOF {
|
|
log.Printf("native xray: VMess UDP backend read failed: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if n <= 0 {
|
|
continue
|
|
}
|
|
if err := waitNativeRate(down, n); err != nil {
|
|
return
|
|
}
|
|
if err := client.WritePacket(buf[:n]); err != nil {
|
|
log.Printf("native xray: VMess UDP client write failed: %v", err)
|
|
return
|
|
}
|
|
downMeter.add(n)
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
upMeter.flush()
|
|
downMeter.flush()
|
|
closeAll()
|
|
}
|
|
|
|
func waitNativeRate(lim *rate.Limiter, n int) error {
|
|
if lim == nil || n <= 0 {
|
|
return nil
|
|
}
|
|
return lim.WaitN(context.Background(), n)
|
|
}
|