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 (
"fmt"
2023-04-23 12:27:29 +07:00
"net"
"net/netip"
2024-06-16 18:46:22 +07:00
2023-04-23 12:27:29 +07:00
"time"
2023-03-14 14:01:55 +07:00
"github.com/daeuniverse/dae/common"
"github.com/daeuniverse/dae/common/consts"
2023-07-13 18:04:48 +07:00
ob "github.com/daeuniverse/dae/component/outbound"
2023-03-14 14:01:55 +07:00
"github.com/daeuniverse/dae/component/outbound/dialer"
"github.com/daeuniverse/dae/component/sniffing"
2024-04-24 01:22:50 +07:00
"github.com/daeuniverse/outbound/pool"
2023-07-09 15:02:17 +07:00
dnsmessage "github.com/miekg/dns"
2023-03-14 17:22:00 +07:00
"github.com/sirupsen/logrus"
2023-01-23 18:54:21 +07:00
)
const (
DefaultNatTimeout = 3 * time . Minute
DnsNatTimeout = 17 * time . Second // RFC 5452
2023-11-15 13:32:57 +07:00
AnyfromTimeout = 5 * time . Second // Do not cache too long.
2023-02-13 09:52:40 +07:00
MaxRetry = 2
2023-01-23 18:54:21 +07:00
)
2023-07-13 18:04:48 +07:00
type DialOption struct {
2024-06-16 18:46:22 +07:00
Target string
Dialer * dialer . Dialer
Outbound * ob . DialerGroup
Network string
SniffedDomain string
2023-07-13 18:04:48 +07:00
}
2023-07-09 15:02:17 +07:00
func ChooseNatTimeout ( data [ ] byte , sniffDns bool ) ( dmsg * dnsmessage . Msg , timeout time . Duration ) {
2023-02-25 01:38:21 +07:00
if sniffDns {
2023-07-09 15:02:17 +07:00
var dnsmsg dnsmessage . Msg
2023-02-25 01:38:21 +07:00
if err := dnsmsg . Unpack ( data ) ; err == nil {
2023-07-09 15:02:17 +07:00
//log.Printf("DEBUG: lookup %v", dnsmsg.Question[0].Name)
2023-02-25 01:38:21 +07:00
return & dnsmsg , DnsNatTimeout
}
2023-01-23 18:54:21 +07:00
}
return nil , DefaultNatTimeout
}
2023-02-12 20:50:15 +07:00
// sendPkt uses bind first, and fallback to send hdr if addr is in use.
2024-01-23 20:11:44 +07:00
func sendPkt ( log * logrus . Logger , data [ ] byte , from netip . AddrPort , realTo , to netip . AddrPort , lConn * net . UDPConn ) ( err error ) {
2024-03-01 17:27:02 +07:00
uConn , _ , err := DefaultAnyfromPool . GetOrCreate ( from . String ( ) , AnyfromTimeout )
2023-02-06 12:56:43 +07:00
if err != nil {
2024-01-11 20:33:16 +07:00
return
2023-02-06 12:56:43 +07:00
}
2023-11-15 13:32:57 +07:00
_ , err = uConn . WriteToUDPAddrPort ( data , realTo )
2023-02-06 12:56:43 +07:00
return err
}
2023-11-15 13:32:57 +07:00
func ( c * ControlPlane ) handlePkt ( lConn * net . UDPConn , data [ ] byte , src , pktDst , realDst netip . AddrPort , routingResult * bpfRoutingResult , skipSniffing bool ) ( err error ) {
2023-02-12 20:50:15 +07:00
var realSrc netip . AddrPort
2023-02-15 00:53:53 +07:00
var domain string
2024-01-23 19:50:07 +07:00
realSrc = src
2024-06-16 18:46:22 +07:00
ue , ueExists := DefaultUdpEndpointPool . Get ( realSrc )
if ueExists && ue . SniffedDomain != "" {
// It is quic ...
// Fast path.
domain := ue . SniffedDomain
dialTarget := realDst . String ( )
if c . log . IsLevelEnabled ( logrus . TraceLevel ) {
fields := logrus . Fields {
"network" : "udp(fp)" ,
"outbound" : ue . Outbound . Name ,
"policy" : ue . Outbound . GetSelectionPolicy ( ) ,
"dialer" : ue . Dialer . Property ( ) . Name ,
"sniffed" : domain ,
"ip" : RefineAddrPortToShow ( realDst ) ,
"pid" : routingResult . Pid ,
"dscp" : routingResult . Dscp ,
"pname" : ProcessName2String ( routingResult . Pname [ : ] ) ,
"mac" : Mac2String ( routingResult . Mac [ : ] ) ,
}
c . log . WithFields ( fields ) . Tracef ( "%v <-> %v" , RefineSourceToShow ( realSrc , realDst . Addr ( ) ) , dialTarget )
}
_ , err = ue . WriteTo ( data , dialTarget )
if err != nil {
return err
}
return nil
}
2023-02-12 20:50:15 +07:00
2023-02-19 21:16:59 +07:00
// To keep consistency with kernel program, we only sniff DNS request sent to 53.
dnsMessage , natTimeout := ChooseNatTimeout ( data , realDst . Port ( ) == 53 )
2023-01-23 18:54:21 +07:00
// We should cache DNS records and set record TTL to 0, in order to monitor the dns req and resp in real time.
isDns := dnsMessage != nil
2024-06-16 18:46:22 +07:00
if ! isDns && ! skipSniffing && ! ueExists {
2023-04-12 20:45:50 +07:00
// Sniff Quic, ...
2023-11-15 13:32:57 +07:00
key := PacketSnifferKey {
LAddr : realSrc ,
RAddr : realDst ,
}
2024-06-16 18:46:22 +07:00
_sniffer , _ := DefaultPacketSnifferSessionMgr . GetOrCreate ( key , nil )
2023-11-15 13:32:57 +07:00
_sniffer . Mu . Lock ( )
// Re-get sniffer from pool to confirm the transaction is not done.
2024-06-16 18:46:22 +07:00
sniffer := DefaultPacketSnifferSessionMgr . Get ( key )
2023-11-15 13:32:57 +07:00
if _sniffer == sniffer {
sniffer . AppendData ( data )
domain , err = sniffer . SniffUdp ( )
if err != nil && ! sniffing . IsSniffingError ( err ) {
sniffer . Mu . Unlock ( )
return err
}
if sniffer . NeedMore ( ) {
sniffer . Mu . Unlock ( )
return nil
}
if err != nil {
logrus . WithError ( err ) .
WithField ( "from" , realSrc ) .
WithField ( "to" , realDst ) .
Trace ( "sniffUdp" )
}
2024-06-16 18:46:22 +07:00
defer DefaultPacketSnifferSessionMgr . Remove ( key , sniffer )
2023-11-15 13:32:57 +07:00
// Re-handlePkt after self func.
toRehandle := sniffer . Data ( ) [ 1 : len ( sniffer . Data ( ) ) - 1 ] // Skip the first empty and the last (self).
sniffer . Mu . Unlock ( )
if len ( toRehandle ) > 0 {
defer func ( ) {
if err == nil {
for _ , d := range toRehandle {
dCopy := pool . Get ( len ( d ) )
copy ( dCopy , d )
go c . handlePkt ( lConn , dCopy , src , pktDst , realDst , routingResult , true )
}
}
} ( )
}
} else {
_sniffer . Mu . Unlock ( )
// sniffer may be nil.
2023-02-15 00:53:53 +07:00
}
2023-02-04 14:28:48 +07:00
}
2023-04-02 10:07:53 +07:00
if routingResult . Must > 0 {
isDns = false // Regard as plain traffic.
}
2023-06-04 10:38:05 +07:00
if routingResult . Mark == 0 {
routingResult . Mark = c . soMarkFromDae
}
2023-02-25 01:38:21 +07:00
if isDns {
return c . dnsController . Handle_ ( dnsMessage , & udpRequest {
realSrc : realSrc ,
realDst : realDst ,
src : src ,
lConn : lConn ,
routingResult : routingResult ,
} )
2023-02-12 14:39:00 +07:00
}
2023-02-25 01:38:21 +07:00
2023-02-09 10:40:34 +07:00
// Dial and send.
2023-02-17 23:49:35 +07:00
// TODO: Rewritten domain should not use full-cone (such as VMess Packet Addr).
// Maybe we should set up a mapping for UDP: Dialer + Target Domain => Remote Resolved IP.
2023-02-25 01:38:21 +07:00
// However, games may not use QUIC for communication, thus we cannot use domain to dial, which is fine.
2023-02-17 23:49:35 +07:00
2023-02-25 01:38:21 +07:00
// Get udp endpoint.
retry := 0
2023-07-13 18:04:48 +07:00
networkType := & dialer . NetworkType {
L4Proto : consts . L4ProtoStr_UDP ,
IpVersion : consts . IpVersionFromAddr ( realDst . Addr ( ) ) ,
2023-08-05 17:18:21 +07:00
IsDns : false ,
2023-07-13 18:04:48 +07:00
}
// Get outbound.
outboundIndex := consts . OutboundIndex ( routingResult . Outbound )
2023-11-15 13:32:57 +07:00
var (
dialTarget string
shouldReroute bool
dialIp bool
)
_ , shouldReroute , _ = c . ChooseDialTarget ( outboundIndex , realDst , domain )
// Do not overwrite target.
// This fixes a problem that quic connection to google servers.
// Reproduce:
// docker run --rm --name curl-http3 ymuski/curl-http3 curl --http3 -o /dev/null -v -L https://i.ytimg.com
dialTarget = realDst . String ( )
dialIp = true
2023-02-25 01:38:21 +07:00
getNew :
if retry > MaxRetry {
2023-03-24 01:35:09 +07:00
c . log . WithFields ( logrus . Fields {
2024-01-23 19:50:07 +07:00
"src" : RefineSourceToShow ( realSrc , realDst . Addr ( ) ) ,
2023-03-24 01:35:09 +07:00
"network" : networkType . String ( ) ,
"dialer" : ue . Dialer . Property ( ) . Name ,
"retry" : retry ,
} ) . Warnln ( "Touch max retry limit." )
2023-02-25 01:38:21 +07:00
return fmt . Errorf ( "touch max retry limit" )
}
ue , isNew , err := DefaultUdpEndpointPool . GetOrCreate ( realSrc , & UdpEndpointOptions {
// Handler handles response packets and send it to the client.
Handler : func ( data [ ] byte , from netip . AddrPort ) ( err error ) {
// Do not return conn-unrelated err in this func.
2024-01-23 20:11:44 +07:00
return sendPkt ( c . log , data , from , realSrc , src , lConn )
2023-02-25 01:38:21 +07:00
} ,
NatTimeout : natTimeout ,
2023-07-13 18:04:48 +07:00
GetDialOption : func ( ) ( option * DialOption , err error ) {
if shouldReroute {
outboundIndex = consts . OutboundControlPlaneRouting
}
switch outboundIndex {
case consts . OutboundDirect :
case consts . OutboundControlPlaneRouting :
if isDns {
// Routing of DNS packets are managed by DNS controller.
break
}
if outboundIndex , routingResult . Mark , _ , err = c . Route ( realSrc , realDst , domain , consts . L4ProtoType_TCP , routingResult ) ; err != nil {
return nil , err
}
routingResult . Outbound = uint8 ( outboundIndex )
if c . log . IsLevelEnabled ( logrus . TraceLevel ) {
c . log . Tracef ( "outbound: %v => %v" ,
consts . OutboundControlPlaneRouting . String ( ) ,
outboundIndex . String ( ) ,
)
}
2023-11-15 13:32:57 +07:00
// Do not overwrite target.
// This fixes quic problem from google.
// Reproduce:
// docker run --rm --name curl-http3 ymuski/curl-http3 curl --http3 -o /dev/null -v -L https://i.ytimg.com
2023-07-13 18:04:48 +07:00
default :
}
if int ( outboundIndex ) >= len ( c . outbounds ) {
2023-07-15 11:23:36 +07:00
if len ( c . outbounds ) == int ( consts . OutboundUserDefinedMin ) {
return nil , fmt . Errorf ( "traffic was dropped due to no-load configuration" )
}
2023-07-13 18:04:48 +07:00
return nil , fmt . Errorf ( "outbound %v out of range [0, %v]" , outboundIndex , len ( c . outbounds ) - 1 )
}
outbound := c . outbounds [ outboundIndex ]
// Select dialer from outbound (dialer group).
strictIpVersion := dialIp
dialerForNew , _ , err := outbound . Select ( networkType , strictIpVersion )
if err != nil {
return nil , fmt . Errorf ( "failed to select dialer from group %v (%v, dns?:%v,from: %v): %w" , outbound . Name , networkType . StringWithoutDns ( ) , isDns , realSrc . String ( ) , err )
}
return & DialOption {
2024-06-16 18:46:22 +07:00
Target : dialTarget ,
Dialer : dialerForNew ,
Outbound : outbound ,
Network : common . MagicNetwork ( "udp" , routingResult . Mark ) ,
SniffedDomain : domain ,
2023-07-13 18:04:48 +07:00
} , nil
} ,
2023-02-25 01:38:21 +07:00
} )
if err != nil {
2023-07-13 18:04:48 +07:00
return fmt . Errorf ( "failed to GetOrCreate: %w" , err )
2023-02-25 01:38:21 +07:00
}
2023-02-09 10:40:34 +07:00
2023-02-25 01:38:21 +07:00
// If the udp endpoint has been not alive, remove it from pool and get a new one.
2023-07-13 18:04:48 +07:00
if ! isNew && ue . Outbound . GetSelectionPolicy ( ) != consts . DialerSelectionPolicy_Fixed && ! ue . Dialer . MustGetAlive ( networkType ) {
2023-02-09 10:40:34 +07:00
2023-02-25 01:38:21 +07:00
if c . log . IsLevelEnabled ( logrus . DebugLevel ) {
c . log . WithFields ( logrus . Fields {
2024-01-23 19:50:07 +07:00
"src" : RefineSourceToShow ( realSrc , realDst . Addr ( ) ) ,
2023-02-25 01:38:21 +07:00
"network" : networkType . String ( ) ,
2023-02-28 20:25:15 +07:00
"dialer" : ue . Dialer . Property ( ) . Name ,
2023-02-25 01:38:21 +07:00
"retry" : retry ,
} ) . Debugln ( "Old udp endpoint was not alive and removed." )
2023-02-09 10:40:34 +07:00
}
2023-02-25 01:38:21 +07:00
_ = DefaultUdpEndpointPool . Remove ( realSrc , ue )
retry ++
goto getNew
}
2024-06-16 18:46:22 +07:00
if domain == "" {
// It is used for showing.
domain = ue . SniffedDomain
}
2023-02-09 10:40:34 +07:00
2023-02-25 01:38:21 +07:00
_ , err = ue . WriteTo ( data , dialTarget )
if err != nil {
if c . log . IsLevelEnabled ( logrus . DebugLevel ) {
c . log . WithFields ( logrus . Fields {
"to" : realDst . String ( ) ,
"domain" : domain ,
"pid" : routingResult . Pid ,
2023-08-20 22:43:33 +07:00
"dscp" : routingResult . Dscp ,
2023-02-25 01:38:21 +07:00
"pname" : ProcessName2String ( routingResult . Pname [ : ] ) ,
"mac" : Mac2String ( routingResult . Mac [ : ] ) ,
"from" : realSrc . String ( ) ,
"network" : networkType . StringWithoutDns ( ) ,
"err" : err . Error ( ) ,
"retry" : retry ,
} ) . Debugln ( "Failed to write UDP packet request. Try to remove old UDP endpoint and retry." )
2023-02-09 10:40:34 +07:00
}
2023-02-25 01:38:21 +07:00
_ = DefaultUdpEndpointPool . Remove ( realSrc , ue )
retry ++
goto getNew
2023-02-09 10:40:34 +07:00
}
// Print log.
2023-02-25 01:38:21 +07:00
// Only print routing for new connection to avoid the log exploded (Quic and BT).
2023-11-15 13:32:57 +07:00
if ( isNew && c . log . IsLevelEnabled ( logrus . InfoLevel ) ) || c . log . IsLevelEnabled ( logrus . DebugLevel ) {
2023-07-13 18:04:48 +07:00
fields := logrus . Fields {
"network" : networkType . StringWithoutDns ( ) ,
"outbound" : ue . Outbound . Name ,
"policy" : ue . Outbound . GetSelectionPolicy ( ) ,
"dialer" : ue . Dialer . Property ( ) . Name ,
2023-11-15 13:32:57 +07:00
"sniffed" : domain ,
2023-07-13 18:04:48 +07:00
"ip" : RefineAddrPortToShow ( realDst ) ,
"pid" : routingResult . Pid ,
2023-08-20 22:43:33 +07:00
"dscp" : routingResult . Dscp ,
2023-07-13 18:04:48 +07:00
"pname" : ProcessName2String ( routingResult . Pname [ : ] ) ,
"mac" : Mac2String ( routingResult . Mac [ : ] ) ,
2023-02-08 20:32:20 +07:00
}
2024-06-16 18:46:22 +07:00
logger := c . log . WithFields ( fields ) . Infof
if ! isNew && c . log . IsLevelEnabled ( logrus . DebugLevel ) {
logger = c . log . WithFields ( fields ) . Debugf
}
logger ( "%v <-> %v" , RefineSourceToShow ( realSrc , realDst . Addr ( ) ) , dialTarget )
2023-01-23 18:54:21 +07:00
}
2023-02-09 10:40:34 +07:00
2023-01-23 18:54:21 +07:00
return nil
}