mirror of
https://github.com/daeuniverse/dae.git
synced 2024-12-22 20:04:40 +07:00
feat: add MPTCP support (#601)
This commit is contained in:
parent
18b657083f
commit
c5b596c293
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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.
|
||||
|
7
go.mod
7
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
|
||||
|
15
go.sum
15
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=
|
||||
|
Loading…
Reference in New Issue
Block a user