mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-17 04:53:59 +00:00
https://github.com/XTLS/Xray-core/pull/7130#issuecomment-6116258188 https://github.com/XTLS/Xray-core/pull/7130#issuecomment-6120742739 https://github.com/XTLS/Xray-core/pull/7130#issuecomment-6137264458 https://github.com/XTLS/Xray-core/pull/7156#issuecomment-6202933664 https://github.com/XTLS/Xray-core/pull/7156#issuecomment-6203239736
231 lines
5.2 KiB
Go
231 lines
5.2 KiB
Go
package tun
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/net"
|
|
)
|
|
|
|
// sub-handler specifically for udp connections under main handler
|
|
type udpConnectionHandler struct {
|
|
sync.RWMutex
|
|
|
|
udpConns map[net.Destination]*udpConn
|
|
|
|
handleConnection func(conn net.Conn, dest net.Destination)
|
|
writePacket func(data []byte, src net.Destination, dst net.Destination) error
|
|
}
|
|
|
|
func newUdpConnectionHandler(handleConnection func(conn net.Conn, dest net.Destination), writePacket func(data []byte, src net.Destination, dst net.Destination) error) *udpConnectionHandler {
|
|
handler := &udpConnectionHandler{
|
|
udpConns: make(map[net.Destination]*udpConn),
|
|
handleConnection: handleConnection,
|
|
writePacket: writePacket,
|
|
}
|
|
|
|
return handler
|
|
}
|
|
|
|
// HandlePacket handles UDP packets coming from tun, to forward to the dispatcher
|
|
// this custom handler support FullCone NAT of returning packets, binding connection only by the source addr:port
|
|
func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destination, data *buf.Buffer) {
|
|
data.UDP = &dst
|
|
|
|
u.RLock()
|
|
conn, found := u.udpConns[src]
|
|
if found {
|
|
if !conn.queue.push(data) {
|
|
errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full")
|
|
data.Release()
|
|
}
|
|
u.RUnlock()
|
|
return
|
|
}
|
|
u.RUnlock()
|
|
|
|
u.Lock()
|
|
defer u.Unlock()
|
|
|
|
conn, found = u.udpConns[src]
|
|
if !found {
|
|
conn = &udpConn{handler: u, queue: newPacketQueue(udpQueueLimit), src: src, dst: dst}
|
|
u.udpConns[src] = conn
|
|
|
|
go u.handleConnection(conn, dst)
|
|
}
|
|
|
|
// queue the packet for the reader of the connection, or discard it
|
|
if !conn.queue.push(data) {
|
|
errors.LogDebug(context.Background(), "drop udp with size ", data.Len(), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full 2")
|
|
data.Release()
|
|
}
|
|
}
|
|
|
|
func (u *udpConnectionHandler) connectionFinished(conn *udpConn) {
|
|
u.Lock()
|
|
// Close runs twice per flow; a newer conn may already own this src.
|
|
if u.udpConns[conn.src] == conn {
|
|
delete(u.udpConns, conn.src)
|
|
conn.queue.close()
|
|
}
|
|
u.Unlock()
|
|
}
|
|
|
|
// udp connection abstraction
|
|
type udpConn struct {
|
|
handler *udpConnectionHandler
|
|
|
|
queue *packetQueue
|
|
src net.Destination
|
|
dst net.Destination
|
|
}
|
|
|
|
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|
b, ok := c.queue.pop()
|
|
if !ok {
|
|
return nil, io.EOF
|
|
}
|
|
return buf.MultiBuffer{b}, nil
|
|
}
|
|
|
|
// Read packets from the connection
|
|
func (c *udpConn) Read(p []byte) (int, error) {
|
|
b, ok := c.queue.pop()
|
|
if !ok {
|
|
return 0, io.EOF
|
|
}
|
|
defer b.Release()
|
|
n := copy(p, b.Bytes())
|
|
if n != int(b.Len()) {
|
|
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 {
|
|
if b.UDP.Address.Family().IsDomain() {
|
|
errors.LogError(context.Background(), "impossible domain packet ", b.UDP, " reply via original target ", dst)
|
|
} else {
|
|
dst = *b.UDP
|
|
}
|
|
}
|
|
err := c.handler.writePacket(b.Bytes(), dst, c.src)
|
|
if err != nil {
|
|
buf.ReleaseMulti(mb[i:])
|
|
return err
|
|
}
|
|
b.Release()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write returning packets back
|
|
func (c *udpConn) Write(p []byte) (int, error) {
|
|
// sending packets back mean sending payload with source/destination reversed
|
|
err := c.handler.writePacket(p, c.dst, c.src)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(p), nil
|
|
}
|
|
|
|
func (c *udpConn) Close() error {
|
|
c.handler.connectionFinished(c)
|
|
|
|
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
|
|
}
|
|
|
|
// udpQueueLimit bounds the packets waiting for one UDP flow; more are dropped.
|
|
const udpQueueLimit = 1024
|
|
|
|
// packetQueue holds the packets waiting for one UDP flow. Unlike a buffered
|
|
// channel of the same bound it only allocates for packets actually queued, so
|
|
// the many idle flows kept until the idle timeout cost next to nothing.
|
|
type packetQueue struct {
|
|
mu sync.Mutex
|
|
items []*buf.Buffer
|
|
limit int
|
|
notify chan struct{}
|
|
closed bool
|
|
}
|
|
|
|
func newPacketQueue(limit int) *packetQueue {
|
|
return &packetQueue{limit: limit, notify: make(chan struct{}, 1)}
|
|
}
|
|
|
|
// push queues b and reports whether it was accepted.
|
|
func (q *packetQueue) push(b *buf.Buffer) bool {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
if q.closed || len(q.items) >= q.limit {
|
|
return false
|
|
}
|
|
q.items = append(q.items, b)
|
|
select {
|
|
case q.notify <- struct{}{}:
|
|
default:
|
|
}
|
|
return true
|
|
}
|
|
|
|
// pop blocks until a packet is queued or the queue is closed and drained.
|
|
func (q *packetQueue) pop() (*buf.Buffer, bool) {
|
|
for {
|
|
q.mu.Lock()
|
|
if len(q.items) > 0 {
|
|
b := q.items[0]
|
|
q.items[0] = nil
|
|
q.items = q.items[1:]
|
|
if len(q.items) == 0 {
|
|
q.items = nil
|
|
}
|
|
q.mu.Unlock()
|
|
return b, true
|
|
}
|
|
if q.closed {
|
|
q.mu.Unlock()
|
|
return nil, false
|
|
}
|
|
q.mu.Unlock()
|
|
<-q.notify
|
|
}
|
|
}
|
|
|
|
func (q *packetQueue) close() {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
if !q.closed {
|
|
q.closed = true
|
|
close(q.notify)
|
|
}
|
|
}
|