Native Xray
This commit is contained in:
@@ -0,0 +1,381 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user