diff --git a/cmd/run.go b/cmd/run.go index 7e808c6..f7dde16 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -331,7 +331,7 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { cd := netproxy.ContextDialerConverter{Dialer: direct.SymmetricDirect} - conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", conf.Global.SoMarkFromDae), addr) + conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", conf.Global.SoMarkFromDae, conf.Global.Mptcp), addr) if err != nil { return nil, err } @@ -373,7 +373,7 @@ func newControlPlane(log *logrus.Logger, bpf interface{}, dnsCache map[string]*c Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { cd := netproxy.ContextDialerConverter{Dialer: direct.SymmetricDirect} - conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", conf.Global.SoMarkFromDae), addr) + conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", conf.Global.SoMarkFromDae, conf.Global.Mptcp), addr) if err != nil { return nil, err } diff --git a/common/utils.go b/common/utils.go index 350195b..0d9ce79 100644 --- a/common/utils.go +++ b/common/utils.go @@ -469,13 +469,14 @@ nextLink: return Deduplicate(defaultIfs), nil } -func MagicNetwork(network string, mark uint32) string { - if mark == 0 { +func MagicNetwork(network string, mark uint32, mptcp bool) string { + if mark == 0 && !mptcp { return network } else { return netproxy.MagicNetwork{ Network: network, Mark: mark, + Mptcp: mptcp, }.Encode() } } diff --git a/component/outbound/dialer/connectivity_check.go b/component/outbound/dialer/connectivity_check.go index 3cb8f1e..9e478a6 100644 --- a/component/outbound/dialer/connectivity_check.go +++ b/component/outbound/dialer/connectivity_check.go @@ -282,8 +282,10 @@ func (d *Dialer) ActivateCheck() { func (d *Dialer) aliveBackground() { cycle := d.CheckInterval var tcpSomark uint32 + var mptcp bool if network, err := netproxy.ParseMagicNetwork(d.TcpCheckOptionRaw.ResolverNetwork); err == nil { tcpSomark = network.Mark + mptcp = network.Mptcp } tcp4CheckOpt := &CheckOption{ networkType: &NetworkType{ @@ -304,7 +306,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.HttpCheck(ctx, opt.Url, opt.Ip4, opt.Method, tcpSomark) + return d.HttpCheck(ctx, opt.Url, opt.Ip4, opt.Method, tcpSomark, mptcp) }, } tcp6CheckOpt := &CheckOption{ @@ -326,7 +328,7 @@ func (d *Dialer) aliveBackground() { }).Debugln("Skip check due to no DNS record.") return false, nil } - return d.HttpCheck(ctx, opt.Url, opt.Ip6, opt.Method, tcpSomark) + return d.HttpCheck(ctx, opt.Url, opt.Ip6, opt.Method, tcpSomark, mptcp) }, } tcpNetwork := netproxy.MagicNetwork{ @@ -580,7 +582,7 @@ func (d *Dialer) Check(opts *CheckOption) (ok bool, err error) { return ok, err } -func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, method string, soMark uint32) (ok bool, err error) { +func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, method string, soMark uint32, mptcp bool) (ok bool, err error) { // HTTP(S) check. if method == "" { method = http.MethodGet @@ -590,7 +592,7 @@ func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr, Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (c net.Conn, err error) { // Force to dial "ip". - conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", soMark), net.JoinHostPort(ip.String(), u.Port())) + conn, err := cd.DialContext(ctx, common.MagicNetwork("tcp", soMark, mptcp), net.JoinHostPort(ip.String(), u.Port())) if err != nil { return nil, err } diff --git a/config/config.go b/config/config.go index 0c21515..873278d 100644 --- a/config/config.go +++ b/config/config.go @@ -42,6 +42,7 @@ type Global struct { TlsImplementation string `mapstructure:"tls_implementation" default:"tls"` UtlsImitate string `mapstructure:"utls_imitate" default:"chrome_auto"` PprofPort uint16 `mapstructure:"pprof_port" default:"0"` + Mptcp bool `mapstructure:"mptcp" default:"false"` } type Utls struct { diff --git a/config/desc.go b/config/desc.go index fad148a..2440527 100644 --- a/config/desc.go +++ b/config/desc.go @@ -57,6 +57,7 @@ var GlobalDesc = Desc{ "sniffing_timeout": "Timeout to waiting for first data sending for sniffing. It is always 0 if dial_mode is ip. Set it higher is useful in high latency LAN network.", "tls_implementation": "TLS implementation. \"tls\" is to use Go's crypto/tls. \"utls\" is to use uTLS, which can imitate browser's Client Hello.", "utls_imitate": "The Client Hello ID for uTLS to imitate. This takes effect only if tls_implementation is utls. See more: https://github.com/daeuniverse/dae/blob/331fa23c16/component/outbound/transport/tls/utls.go#L17", + "mptcp": "Enable Multipath TCP. If is true, dae will try to use MPTCP to connect all nodes, but it will only take effects when the node supports MPTCP. It can use for load balance and failover to multiple interfaces and IPs.", } var DnsDesc = Desc{ diff --git a/control/control_plane.go b/control/control_plane.go index 0e640f0..5cc1908 100644 --- a/control/control_plane.go +++ b/control/control_plane.go @@ -75,6 +75,7 @@ type ControlPlane struct { sniffingTimeout time.Duration tproxyPortProtect bool soMarkFromDae uint32 + mptcp bool } func NewControlPlane( @@ -261,8 +262,8 @@ func NewControlPlane( TlsImplementation: global.TlsImplementation, UtlsImitate: global.UtlsImitate}, Log: log, - TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), Method: global.TcpCheckHttpMethod}, - CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{Raw: global.UdpCheckDns, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), Somark: global.SoMarkFromDae}, + TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae, global.Mptcp), Method: global.TcpCheckHttpMethod}, + CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{Raw: global.UdpCheckDns, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae, global.Mptcp), Somark: global.SoMarkFromDae}, CheckInterval: global.CheckInterval, CheckTolerance: global.CheckTolerance, CheckDnsTcp: true, @@ -395,6 +396,7 @@ func NewControlPlane( sniffingTimeout: sniffingTimeout, tproxyPortProtect: global.TproxyPortProtect, soMarkFromDae: global.SoMarkFromDae, + mptcp: global.Mptcp, } defer func() { if err != nil { @@ -407,7 +409,7 @@ func NewControlPlane( Logger: log, LocationFinder: locationFinder, UpstreamReadyCallback: plane.dnsUpstreamReadyCallback, - UpstreamResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), + UpstreamResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae, global.Mptcp), }) if err != nil { return nil, err @@ -620,7 +622,7 @@ func (c *ControlPlane) ChooseDialTarget(outbound consts.OutboundIndex, dst netip // TODO: use DNS controller and re-route by control plane. systemDns, err := netutils.SystemDns() if err == nil { - if ip46, err := netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, domain, common.MagicNetwork("udp", c.soMarkFromDae), true); err == nil && (ip46.Ip4.IsValid() || ip46.Ip6.IsValid()) { + if ip46, err := netutils.ResolveIp46(ctx, direct.SymmetricDirect, systemDns, domain, common.MagicNetwork("udp", c.soMarkFromDae, c.mptcp), true); err == nil && (ip46.Ip4.IsValid() || ip46.Ip6.IsValid()) { // Has A/AAAA records. It is a real domain. dialMode = consts.DialMode_Domain // Add it to real-domain set. @@ -938,6 +940,7 @@ func (c *ControlPlane) chooseBestDnsDialer( bestOutbound: bestOutbound, bestTarget: bestTarget, mark: dialMark, + mptcp: c.mptcp, }, nil } diff --git a/control/dns_control.go b/control/dns_control.go index 6ff7054..8b41c30 100644 --- a/control/dns_control.go +++ b/control/dns_control.go @@ -344,6 +344,7 @@ type dialArgument struct { bestOutbound *outbound.DialerGroup bestTarget netip.AddrPort mark uint32 + mptcp bool } func (c *DnsController) Handle_(dnsMessage *dnsmessage.Msg, req *udpRequest) (err error) { @@ -570,7 +571,7 @@ func (c *DnsController) dialSend(invokingDepth int, req *udpRequest, data []byte // TODO: connection pool. conn, err = bestContextDialer.DialContext( ctxDial, - common.MagicNetwork("udp", dialArgument.mark), + common.MagicNetwork("udp", dialArgument.mark, dialArgument.mptcp), dialArgument.bestTarget.String(), ) if err != nil { @@ -633,7 +634,7 @@ func (c *DnsController) dialSend(invokingDepth int, req *udpRequest, data []byte case consts.L4ProtoStr_TCP: // We can block here because we are in a coroutine. - conn, err = bestContextDialer.DialContext(ctxDial, common.MagicNetwork("tcp", dialArgument.mark), dialArgument.bestTarget.String()) + conn, err = bestContextDialer.DialContext(ctxDial, common.MagicNetwork("tcp", dialArgument.mark, dialArgument.mptcp), dialArgument.bestTarget.String()) if err != nil { return fmt.Errorf("failed to dial proxy to tcp: %w", err) } diff --git a/control/tcp.go b/control/tcp.go index dd99cd0..9796a8e 100644 --- a/control/tcp.go +++ b/control/tcp.go @@ -165,7 +165,7 @@ func (c *ControlPlane) RouteDialTcp(p *RouteDialParam) (conn netproxy.Conn, err cd := netproxy.ContextDialerConverter{ Dialer: d, } - return cd.DialContext(ctx, common.MagicNetwork("tcp", routingResult.Mark), dialTarget) + return cd.DialContext(ctx, common.MagicNetwork("tcp", routingResult.Mark, c.mptcp), dialTarget) } type WriteCloser interface { diff --git a/control/udp.go b/control/udp.go index 638ce93..5f3b459 100644 --- a/control/udp.go +++ b/control/udp.go @@ -250,7 +250,7 @@ getNew: Target: dialTarget, Dialer: dialerForNew, Outbound: outbound, - Network: common.MagicNetwork("udp", routingResult.Mark), + Network: common.MagicNetwork("udp", routingResult.Mark, c.mptcp), SniffedDomain: domain, }, nil }, diff --git a/example.dae b/example.dae index e2ceeb1..e1814e2 100644 --- a/example.dae +++ b/example.dae @@ -96,6 +96,10 @@ global { # The Client Hello ID for uTLS to imitate. This takes effect only if tls_implementation is utls. # See more: https://github.com/daeuniverse/dae/blob/331fa23c16/component/outbound/transport/tls/utls.go#L17 utls_imitate: chrome_auto + + # Multipath TCP (MPTCP) support. If is true, dae will try to use MPTCP to connect all nodes, but it will only take + # effects when the node supports MPTCP. It can use for load balance and failover to multiple interfaces and IPs. + mptcp: false } # Subscriptions defined here will be resolved as nodes and merged as a part of the global node pool. diff --git a/go.mod b/go.mod index 7e39317..405e731 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,15 @@ require ( github.com/bits-and-blooms/bloom/v3 v3.5.0 github.com/cilium/ebpf v0.12.3 github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d - github.com/daeuniverse/outbound v0.0.0-20240628165628-7c0c217530ea + github.com/daeuniverse/outbound v0.0.0-20240807173909-1bac5b52e542 github.com/fsnotify/fsnotify v1.7.0 github.com/json-iterator/go v1.1.12 + github.com/mholt/archiver/v3 v3.5.1 github.com/miekg/dns v1.1.55 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd github.com/safchain/ethtool v0.3.0 + github.com/shirou/gopsutil/v4 v4.24.5 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/v2rayA/ahocorasick-domain v0.0.0-20231231085011-99ceb8ef3208 @@ -43,14 +45,11 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/pgzip v1.2.5 // indirect - github.com/mholt/archiver/v3 v3.5.1 // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/pierrec/lz4/v4 v4.1.2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/shirou/gopsutil/v4 v4.24.5 // indirect - github.com/stretchr/testify v1.9.0 // indirect github.com/ulikunitz/xz v0.5.9 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/go.sum b/go.sum index d35af87..db7bec1 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d h1:hnC39MjR7xt5kZjrKlef7DXKFDkiX8MIcDXYC/6Jf9Q= github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d/go.mod h1:VGWGgv7pCP5WGyHGUyb9+nq/gW0yBm+i/GfCNATOJ1M= -github.com/daeuniverse/outbound v0.0.0-20240628165628-7c0c217530ea h1:mQwAcoKHR/AVsajoEpP/NSYL8nBTuP+kw7l2+xWM4xE= -github.com/daeuniverse/outbound v0.0.0-20240628165628-7c0c217530ea/go.mod h1:z0vJ5ZlLErX8WTruVeOuGr+1KOhSFcaPzEhZMAYfPdA= +github.com/daeuniverse/outbound v0.0.0-20240807173909-1bac5b52e542 h1:hL3E0XKvBvVmjNJrRDsI7SnmZmiery12f6/7b+kBILw= +github.com/daeuniverse/outbound v0.0.0-20240807173909-1bac5b52e542/go.mod h1:z0vJ5ZlLErX8WTruVeOuGr+1KOhSFcaPzEhZMAYfPdA= github.com/daeuniverse/quic-go v0.0.0-20240413031024-943f218e0810 h1:YtEYouFaNrg9sV9vf3UabvKShKn6sD0QaCdOxCwaF3g= github.com/daeuniverse/quic-go v0.0.0-20240413031024-943f218e0810/go.mod h1:61o2uZUGLrlv1i+oO2rx9sVX0vbf8cHzdSHt7h6lMnM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -77,9 +77,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA= github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= @@ -166,17 +165,11 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= @@ -244,8 +237,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=