Files
Xray-core/proxy/tun/udp_fullcone.go
T

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)
}
}