mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-04-16 12:35:54 +00:00
1. **Fixed XPadding Placement Dropdown**: - Added the missing `cookie` and `query` options to `xPaddingPlacement` (`stream_xhttp.html`). - *Why:* Previously, users wanting `cookie` obfuscation were forced to use the `header` placement string. This caused Xray-core to blindly intercept the entire monolithic HTTP Cookie header, failing internal padding-length validations and causing the inbound to silently drop the connection. 2. **Fixed Uplink Data Placement Validation**: - Replaced the unsupported `query` option with `cookie` in `uplinkDataPlacement`. - *Why:* Xray-core's `transport_internet.go` explicitly forbids `query` as an uplink placement option. Selecting it from the UI previously sent a payload that would cause Xray-core to instantly throw an `unsupported uplink data placement: query` panic. Adding `cookie` perfectly aligns the UI with Xray-core restrictions. ### Related Issues - Resolves #3992
429 lines
10 KiB
Go
429 lines
10 KiB
Go
package wireguard
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/netip"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/log"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/proxy/wireguard/gvisortun"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
|
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
|
|
"golang.zx2c4.com/wireguard/conn"
|
|
"golang.zx2c4.com/wireguard/device"
|
|
"golang.zx2c4.com/wireguard/tun"
|
|
)
|
|
|
|
type tunCreator func(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error)
|
|
|
|
type promiscuousModeHandler func(dest net.Destination, conn net.Conn)
|
|
|
|
type Tunnel interface {
|
|
BuildDevice(ipc string, bind conn.Bind) error
|
|
DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (net.Conn, error)
|
|
DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error)
|
|
Close() error
|
|
}
|
|
|
|
type tunnel struct {
|
|
tun tun.Device
|
|
device *device.Device
|
|
rw sync.Mutex
|
|
}
|
|
|
|
func (t *tunnel) BuildDevice(ipc string, bind conn.Bind) (err error) {
|
|
t.rw.Lock()
|
|
defer t.rw.Unlock()
|
|
|
|
if t.device != nil {
|
|
return errors.New("device is already initialized")
|
|
}
|
|
|
|
logger := &device.Logger{
|
|
Verbosef: func(format string, args ...any) {
|
|
log.Record(&log.GeneralMessage{
|
|
Severity: log.Severity_Debug,
|
|
Content: fmt.Sprintf(format, args...),
|
|
})
|
|
},
|
|
Errorf: func(format string, args ...any) {
|
|
log.Record(&log.GeneralMessage{
|
|
Severity: log.Severity_Error,
|
|
Content: fmt.Sprintf(format, args...),
|
|
})
|
|
},
|
|
}
|
|
|
|
t.device = device.NewDevice(t.tun, bind, logger)
|
|
if err = t.device.IpcSet(ipc); err != nil {
|
|
return err
|
|
}
|
|
if err = t.device.Up(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tunnel) Close() (err error) {
|
|
t.rw.Lock()
|
|
defer t.rw.Unlock()
|
|
|
|
if t.device == nil {
|
|
return nil
|
|
}
|
|
|
|
t.device.Close()
|
|
t.device = nil
|
|
err = t.tun.Close()
|
|
t.tun = nil
|
|
return nil
|
|
}
|
|
|
|
func CalculateInterfaceName(name string) (tunName string) {
|
|
if runtime.GOOS == "darwin" {
|
|
tunName = "utun"
|
|
} else if name != "" {
|
|
tunName = name
|
|
} else {
|
|
tunName = "tun"
|
|
}
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return
|
|
}
|
|
var tunIndex int
|
|
for _, netInterface := range interfaces {
|
|
if strings.HasPrefix(netInterface.Name, tunName) {
|
|
index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)
|
|
if parseErr == nil {
|
|
tunIndex = int(index) + 1
|
|
}
|
|
}
|
|
}
|
|
tunName = fmt.Sprintf("%s%d", tunName, tunIndex)
|
|
return
|
|
}
|
|
|
|
var _ Tunnel = (*gvisorNet)(nil)
|
|
|
|
type gvisorNet struct {
|
|
tunnel
|
|
net *gvisortun.Net
|
|
}
|
|
|
|
func (g *gvisorNet) Close() error {
|
|
return g.tunnel.Close()
|
|
}
|
|
|
|
func (g *gvisorNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (
|
|
net.Conn, error,
|
|
) {
|
|
return g.net.DialContextTCPAddrPort(ctx, addr)
|
|
}
|
|
|
|
func (g *gvisorNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
|
|
return g.net.DialUDPAddrPort(laddr, raddr)
|
|
}
|
|
|
|
func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousModeHandler) (Tunnel, error) {
|
|
out := &gvisorNet{}
|
|
tun, n, gstack, err := gvisortun.CreateNetTUN(localAddresses, mtu, handler != nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if handler != nil {
|
|
// handler is only used for promiscuous mode
|
|
// capture all packets and send to handler
|
|
|
|
tcpForwarder := tcp.NewForwarder(gstack, 0, 65535, func(r *tcp.ForwarderRequest) {
|
|
go func(r *tcp.ForwarderRequest) {
|
|
var wq waiter.Queue
|
|
var id = r.ID()
|
|
|
|
ep, err := r.CreateEndpoint(&wq)
|
|
if err != nil {
|
|
errors.LogError(context.Background(), err.String())
|
|
r.Complete(true)
|
|
return
|
|
}
|
|
|
|
options := ep.SocketOptions()
|
|
options.SetKeepAlive(false)
|
|
options.SetReuseAddress(true)
|
|
options.SetReusePort(true)
|
|
|
|
handler(net.TCPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort)), gonet.NewTCPConn(&wq, ep))
|
|
|
|
ep.Close()
|
|
r.Complete(false)
|
|
}(r)
|
|
})
|
|
gstack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
|
|
|
|
manager := &udpManager{
|
|
stack: gstack,
|
|
handler: handler,
|
|
m: make(map[string]*udpConn),
|
|
}
|
|
|
|
gstack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
|
|
data := pkt.Clone().Data().AsRange().ToSlice()
|
|
// if len(data) == 0 {
|
|
// return false
|
|
// }
|
|
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
|
|
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
|
|
if srcIP == nil || dstIP == nil {
|
|
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " > invalid ip address ", id.RemoteAddress.AsSlice(), " ", id.LocalAddress.AsSlice())
|
|
return true
|
|
}
|
|
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
|
|
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
|
|
manager.feed(src, dst, data)
|
|
return true
|
|
})
|
|
}
|
|
|
|
out.tun, out.net = tun, n
|
|
return out, nil
|
|
}
|
|
|
|
type udpManager struct {
|
|
stack *stack.Stack
|
|
handler func(dest net.Destination, conn net.Conn)
|
|
m map[string]*udpConn
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte) {
|
|
m.mutex.RLock()
|
|
uc, ok := m.m[src.NetAddr()]
|
|
if ok {
|
|
select {
|
|
case uc.queue <- &packet{
|
|
p: data,
|
|
dest: &dst,
|
|
}:
|
|
default:
|
|
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
|
|
}
|
|
m.mutex.RUnlock()
|
|
return
|
|
}
|
|
m.mutex.RUnlock()
|
|
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
uc, ok = m.m[src.NetAddr()]
|
|
if !ok {
|
|
uc = &udpConn{
|
|
queue: make(chan *packet, 1024),
|
|
src: src,
|
|
dst: dst,
|
|
}
|
|
uc.writeFunc = m.writeRawUDPPacket
|
|
uc.closeFunc = func() {
|
|
m.mutex.Lock()
|
|
m.close(uc)
|
|
m.mutex.Unlock()
|
|
}
|
|
m.m[src.NetAddr()] = uc
|
|
go m.handler(dst, uc)
|
|
}
|
|
|
|
select {
|
|
case uc.queue <- &packet{
|
|
p: data,
|
|
dest: &dst,
|
|
}:
|
|
default:
|
|
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
|
|
}
|
|
}
|
|
|
|
func (m *udpManager) close(uc *udpConn) {
|
|
if !uc.closed {
|
|
uc.closed = true
|
|
close(uc.queue)
|
|
delete(m.m, uc.src.NetAddr())
|
|
}
|
|
}
|
|
|
|
func (m *udpManager) writeRawUDPPacket(payload []byte, src net.Destination, dst net.Destination) error {
|
|
udpLen := header.UDPMinimumSize + len(payload)
|
|
srcIP := tcpip.AddrFromSlice(src.Address.IP())
|
|
dstIP := tcpip.AddrFromSlice(dst.Address.IP())
|
|
|
|
// build packet with appropriate IP header size
|
|
isIPv4 := dst.Address.Family().IsIPv4()
|
|
ipHdrSize := header.IPv6MinimumSize
|
|
ipProtocol := header.IPv6ProtocolNumber
|
|
if isIPv4 {
|
|
ipHdrSize = header.IPv4MinimumSize
|
|
ipProtocol = header.IPv4ProtocolNumber
|
|
}
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: ipHdrSize + header.UDPMinimumSize,
|
|
Payload: buffer.MakeWithData(payload),
|
|
})
|
|
defer pkt.DecRef()
|
|
|
|
// Build UDP header
|
|
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
|
|
udpHdr.Encode(&header.UDPFields{
|
|
SrcPort: uint16(src.Port),
|
|
DstPort: uint16(dst.Port),
|
|
Length: uint16(udpLen),
|
|
})
|
|
|
|
// Calculate and set UDP checksum
|
|
xsum := header.PseudoHeaderChecksum(header.UDPProtocolNumber, srcIP, dstIP, uint16(udpLen))
|
|
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(checksum.Checksum(payload, xsum)))
|
|
|
|
// Build IP header
|
|
if isIPv4 {
|
|
ipHdr := header.IPv4(pkt.NetworkHeader().Push(header.IPv4MinimumSize))
|
|
ipHdr.Encode(&header.IPv4Fields{
|
|
TotalLength: uint16(header.IPv4MinimumSize + udpLen),
|
|
TTL: 64,
|
|
Protocol: uint8(header.UDPProtocolNumber),
|
|
SrcAddr: srcIP,
|
|
DstAddr: dstIP,
|
|
})
|
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
} else {
|
|
ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
|
|
ipHdr.Encode(&header.IPv6Fields{
|
|
PayloadLength: uint16(udpLen),
|
|
TransportProtocol: header.UDPProtocolNumber,
|
|
HopLimit: 64,
|
|
SrcAddr: srcIP,
|
|
DstAddr: dstIP,
|
|
})
|
|
}
|
|
|
|
// dispatch the packet
|
|
err := m.stack.WriteRawPacket(1, ipProtocol, buffer.MakeWithView(pkt.ToView()))
|
|
if err != nil {
|
|
return errors.New("failed to write raw udp packet back to stack err ", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type packet struct {
|
|
p []byte
|
|
dest *net.Destination
|
|
}
|
|
|
|
type udpConn struct {
|
|
queue chan *packet
|
|
src net.Destination
|
|
dst net.Destination
|
|
writeFunc func(payload []byte, src net.Destination, dst net.Destination) error
|
|
closeFunc func()
|
|
closed bool
|
|
}
|
|
|
|
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
for {
|
|
q, ok := <-c.queue
|
|
if !ok {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
b := buf.New()
|
|
|
|
_, err := b.Write(q.p)
|
|
if err != nil {
|
|
errors.LogDebugInner(context.Background(), err, "drop udp with size ", len(q.p), " to ", q.dest.NetAddr(), " original ", c.dst.NetAddr())
|
|
b.Release()
|
|
continue
|
|
}
|
|
|
|
b.UDP = q.dest
|
|
|
|
return buf.MultiBuffer{b}, nil
|
|
}
|
|
}
|
|
|
|
func (c *udpConn) Read(p []byte) (int, error) {
|
|
q, ok := <-c.queue
|
|
if !ok {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(p, q.p)
|
|
if n != len(q.p) {
|
|
return 0, io.ErrShortBuffer
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|
for i, b := range mb {
|
|
dst := c.dst
|
|
if b.UDP != nil {
|
|
dst = *b.UDP
|
|
}
|
|
err := c.writeFunc(b.Bytes(), dst, c.src)
|
|
if err != nil {
|
|
buf.ReleaseMulti(mb[i:])
|
|
return err
|
|
}
|
|
b.Release()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *udpConn) Write(p []byte) (int, error) {
|
|
err := c.writeFunc(p, c.dst, c.src)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (c *udpConn) Close() error {
|
|
c.closeFunc()
|
|
return nil
|
|
}
|
|
|
|
func (c *udpConn) LocalAddr() net.Addr {
|
|
return c.dst.RawNetAddr()
|
|
}
|
|
|
|
func (c *udpConn) RemoteAddr() net.Addr {
|
|
return c.src.RawNetAddr()
|
|
}
|
|
|
|
func (c *udpConn) SetDeadline(t time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *udpConn) SetReadDeadline(t time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *udpConn) SetWriteDeadline(t time.Time) error {
|
|
return nil
|
|
}
|