2024-01-04 16:28:16 +07:00
/ *
* SPDX - License - Identifier : AGPL - 3.0 - only
* Copyright ( c ) 2022 - 2024 , daeuniverse Organization < dae @ v2raya . org >
* /
2023-01-28 00:50:21 +07:00
package cmd
import (
2023-06-04 10:38:05 +07:00
"context"
2023-04-02 16:43:50 +07:00
"errors"
2023-01-28 00:50:21 +07:00
"fmt"
2023-04-02 16:43:50 +07:00
"net"
2023-03-15 15:19:32 +07:00
"net/http"
2023-01-28 00:50:21 +07:00
"os"
"os/signal"
2023-02-09 19:17:45 +07:00
"path/filepath"
2023-02-25 01:38:21 +07:00
"runtime"
2023-02-27 14:10:15 +07:00
"strconv"
2023-02-09 22:17:49 +07:00
"strings"
2023-01-28 00:50:21 +07:00
"syscall"
2023-03-15 15:19:32 +07:00
"time"
2023-04-23 12:27:29 +07:00
2023-08-05 17:18:21 +07:00
"github.com/daeuniverse/softwind/netproxy"
"github.com/daeuniverse/softwind/pkg/fastrand"
"github.com/daeuniverse/softwind/protocol/direct"
2023-07-15 11:23:36 +07:00
"gopkg.in/natefinch/lumberjack.v2"
2023-06-18 17:53:26 +07:00
2023-04-23 12:27:29 +07:00
"github.com/daeuniverse/dae/cmd/internal"
"github.com/daeuniverse/dae/common"
2024-03-27 12:01:37 +07:00
"github.com/daeuniverse/dae/common/consts"
2023-04-23 12:27:29 +07:00
"github.com/daeuniverse/dae/common/subscription"
"github.com/daeuniverse/dae/config"
"github.com/daeuniverse/dae/control"
"github.com/daeuniverse/dae/pkg/config_parser"
"github.com/daeuniverse/dae/pkg/logger"
"github.com/mohae/deepcopy"
"github.com/okzk/sdnotify"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2023-01-28 00:50:21 +07:00
)
2023-02-27 14:10:15 +07:00
const (
2024-03-27 12:01:37 +07:00
PidFilePath = "/var/run/dae.pid"
SignalProgressFilePath = "/var/run/dae.progress"
2023-02-27 14:10:15 +07:00
)
2023-03-19 11:26:09 +07:00
var (
CheckNetworkLinks = [ ] string {
2023-03-24 01:21:36 +07:00
"http://edge.microsoft.com/captiveportal/generate_204" ,
"http://www.gstatic.com/generate_204" ,
2023-03-19 11:26:09 +07:00
"http://www.qualcomm.cn/generate_204" ,
}
)
2023-02-27 14:10:15 +07:00
func init ( ) {
2023-07-15 11:23:36 +07:00
runCmd . PersistentFlags ( ) . StringVarP ( & cfgFile , "config" , "c" , "" , "Config file of dae." )
runCmd . PersistentFlags ( ) . StringVar ( & logFile , "logfile" , "" , "Log file to write. Empty means writing to stdout and stderr." )
runCmd . PersistentFlags ( ) . IntVar ( & logFileMaxSize , "logfile-maxsize" , 30 , "Unit: MB. The maximum size in megabytes of the log file before it gets rotated." )
runCmd . PersistentFlags ( ) . IntVar ( & logFileMaxBackups , "logfile-maxbackups" , 3 , "The maximum number of old log files to retain." )
runCmd . PersistentFlags ( ) . BoolVarP ( & disableTimestamp , "disable-timestamp" , "" , false , "Disable timestamp." )
runCmd . PersistentFlags ( ) . BoolVarP ( & disablePidFile , "disable-pidfile" , "" , false , "Not generate /var/run/dae.pid." )
2023-03-19 11:26:09 +07:00
2023-05-30 22:01:23 +07:00
fastrand . Rand ( ) . Shuffle ( len ( CheckNetworkLinks ) , func ( i , j int ) {
2023-03-19 11:26:09 +07:00
CheckNetworkLinks [ i ] , CheckNetworkLinks [ j ] = CheckNetworkLinks [ j ] , CheckNetworkLinks [ i ]
} )
2023-02-27 14:10:15 +07:00
}
2023-01-28 00:50:21 +07:00
var (
2023-07-15 11:23:36 +07:00
cfgFile string
logFile string
logFileMaxSize int
logFileMaxBackups int
disableTimestamp bool
disablePidFile bool
2023-01-28 00:50:21 +07:00
runCmd = & cobra . Command {
Use : "run" ,
2023-03-13 23:46:45 +07:00
Short : "To run dae in the foreground." ,
2023-01-28 00:50:21 +07:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2023-02-01 10:10:41 +07:00
if cfgFile == "" {
logrus . Fatalln ( "Argument \"--config\" or \"-c\" is required but not provided." )
}
2023-02-05 13:03:34 +07:00
2023-02-07 12:49:47 +07:00
// Require "sudo" if necessary.
internal . AutoSu ( )
2023-02-05 13:03:34 +07:00
// Read config from --config cfgFile.
2023-02-25 21:53:18 +07:00
conf , includes , err := readConfig ( cfgFile )
2023-02-05 13:03:34 +07:00
if err != nil {
2023-02-27 16:22:05 +07:00
logrus . WithFields ( logrus . Fields {
"err" : err ,
} ) . Fatalln ( "Failed to read config" )
2023-02-05 13:03:34 +07:00
}
2023-07-15 11:23:36 +07:00
var logOpts * lumberjack . Logger
if logFile != "" {
logOpts = & lumberjack . Logger {
Filename : logFile ,
MaxSize : logFileMaxSize ,
MaxAge : 0 ,
MaxBackups : logFileMaxBackups ,
LocalTime : true ,
Compress : true ,
}
}
log := logger . NewLogger ( conf . Global . LogLevel , disableTimestamp , logOpts )
2023-02-05 13:03:34 +07:00
logrus . SetLevel ( log . Level )
2023-02-09 22:17:49 +07:00
log . Infof ( "Include config files: [%v]" , strings . Join ( includes , ", " ) )
2023-03-24 23:57:04 +07:00
if err := Run ( log , conf , [ ] string { filepath . Dir ( cfgFile ) } ) ; err != nil {
2023-01-28 00:50:21 +07:00
logrus . Fatalln ( err )
}
} ,
}
)
2023-03-24 23:57:04 +07:00
func Run ( log * logrus . Logger , conf * config . Config , externGeoDataDirs [ ] string ) ( err error ) {
2023-11-14 15:26:33 +07:00
// Remove AbortFile at beginning.
_ = os . Remove ( AbortFile )
2023-01-28 00:50:21 +07:00
2023-02-27 12:29:42 +07:00
// New ControlPlane.
2023-03-24 23:57:04 +07:00
c , err := newControlPlane ( log , nil , nil , conf , externGeoDataDirs )
2023-02-27 12:29:42 +07:00
if err != nil {
return err
}
// Serve tproxy TCP/UDP server util signals.
var listener * control . Listener
sigs := make ( chan os . Signal , 1 )
2023-03-25 14:46:05 +07:00
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM , syscall . SIGHUP , syscall . SIGQUIT , syscall . SIGKILL , syscall . SIGILL , syscall . SIGUSR1 , syscall . SIGUSR2 )
2023-02-27 12:29:42 +07:00
go func ( ) {
2023-02-27 13:36:36 +07:00
readyChan := make ( chan bool , 1 )
go func ( ) {
<- readyChan
sdnotify . Ready ( )
2023-02-28 20:25:15 +07:00
if ! disablePidFile {
2023-02-27 14:10:15 +07:00
_ = os . WriteFile ( PidFilePath , [ ] byte ( strconv . Itoa ( os . Getpid ( ) ) ) , 0644 )
}
2024-03-27 12:01:37 +07:00
_ = os . WriteFile ( SignalProgressFilePath , [ ] byte { consts . ReloadDone } , 0644 )
2023-02-27 13:36:36 +07:00
} ( )
2024-03-01 17:27:02 +07:00
control . GetDaeNetns ( ) . With ( func ( ) error {
if listener , err = c . ListenAndServe ( readyChan , conf . Global . TproxyPort ) ; err != nil {
log . Errorln ( "ListenAndServe:" , err )
}
return err
} )
2023-02-27 12:29:42 +07:00
sigs <- nil
} ( )
reloading := false
2023-03-13 23:34:38 +07:00
isSuspend := false
2023-11-14 15:26:33 +07:00
abortConnections := false
2023-02-27 12:29:42 +07:00
loop :
for sig := range sigs {
switch sig {
case nil :
if reloading {
2023-06-11 15:58:55 +07:00
if listener == nil {
// Failed to listen. Exit.
break loop
}
2023-02-27 18:33:51 +07:00
// Serve.
2023-02-27 12:29:42 +07:00
reloading = false
log . Warnln ( "[Reload] Serve" )
2023-02-27 13:36:36 +07:00
readyChan := make ( chan bool , 1 )
2023-02-27 12:29:42 +07:00
go func ( ) {
2023-02-27 13:36:36 +07:00
if err := c . Serve ( readyChan , listener ) ; err != nil {
2023-02-27 12:29:42 +07:00
log . Errorln ( "ListenAndServe:" , err )
}
sigs <- nil
} ( )
2023-02-27 13:36:36 +07:00
<- readyChan
sdnotify . Ready ( )
2024-03-27 12:01:37 +07:00
_ = os . WriteFile ( SignalProgressFilePath , append ( [ ] byte { consts . ReloadDone } , [ ] byte ( "\nOK" ) ... ) , 0644 )
2023-02-27 13:36:36 +07:00
log . Warnln ( "[Reload] Finished" )
2023-02-27 12:29:42 +07:00
} else {
2023-02-27 18:33:51 +07:00
// Listening error.
2023-02-27 12:29:42 +07:00
break loop
}
2023-03-25 14:46:05 +07:00
case syscall . SIGUSR2 :
2023-03-13 23:34:38 +07:00
isSuspend = true
fallthrough
2023-02-27 12:29:42 +07:00
case syscall . SIGUSR1 :
// Reload signal.
2023-03-25 14:46:05 +07:00
if isSuspend {
log . Warnln ( "[Reload] Received suspend signal; prepare to suspend" )
} else {
log . Warnln ( "[Reload] Received reload signal; prepare to reload" )
}
2023-02-27 16:22:05 +07:00
sdnotify . Reloading ( )
2024-03-27 12:01:37 +07:00
_ = os . WriteFile ( SignalProgressFilePath , [ ] byte { consts . ReloadProcessing } , 0644 )
2023-02-27 16:22:05 +07:00
2023-02-27 18:33:51 +07:00
// Load new config.
2023-11-14 15:26:33 +07:00
abortConnections = os . Remove ( AbortFile ) == nil
2023-02-27 16:22:05 +07:00
log . Warnln ( "[Reload] Load new config" )
2023-03-13 23:34:38 +07:00
var newConf * config . Config
if isSuspend {
isSuspend = false
newConf , err = emptyConfig ( )
if err != nil {
log . WithFields ( logrus . Fields {
"err" : err ,
} ) . Errorln ( "[Reload] Failed to reload" )
sdnotify . Ready ( )
2024-03-27 12:01:37 +07:00
_ = os . WriteFile ( SignalProgressFilePath , append ( [ ] byte { consts . ReloadError } , [ ] byte ( "\n" + err . Error ( ) ) ... ) , 0644 )
2023-03-13 23:34:38 +07:00
continue
}
2023-09-02 14:06:50 +07:00
newConf . Global = deepcopy . Copy ( conf . Global ) . ( config . Global )
newConf . Global . WanInterface = nil
newConf . Global . LanInterface = nil
newConf . Global . LogLevel = "warning"
2023-03-13 23:34:38 +07:00
} else {
var includes [ ] string
newConf , includes , err = readConfig ( cfgFile )
if err != nil {
log . WithFields ( logrus . Fields {
"err" : err ,
} ) . Errorln ( "[Reload] Failed to reload" )
sdnotify . Ready ( )
2024-03-27 12:01:37 +07:00
_ = os . WriteFile ( SignalProgressFilePath , append ( [ ] byte { consts . ReloadError } , [ ] byte ( "\n" + err . Error ( ) ) ... ) , 0644 )
2023-03-13 23:34:38 +07:00
continue
}
log . Infof ( "Include config files: [%v]" , strings . Join ( includes , ", " ) )
2023-02-27 16:22:05 +07:00
}
2023-02-27 18:33:51 +07:00
// New logger.
2023-07-15 11:23:36 +07:00
oldLogOutput := log . Out
log = logger . NewLogger ( newConf . Global . LogLevel , disableTimestamp , nil )
log . SetOutput ( oldLogOutput ) // FIXME: THIS IS A HACK.
2023-02-27 18:33:51 +07:00
logrus . SetLevel ( log . Level )
2023-02-27 16:22:05 +07:00
2023-02-27 18:33:51 +07:00
// New control plane.
2023-02-27 12:29:42 +07:00
obj := c . EjectBpf ( )
2023-04-07 22:06:04 +07:00
var dnsCache map [ string ] * control . DnsCache
if conf . Dns . IpVersionPrefer == newConf . Dns . IpVersionPrefer {
// Only keep dns cache when ip version preference not change.
dnsCache = c . CloneDnsCache ( )
}
2023-02-27 12:29:42 +07:00
log . Warnln ( "[Reload] Load new control plane" )
2023-03-24 23:57:04 +07:00
newC , err := newControlPlane ( log , obj , dnsCache , newConf , externGeoDataDirs )
2023-02-27 12:29:42 +07:00
if err != nil {
log . WithFields ( logrus . Fields {
"err" : err ,
2023-03-02 21:25:51 +07:00
} ) . Errorln ( "[Reload] Failed to reload; try to roll back configuration" )
// Load last config back.
2023-03-24 23:57:04 +07:00
newC , err = newControlPlane ( log , obj , dnsCache , conf , externGeoDataDirs )
2023-03-02 21:25:51 +07:00
if err != nil {
sdnotify . Stopping ( )
obj . Close ( )
c . Close ( )
log . WithFields ( logrus . Fields {
"err" : err ,
} ) . Fatalln ( "[Reload] Failed to roll back configuration" )
}
2023-03-23 14:34:56 +07:00
newConf = conf
2023-03-30 22:28:45 +07:00
log . Errorln ( "[Reload] Last reload failed; rolled back configuration" )
2023-03-02 21:25:51 +07:00
} else {
log . Warnln ( "[Reload] Stopped old control plane" )
2023-02-27 12:29:42 +07:00
}
2023-03-07 05:38:06 +07:00
2023-03-02 21:25:51 +07:00
// Inject bpf objects into the new control plane life-cycle.
newC . InjectBpf ( obj )
2023-03-07 05:38:06 +07:00
// Prepare new context.
oldC := c
2023-02-27 12:29:42 +07:00
c = newC
2023-03-02 21:25:51 +07:00
conf = newConf
2023-02-27 12:29:42 +07:00
reloading = true
2023-03-07 05:38:06 +07:00
// Ready to close.
2023-11-14 15:26:33 +07:00
if abortConnections {
oldC . AbortConnections ( )
}
2023-03-07 05:38:06 +07:00
oldC . Close ( )
2023-03-25 14:51:20 +07:00
case syscall . SIGHUP :
// Ignore.
continue
2023-02-27 12:29:42 +07:00
default :
2023-03-13 13:25:14 +07:00
log . Infof ( "Received signal: %v" , sig . String ( ) )
2023-02-27 12:29:42 +07:00
break loop
}
}
2024-01-11 20:33:16 +07:00
defer os . Remove ( PidFilePath )
defer control . GetDaeNetns ( ) . Close ( )
2023-02-27 12:29:42 +07:00
if e := c . Close ( ) ; e != nil {
return fmt . Errorf ( "close control plane: %w" , e )
}
return nil
}
2023-03-24 23:57:04 +07:00
func newControlPlane ( log * logrus . Logger , bpf interface { } , dnsCache map [ string ] * control . DnsCache , conf * config . Config , externGeoDataDirs [ ] string ) ( c * control . ControlPlane , err error ) {
2023-03-02 21:25:51 +07:00
// Deep copy to prevent modification.
conf = deepcopy . Copy ( conf ) . ( * config . Config )
2023-02-10 10:04:16 +07:00
/// Get tag -> nodeList mapping.
tagToNodeList := map [ string ] [ ] string { }
2023-02-25 21:53:18 +07:00
if len ( conf . Node ) > 0 {
for _ , node := range conf . Node {
tagToNodeList [ "" ] = append ( tagToNodeList [ "" ] , string ( node ) )
}
2023-02-10 10:04:16 +07:00
}
2023-01-28 00:50:21 +07:00
// Resolve subscriptions to nodes.
2023-02-27 14:10:15 +07:00
resolvingfailed := false
2023-03-15 15:19:32 +07:00
if ! conf . Global . DisableWaitingNetwork && len ( conf . Subscription ) > 0 {
2023-04-02 16:43:50 +07:00
epo := 5 * time . Second
client := http . Client {
2023-06-04 10:38:05 +07:00
Transport : & http . Transport {
DialContext : func ( ctx context . Context , network , addr string ) ( c net . Conn , err error ) {
2023-08-09 22:00:38 +07:00
cd := netproxy . ContextDialerConverter { Dialer : direct . SymmetricDirect }
2023-06-04 10:38:05 +07:00
conn , err := cd . DialContext ( ctx , common . MagicNetwork ( "tcp" , conf . Global . SoMarkFromDae ) , addr )
if err != nil {
return nil , err
}
return & netproxy . FakeNetConn {
Conn : conn ,
LAddr : nil ,
RAddr : nil ,
} , nil
} ,
} ,
2023-04-02 16:43:50 +07:00
Timeout : epo ,
}
2023-03-15 15:19:32 +07:00
log . Infoln ( "Waiting for network..." )
2023-03-19 11:26:09 +07:00
for i := 0 ; ; i ++ {
2023-04-02 16:43:50 +07:00
resp , err := client . Get ( CheckNetworkLinks [ i % len ( CheckNetworkLinks ) ] )
2023-03-15 15:19:32 +07:00
if err != nil {
2023-04-02 16:43:50 +07:00
log . Debugln ( "CheckNetwork:" , err )
var neterr net . Error
if errors . As ( err , & neterr ) && neterr . Timeout ( ) {
// Do not sleep.
continue
}
time . Sleep ( epo )
2023-03-15 15:19:32 +07:00
continue
}
resp . Body . Close ( )
if resp . StatusCode >= 200 && resp . StatusCode < 500 {
break
}
log . Infof ( "Bad status: %v (%v)" , resp . Status , resp . StatusCode )
2023-04-02 16:43:50 +07:00
time . Sleep ( epo )
2023-03-15 15:19:32 +07:00
}
log . Infoln ( "Network online." )
}
2023-04-04 23:42:56 +07:00
if len ( conf . Subscription ) > 0 {
log . Infoln ( "Fetching subscriptions..." )
}
2023-06-04 10:38:05 +07:00
client := http . Client {
Transport : & http . Transport {
DialContext : func ( ctx context . Context , network , addr string ) ( c net . Conn , err error ) {
2023-08-09 22:00:38 +07:00
cd := netproxy . ContextDialerConverter { Dialer : direct . SymmetricDirect }
2023-06-04 10:38:05 +07:00
conn , err := cd . DialContext ( ctx , common . MagicNetwork ( "tcp" , conf . Global . SoMarkFromDae ) , addr )
if err != nil {
return nil , err
}
return & netproxy . FakeNetConn {
Conn : conn ,
LAddr : nil ,
RAddr : nil ,
} , nil
} ,
} ,
Timeout : 30 * time . Second ,
}
2023-02-25 21:53:18 +07:00
for _ , sub := range conf . Subscription {
2023-06-04 10:38:05 +07:00
tag , nodes , err := subscription . ResolveSubscription ( log , & client , filepath . Dir ( cfgFile ) , string ( sub ) )
2023-01-28 00:50:21 +07:00
if err != nil {
log . Warnf ( ` failed to resolve subscription "%v": %v ` , sub , err )
2023-02-27 14:10:15 +07:00
resolvingfailed = true
2023-01-28 00:50:21 +07:00
}
2023-02-10 10:04:16 +07:00
if len ( nodes ) > 0 {
tagToNodeList [ tag ] = append ( tagToNodeList [ tag ] , nodes ... )
}
2023-01-28 00:50:21 +07:00
}
2023-02-10 10:04:16 +07:00
if len ( tagToNodeList ) == 0 {
2023-02-27 14:10:15 +07:00
if resolvingfailed {
log . Warnln ( "No node found because all subscription resolving failed." )
} else {
log . Warnln ( "No node found." )
}
2023-02-04 21:02:37 +07:00
}
2023-01-28 00:50:21 +07:00
2023-02-25 21:53:18 +07:00
if len ( conf . Global . LanInterface ) == 0 && len ( conf . Global . WanInterface ) == 0 {
2023-03-04 12:16:26 +07:00
log . Warnln ( "No interface to bind." )
2023-02-01 09:59:57 +07:00
}
2023-03-27 11:33:07 +07:00
if err = preprocessWanInterfaceAuto ( conf ) ; err != nil {
return nil , err
}
2023-02-27 12:29:42 +07:00
c , err = control . NewControlPlane (
2023-01-28 00:50:21 +07:00
log ,
2023-02-27 12:29:42 +07:00
bpf ,
2023-03-17 12:13:42 +07:00
dnsCache ,
2023-02-10 10:04:16 +07:00
tagToNodeList ,
2023-02-25 21:53:18 +07:00
conf . Group ,
& conf . Routing ,
& conf . Global ,
& conf . Dns ,
2023-03-24 23:57:04 +07:00
externGeoDataDirs ,
2023-01-28 00:50:21 +07:00
)
if err != nil {
2023-02-27 12:29:42 +07:00
return nil , err
2023-01-28 00:50:21 +07:00
}
2023-02-25 01:38:21 +07:00
// Call GC to release memory.
runtime . GC ( )
2023-02-27 12:29:42 +07:00
return c , nil
2023-01-28 00:50:21 +07:00
}
2023-03-27 11:33:07 +07:00
func preprocessWanInterfaceAuto ( params * config . Config ) error {
// preprocess "auto".
ifs := make ( [ ] string , 0 , len ( params . Global . WanInterface ) + 2 )
for _ , ifname := range params . Global . WanInterface {
if ifname == "auto" {
defaultIfs , err := common . GetDefaultIfnames ( )
if err != nil {
return fmt . Errorf ( "failed to convert 'auto': %w" , err )
}
ifs = append ( ifs , defaultIfs ... )
} else {
ifs = append ( ifs , ifname )
}
}
params . Global . WanInterface = common . Deduplicate ( ifs )
return nil
}
2023-02-25 21:53:18 +07:00
func readConfig ( cfgFile string ) ( conf * config . Config , includes [ ] string , err error ) {
2023-02-09 22:17:49 +07:00
merger := config . NewMerger ( cfgFile )
2023-02-10 10:55:00 +07:00
sections , includes , err := merger . Merge ( )
2023-01-28 00:50:21 +07:00
if err != nil {
2023-02-09 22:17:49 +07:00
return nil , nil , err
2023-01-28 00:50:21 +07:00
}
2023-02-25 21:53:18 +07:00
if conf , err = config . New ( sections ) ; err != nil {
2023-02-09 22:17:49 +07:00
return nil , nil , err
2023-01-28 00:50:21 +07:00
}
2023-02-25 21:53:18 +07:00
return conf , includes , nil
2023-01-28 00:50:21 +07:00
}
2023-03-13 23:34:38 +07:00
func emptyConfig ( ) ( conf * config . Config , err error ) {
sections , err := config_parser . Parse ( ` global { } routing { } ` )
if err != nil {
return nil , err
}
if conf , err = config . New ( sections ) ; err != nil {
return nil , err
}
return conf , nil
}
func init ( ) {
rootCmd . AddCommand ( runCmd )
}