2023-01-23 18:54:21 +07:00
|
|
|
/*
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
2024-01-04 16:28:16 +07:00
|
|
|
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
|
2023-01-23 18:54:21 +07:00
|
|
|
*/
|
|
|
|
|
|
|
|
package control
|
|
|
|
|
|
|
|
import (
|
2023-07-13 18:04:48 +07:00
|
|
|
"context"
|
2023-01-23 18:54:21 +07:00
|
|
|
"fmt"
|
|
|
|
"net/netip"
|
|
|
|
"sync"
|
|
|
|
"time"
|
2023-04-23 12:27:29 +07:00
|
|
|
|
2023-07-11 00:25:05 +07:00
|
|
|
"github.com/daeuniverse/dae/common/consts"
|
2023-07-13 18:04:48 +07:00
|
|
|
"github.com/daeuniverse/dae/component/outbound"
|
2023-04-23 12:27:29 +07:00
|
|
|
"github.com/daeuniverse/dae/component/outbound/dialer"
|
2024-04-24 01:22:50 +07:00
|
|
|
"github.com/daeuniverse/outbound/netproxy"
|
|
|
|
"github.com/daeuniverse/outbound/pool"
|
2023-01-23 18:54:21 +07:00
|
|
|
)
|
|
|
|
|
|
|
|
type UdpHandler func(data []byte, from netip.AddrPort) error
|
|
|
|
|
|
|
|
type UdpEndpoint struct {
|
2023-02-17 23:49:35 +07:00
|
|
|
conn netproxy.PacketConn
|
2023-01-23 18:54:21 +07:00
|
|
|
// mu protects deadlineTimer
|
|
|
|
mu sync.Mutex
|
2023-02-20 17:06:54 +07:00
|
|
|
deadlineTimer *time.Timer
|
2023-01-23 18:54:21 +07:00
|
|
|
handler UdpHandler
|
|
|
|
NatTimeout time.Duration
|
2023-02-04 14:28:48 +07:00
|
|
|
|
2023-07-13 18:04:48 +07:00
|
|
|
Dialer *dialer.Dialer
|
|
|
|
Outbound *outbound.DialerGroup
|
2024-06-16 18:46:22 +07:00
|
|
|
|
|
|
|
// Non-empty indicates this UDP Endpoint is related with a sniffed domain.
|
|
|
|
SniffedDomain string
|
|
|
|
DialTarget string
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ue *UdpEndpoint) start() {
|
2023-07-11 00:25:05 +07:00
|
|
|
buf := pool.GetFullCap(consts.EthernetMtu)
|
2023-01-23 18:54:21 +07:00
|
|
|
defer pool.Put(buf)
|
|
|
|
for {
|
|
|
|
n, from, err := ue.conn.ReadFrom(buf[:])
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
ue.mu.Lock()
|
|
|
|
ue.deadlineTimer.Reset(ue.NatTimeout)
|
|
|
|
ue.mu.Unlock()
|
2023-02-17 23:49:35 +07:00
|
|
|
if err = ue.handler(buf[:n], from); err != nil {
|
2023-01-23 18:54:21 +07:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ue.mu.Lock()
|
2023-02-20 17:06:54 +07:00
|
|
|
ue.deadlineTimer.Stop()
|
2023-01-23 18:54:21 +07:00
|
|
|
ue.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2023-02-17 23:49:35 +07:00
|
|
|
func (ue *UdpEndpoint) WriteTo(b []byte, addr string) (int, error) {
|
|
|
|
return ue.conn.WriteTo(b, addr)
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
|
2023-02-20 17:06:54 +07:00
|
|
|
func (ue *UdpEndpoint) Close() error {
|
2023-01-23 18:54:21 +07:00
|
|
|
ue.mu.Lock()
|
|
|
|
if ue.deadlineTimer != nil {
|
|
|
|
ue.deadlineTimer.Stop()
|
|
|
|
}
|
|
|
|
ue.mu.Unlock()
|
2023-02-20 17:06:54 +07:00
|
|
|
return ue.conn.Close()
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
// UdpEndpointPool is a full-cone udp conn pool
|
|
|
|
type UdpEndpointPool struct {
|
2023-07-13 18:04:48 +07:00
|
|
|
pool sync.Map
|
|
|
|
createMuMap sync.Map
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
type UdpEndpointOptions struct {
|
|
|
|
Handler UdpHandler
|
|
|
|
NatTimeout time.Duration
|
2023-07-13 18:04:48 +07:00
|
|
|
// GetTarget is useful only if the underlay does not support Full-cone.
|
|
|
|
GetDialOption func() (option *DialOption, err error)
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
var DefaultUdpEndpointPool = NewUdpEndpointPool()
|
|
|
|
|
|
|
|
func NewUdpEndpointPool() *UdpEndpointPool {
|
2023-07-13 18:04:48 +07:00
|
|
|
return &UdpEndpointPool{}
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
|
2023-02-08 20:28:08 +07:00
|
|
|
func (p *UdpEndpointPool) Remove(lAddr netip.AddrPort, udpEndpoint *UdpEndpoint) (err error) {
|
2023-07-13 18:04:48 +07:00
|
|
|
if ue, ok := p.pool.LoadAndDelete(lAddr); ok {
|
2023-02-08 20:28:08 +07:00
|
|
|
if ue != udpEndpoint {
|
2023-11-15 13:32:57 +07:00
|
|
|
udpEndpoint.Close()
|
2023-02-08 20:28:08 +07:00
|
|
|
return fmt.Errorf("target udp endpoint is not in the pool")
|
|
|
|
}
|
2023-07-13 18:04:48 +07:00
|
|
|
ue.(*UdpEndpoint).Close()
|
2023-02-08 20:28:08 +07:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2023-02-08 20:32:20 +07:00
|
|
|
|
2024-06-16 18:46:22 +07:00
|
|
|
func (p *UdpEndpointPool) Get(lAddr netip.AddrPort) (udpEndpoint *UdpEndpoint, ok bool) {
|
|
|
|
_ue, ok := p.pool.Load(lAddr)
|
|
|
|
if !ok {
|
|
|
|
return nil, ok
|
|
|
|
}
|
|
|
|
return _ue.(*UdpEndpoint), ok
|
2023-11-15 13:32:57 +07:00
|
|
|
}
|
|
|
|
|
2023-02-08 20:28:08 +07:00
|
|
|
func (p *UdpEndpointPool) GetOrCreate(lAddr netip.AddrPort, createOption *UdpEndpointOptions) (udpEndpoint *UdpEndpoint, isNew bool, err error) {
|
2023-07-13 18:04:48 +07:00
|
|
|
_ue, ok := p.pool.Load(lAddr)
|
|
|
|
begin:
|
2023-01-23 18:54:21 +07:00
|
|
|
if !ok {
|
2023-07-13 18:04:48 +07:00
|
|
|
createMu, _ := p.createMuMap.LoadOrStore(lAddr, &sync.Mutex{})
|
|
|
|
createMu.(*sync.Mutex).Lock()
|
|
|
|
defer createMu.(*sync.Mutex).Unlock()
|
|
|
|
defer p.createMuMap.Delete(lAddr)
|
|
|
|
_ue, ok = p.pool.Load(lAddr)
|
|
|
|
if ok {
|
|
|
|
goto begin
|
|
|
|
}
|
2023-01-23 18:54:21 +07:00
|
|
|
// Create an UdpEndpoint.
|
|
|
|
if createOption == nil {
|
|
|
|
createOption = &UdpEndpointOptions{}
|
|
|
|
}
|
|
|
|
if createOption.NatTimeout == 0 {
|
|
|
|
createOption.NatTimeout = DefaultNatTimeout
|
|
|
|
}
|
|
|
|
if createOption.Handler == nil {
|
2023-02-08 20:28:08 +07:00
|
|
|
return nil, true, fmt.Errorf("createOption.Handler cannot be nil")
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
|
|
|
|
2023-07-13 18:04:48 +07:00
|
|
|
dialOption, err := createOption.GetDialOption()
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.TODO(), consts.DefaultDialTimeout)
|
|
|
|
defer cancel()
|
2024-09-26 21:40:29 +07:00
|
|
|
udpConn, err := dialOption.Dialer.DialContext(ctx, dialOption.Network, dialOption.Target)
|
2023-01-23 18:54:21 +07:00
|
|
|
if err != nil {
|
2023-02-08 20:28:08 +07:00
|
|
|
return nil, true, err
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
2023-02-17 23:49:35 +07:00
|
|
|
if _, ok = udpConn.(netproxy.PacketConn); !ok {
|
2023-02-11 20:10:34 +07:00
|
|
|
return nil, true, fmt.Errorf("protocol does not support udp")
|
|
|
|
}
|
2023-07-13 18:04:48 +07:00
|
|
|
ue := &UdpEndpoint{
|
2023-11-15 13:32:57 +07:00
|
|
|
conn: udpConn.(netproxy.PacketConn),
|
|
|
|
deadlineTimer: nil,
|
|
|
|
handler: createOption.Handler,
|
|
|
|
NatTimeout: createOption.NatTimeout,
|
|
|
|
Dialer: dialOption.Dialer,
|
|
|
|
Outbound: dialOption.Outbound,
|
2024-06-16 18:46:22 +07:00
|
|
|
SniffedDomain: dialOption.SniffedDomain,
|
|
|
|
DialTarget: dialOption.Target,
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|
2023-11-15 13:32:57 +07:00
|
|
|
ue.deadlineTimer = time.AfterFunc(createOption.NatTimeout, func() {
|
|
|
|
if _ue, ok := p.pool.LoadAndDelete(lAddr); ok {
|
|
|
|
if _ue == ue {
|
|
|
|
ue.Close()
|
|
|
|
} else {
|
|
|
|
// FIXME: ?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2023-07-13 18:04:48 +07:00
|
|
|
_ue = ue
|
|
|
|
p.pool.Store(lAddr, ue)
|
2023-01-23 18:54:21 +07:00
|
|
|
// Receive UDP messages.
|
|
|
|
go ue.start()
|
2023-02-08 20:28:08 +07:00
|
|
|
isNew = true
|
2023-01-23 18:54:21 +07:00
|
|
|
} else {
|
2023-07-13 18:04:48 +07:00
|
|
|
ue := _ue.(*UdpEndpoint)
|
2023-01-23 18:54:21 +07:00
|
|
|
// Postpone the deadline.
|
|
|
|
ue.mu.Lock()
|
2023-02-20 17:06:54 +07:00
|
|
|
ue.deadlineTimer.Reset(ue.NatTimeout)
|
2023-01-23 18:54:21 +07:00
|
|
|
ue.mu.Unlock()
|
|
|
|
}
|
2023-07-13 18:04:48 +07:00
|
|
|
return _ue.(*UdpEndpoint), isNew, nil
|
2023-01-23 18:54:21 +07:00
|
|
|
}
|