Chore: merge branch 'with-tun' into plus-pro
This commit is contained in:
commit
ea8a5409ad
1
.github/workflows/build-windows-amd.yml
vendored
1
.github/workflows/build-windows-amd.yml
vendored
@ -59,6 +59,7 @@ jobs:
|
|||||||
Compress-Archive -Path clash-windows-amd64-v3.exe -DestinationPath clash-plus-windows-amd64-v3-$(Get-Date -Format 'yyyy.MM.dd').zip
|
Compress-Archive -Path clash-windows-amd64-v3.exe -DestinationPath clash-plus-windows-amd64-v3-$(Get-Date -Format 'yyyy.MM.dd').zip
|
||||||
Remove-Item -Force clash-windows-amd64.exe
|
Remove-Item -Force clash-windows-amd64.exe
|
||||||
Remove-Item -Force clash-windows-amd64-v3.exe
|
Remove-Item -Force clash-windows-amd64-v3.exe
|
||||||
|
"$(Get-Date -Format 'yyyy.MM.dd')" | Out-File version.txt -NoNewLine
|
||||||
|
|
||||||
- name: Upload files to Artifacts
|
- name: Upload files to Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
@ -173,7 +173,6 @@ Then manually create the default route and DNS server. If your device already ha
|
|||||||
Enjoy! :)
|
Enjoy! :)
|
||||||
|
|
||||||
#### For Windows:
|
#### For Windows:
|
||||||
go to [https://www.wintun.net](https://www.wintun.net) and download the latest release, copy the right `wintun.dll` into the system32 directory.
|
|
||||||
```yaml
|
```yaml
|
||||||
tun:
|
tun:
|
||||||
enable: true
|
enable: true
|
||||||
|
@ -37,12 +37,59 @@ type Socks5Option struct {
|
|||||||
|
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
c, _, err = ss.streamConn(c, metadata)
|
||||||
|
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Socks5) StreamSocks5PacketConn(c net.Conn, pc net.PacketConn, metadata *C.Metadata) (net.PacketConn, error) {
|
||||||
|
if c == nil {
|
||||||
|
return pc, fmt.Errorf("%s connect error: parameter net.Conn is nil", ss.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc == nil {
|
||||||
|
return pc, fmt.Errorf("%s connect error: parameter net.PacketConn is nil", ss.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, bindAddr, err := ss.streamConn(c, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return pc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c = cc
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, _ = io.Copy(io.Discard, c)
|
||||||
|
_ = c.Close()
|
||||||
|
// A UDP association terminates when the TCP connection that the UDP
|
||||||
|
// ASSOCIATE request arrived on terminates. RFC1928
|
||||||
|
_ = pc.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Support unspecified UDP bind address.
|
||||||
|
bindUDPAddr := bindAddr.UDPAddr()
|
||||||
|
if bindUDPAddr == nil {
|
||||||
|
return pc, errors.New("invalid UDP bind address")
|
||||||
|
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||||
|
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return pc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUDPAddr.IP = serverAddr.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
return &socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *Socks5) streamConn(c net.Conn, metadata *C.Metadata) (_ net.Conn, bindAddr socks5.Addr, err error) {
|
||||||
if ss.tls {
|
if ss.tls {
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
cc := tls.Client(c, ss.tlsConfig)
|
||||||
err := cc.Handshake()
|
err := cc.Handshake()
|
||||||
c = cc
|
c = cc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return c, nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,10 +100,14 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
Password: ss.pass,
|
Password: ss.pass,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
|
||||||
return nil, err
|
if metadata.NetWork == C.UDP {
|
||||||
|
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
|
||||||
|
} else {
|
||||||
|
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user)
|
||||||
}
|
}
|
||||||
return c, nil
|
|
||||||
|
return c, bindAddr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -81,61 +132,24 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
|||||||
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ss.tls {
|
|
||||||
cc := tls.Client(c, ss.tlsConfig)
|
|
||||||
err = cc.Handshake()
|
|
||||||
c = cc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer safeConnClose(c, err)
|
defer safeConnClose(c, err)
|
||||||
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
var user *socks5.User
|
|
||||||
if ss.user != "" {
|
|
||||||
user = &socks5.User{
|
|
||||||
Username: ss.user,
|
|
||||||
Password: ss.pass,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("client hanshake error: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
tcpKeepAlive(c)
|
||||||
_, _ = io.Copy(io.Discard, c)
|
|
||||||
_ = c.Close()
|
|
||||||
// A UDP association terminates when the TCP connection that the UDP
|
|
||||||
// ASSOCIATE request arrived on terminates. RFC1928
|
|
||||||
_ = pc.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Support unspecified UDP bind address.
|
pc, err = ss.StreamSocks5PacketConn(c, pc, metadata)
|
||||||
bindUDPAddr := bindAddr.UDPAddr()
|
if err != nil {
|
||||||
if bindUDPAddr == nil {
|
|
||||||
err = errors.New("invalid UDP bind address")
|
|
||||||
return
|
return
|
||||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
|
||||||
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bindUDPAddr.IP = serverAddr.IP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
return NewPacketConn(pc, ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(option Socks5Option) *Socks5 {
|
func NewSocks5(option Socks5Option) *Socks5 {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
@ -37,30 +38,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||||||
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
first := proxies[0]
|
c, err := r.streamContext(ctx, proxies, r.Base.DialOptions(opts...)...)
|
||||||
last := proxies[len(proxies)-1]
|
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
return nil, err
|
||||||
}
|
|
||||||
tcpKeepAlive(c)
|
|
||||||
|
|
||||||
var currentMeta *C.Metadata
|
|
||||||
for _, proxy := range proxies[1:] {
|
|
||||||
currentMeta, err = addrToMetadata(proxy.Addr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err = first.StreamConn(c, currentMeta)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
first = proxy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last := proxies[len(proxies)-1]
|
||||||
c, err = last.StreamConn(c, metadata)
|
c, err = last.StreamConn(c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||||
@ -78,7 +61,9 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(proxies) {
|
length := len(proxies)
|
||||||
|
|
||||||
|
switch length {
|
||||||
case 0:
|
case 0:
|
||||||
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
|
||||||
case 1:
|
case 1:
|
||||||
@ -90,12 +75,16 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
first = proxies[0]
|
firstIndex = 0
|
||||||
last = proxies[len(proxies)-1]
|
nextIndex = 1
|
||||||
rawUDPRelay bool
|
lastUDPOverTCPIndex = -1
|
||||||
udpOverTCPEndIndex = -1
|
rawUDPRelay = false
|
||||||
|
|
||||||
|
first = proxies[firstIndex]
|
||||||
|
last = proxies[length-1]
|
||||||
|
|
||||||
c net.Conn
|
c net.Conn
|
||||||
|
cc net.Conn
|
||||||
err error
|
err error
|
||||||
currentMeta *C.Metadata
|
currentMeta *C.Metadata
|
||||||
)
|
)
|
||||||
@ -104,38 +93,47 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name())
|
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
rawUDPRelay, udpOverTCPEndIndex = isRawUDPRelay(proxies)
|
rawUDPRelay, lastUDPOverTCPIndex = isRawUDPRelay(proxies)
|
||||||
|
|
||||||
if rawUDPRelay {
|
if first.Type() == C.Socks5 {
|
||||||
|
cc1, err1 := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
||||||
|
if err1 != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
cc = cc1
|
||||||
|
tcpKeepAlive(cc)
|
||||||
|
|
||||||
|
var pc net.PacketConn
|
||||||
|
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
|
||||||
|
c = outbound.WrapConn(pc)
|
||||||
|
} else if rawUDPRelay {
|
||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
|
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
|
||||||
c = outbound.WrapConn(pc)
|
c = outbound.WrapConn(pc)
|
||||||
} else {
|
} else {
|
||||||
c, err = dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
|
firstIndex = lastUDPOverTCPIndex
|
||||||
|
nextIndex = firstIndex + 1
|
||||||
|
first = proxies[firstIndex]
|
||||||
|
c, err = r.streamContext(ctx, proxies[:nextIndex], r.Base.DialOptions(opts...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
if nextIndex < length {
|
||||||
if err != nil && c != nil {
|
for i, proxy := range proxies[nextIndex:] { // raw udp in loop
|
||||||
_ = c.Close()
|
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||||
}
|
if err != nil {
|
||||||
}()
|
return nil, err
|
||||||
|
}
|
||||||
|
currentMeta.NetWork = C.UDP
|
||||||
|
|
||||||
for i, proxy := range proxies[1:] {
|
|
||||||
currentMeta, err = addrToMetadata(proxy.Addr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if outbound.IsPacketConn(c) || udpOverTCPEndIndex == i {
|
|
||||||
if !isRawUDP(first) && !first.SupportUDP() {
|
if !isRawUDP(first) && !first.SupportUDP() {
|
||||||
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name())
|
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !currentMeta.Resolved() && needResolveIP(first) {
|
if needResolveIP(first, currentMeta) {
|
||||||
var ip netip.Addr
|
var ip netip.Addr
|
||||||
ip, err = resolver.ResolveProxyServerHost(currentMeta.Host)
|
ip, err = resolver.ResolveProxyServerHost(currentMeta.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,28 +142,57 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
currentMeta.DstIP = ip
|
currentMeta.DstIP = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMeta.NetWork = C.UDP
|
if cc != nil { // socks5
|
||||||
c, err = first.StreamPacketConn(c, currentMeta)
|
c, err = streamSocks5PacketConn(first, cc, c, currentMeta)
|
||||||
} else {
|
cc = nil
|
||||||
c, err = first.StreamConn(c, currentMeta)
|
} else {
|
||||||
}
|
c, err = first.StreamPacketConn(c, currentMeta)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
first = proxy
|
if proxy.Type() == C.Socks5 {
|
||||||
|
endIndex := nextIndex + i + 1
|
||||||
|
cc, err = r.streamContext(ctx, proxies[:endIndex], r.Base.DialOptions(opts...)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first = proxy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = last.StreamPacketConn(c, metadata)
|
if cc != nil {
|
||||||
|
c, err = streamSocks5PacketConn(last, cc, c, metadata)
|
||||||
|
} else {
|
||||||
|
c, err = last.StreamPacketConn(c, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return outbound.NewPacketConn(c.(net.PacketConn), r), nil
|
return outbound.NewPacketConn(c.(net.PacketConn), r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SupportUDP implements C.ProxyAdapter
|
||||||
|
func (r *Relay) SupportUDP() bool {
|
||||||
|
proxies := r.rawProxies(true)
|
||||||
|
|
||||||
|
l := len(proxies)
|
||||||
|
|
||||||
|
if l == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
last := proxies[l-1]
|
||||||
|
|
||||||
|
return isRawUDP(last) || last.SupportUDP()
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||||
var all []string
|
var all []string
|
||||||
@ -200,12 +227,47 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
|||||||
return proxies
|
return proxies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Relay) streamContext(ctx context.Context, proxies []C.Proxy, opts ...dialer.Option) (net.Conn, error) {
|
||||||
|
first := proxies[0]
|
||||||
|
|
||||||
|
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
tcpKeepAlive(c)
|
||||||
|
|
||||||
|
if len(proxies) > 1 {
|
||||||
|
var currentMeta *C.Metadata
|
||||||
|
for _, proxy := range proxies[1:] {
|
||||||
|
currentMeta, err = addrToMetadata(proxy.Addr())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = first.StreamConn(c, currentMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
first = proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamSocks5PacketConn(proxy C.Proxy, cc, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
|
pc, err := proxy.(*adapter.Proxy).ProxyAdapter.(*outbound.Socks5).StreamSocks5PacketConn(cc, c.(net.PacketConn), metadata)
|
||||||
|
return outbound.WrapConn(pc), err
|
||||||
|
}
|
||||||
|
|
||||||
func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
|
func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
|
||||||
var (
|
var (
|
||||||
lastIndex = len(proxies) - 1
|
lastIndex = len(proxies) - 1
|
||||||
isLastRawUDP = isRawUDP(proxies[lastIndex])
|
last = proxies[lastIndex]
|
||||||
isUDPOverTCP = false
|
isLastRawUDP = isRawUDP(last)
|
||||||
udpOverTCPEndIndex = -1
|
isUDPOverTCP = false
|
||||||
|
lastUDPOverTCPIndex = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := lastIndex; i >= 0; i-- {
|
for i := lastIndex; i >= 0; i-- {
|
||||||
@ -213,26 +275,33 @@ func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
|
|||||||
|
|
||||||
isUDPOverTCP = isUDPOverTCP || !isRawUDP(p)
|
isUDPOverTCP = isUDPOverTCP || !isRawUDP(p)
|
||||||
|
|
||||||
if isLastRawUDP && isUDPOverTCP && udpOverTCPEndIndex == -1 {
|
if isLastRawUDP && isUDPOverTCP && lastUDPOverTCPIndex == -1 {
|
||||||
udpOverTCPEndIndex = i
|
lastUDPOverTCPIndex = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !isUDPOverTCP, udpOverTCPEndIndex
|
if !isLastRawUDP {
|
||||||
|
lastUDPOverTCPIndex = lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isUDPOverTCP, lastUDPOverTCPIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func isRawUDP(proxy C.ProxyAdapter) bool {
|
func isRawUDP(proxy C.ProxyAdapter) bool {
|
||||||
if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR {
|
if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR || proxy.Type() == C.Socks5 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func needResolveIP(proxy C.ProxyAdapter) bool {
|
func needResolveIP(proxy C.ProxyAdapter, metadata *C.Metadata) bool {
|
||||||
if proxy.Type() == C.Vmess || proxy.Type() == C.Vless {
|
if metadata.Resolved() {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
return false
|
if proxy.Type() != C.Vmess && proxy.Type() != C.Vless {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||||
@ -240,7 +309,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
|
|||||||
Base: outbound.NewBase(outbound.BaseOption{
|
Base: outbound.NewBase(outbound.BaseOption{
|
||||||
Name: option.Name,
|
Name: option.Name,
|
||||||
Type: C.Relay,
|
Type: C.Relay,
|
||||||
UDP: true,
|
|
||||||
Interface: option.Interface,
|
Interface: option.Interface,
|
||||||
RoutingMark: option.RoutingMark,
|
RoutingMark: option.RoutingMark,
|
||||||
}),
|
}),
|
||||||
|
@ -24,8 +24,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
|
|||||||
DstIP: netip.Addr{},
|
DstIP: netip.Addr{},
|
||||||
DstPort: port,
|
DstPort: port,
|
||||||
}
|
}
|
||||||
err = nil
|
return addr, nil
|
||||||
return
|
|
||||||
} else if ip.Is4() {
|
} else if ip.Is4() {
|
||||||
addr = &C.Metadata{
|
addr = &C.Metadata{
|
||||||
AddrType: C.AtypIPv4,
|
AddrType: C.AtypIPv4,
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
types "github.com/Dreamacro/clash/constant/provider"
|
types "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
2
common/cache/cache.go
vendored
2
common/cache/cache.go
vendored
@ -61,7 +61,7 @@ func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
|
|||||||
|
|
||||||
func (c *cache[K, V]) cleanup() {
|
func (c *cache[K, V]) cleanup() {
|
||||||
c.mapping.Range(func(k, v any) bool {
|
c.mapping.Range(func(k, v any) bool {
|
||||||
key := k.(string)
|
key := k
|
||||||
elm := v.(*element[V])
|
elm := v.(*element[V])
|
||||||
if time.Since(elm.Expired) > 0 {
|
if time.Since(elm.Expired) > 0 {
|
||||||
c.mapping.Delete(key)
|
c.mapping.Delete(key)
|
||||||
|
@ -52,8 +52,8 @@ func (alloc *Allocator) Put(buf []byte) error {
|
|||||||
return errors.New("allocator Put() incorrect buffer size")
|
return errors.New("allocator Put() incorrect buffer size")
|
||||||
}
|
}
|
||||||
|
|
||||||
//lint:ignore SA6002 ignore temporarily
|
|
||||||
//nolint
|
//nolint
|
||||||
|
//lint:ignore SA6002 ignore temporarily
|
||||||
alloc.buffers[bits].Put(buf)
|
alloc.buffers[bits].Put(buf)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ type Result[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do single.Do likes sync.singleFlight
|
// Do single.Do likes sync.singleFlight
|
||||||
//lint:ignore ST1008 it likes sync.singleFlight
|
|
||||||
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -31,7 +31,7 @@ import (
|
|||||||
R "github.com/Dreamacro/clash/rule"
|
R "github.com/Dreamacro/clash/rule"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// General config
|
// General config
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func downloadMMDB(path string) (err error) {
|
func downloadMMDB(path string) (err error) {
|
||||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb")
|
resp, err := http.Get("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -32,18 +32,18 @@ func initMMDB() error {
|
|||||||
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find MMDB, start download")
|
log.Infoln("Can't find MMDB, start download")
|
||||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||||
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
return fmt.Errorf("can't download MMDB: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mmdb.Verify() {
|
if !mmdb.Verify() {
|
||||||
log.Warnln("MMDB invalid, remove and download")
|
log.Warnln("MMDB invalid, remove and download")
|
||||||
if err := os.Remove(C.Path.MMDB()); err != nil {
|
if err := os.Remove(C.Path.MMDB()); err != nil {
|
||||||
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
|
return fmt.Errorf("can't remove invalid MMDB: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
if err := downloadMMDB(C.Path.MMDB()); err != nil {
|
||||||
return fmt.Errorf("can't download MMDB: %s", err.Error())
|
return fmt.Errorf("can't download MMDB: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ func initMMDB() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadGeoSite(path string) (err error) {
|
func downloadGeoSite(path string) (err error) {
|
||||||
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
|
resp, err := http.Get("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ func initGeoSite() error {
|
|||||||
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find GeoSite.dat, start download")
|
log.Infoln("Can't find GeoSite.dat, start download")
|
||||||
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
|
||||||
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
|
return fmt.Errorf("can't download GeoSite.dat: %w", err)
|
||||||
}
|
}
|
||||||
log.Infoln("Download GeoSite.dat finish")
|
log.Infoln("Download GeoSite.dat finish")
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ func Init(dir string) error {
|
|||||||
// initial homedir
|
// initial homedir
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(dir, 0o777); err != nil {
|
if err := os.MkdirAll(dir, 0o777); err != nil {
|
||||||
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
|
return fmt.Errorf("can't create config directory %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,10 +93,10 @@ func Init(dir string) error {
|
|||||||
log.Infoln("Can't find config, create a initial config file")
|
log.Infoln("Can't find config, create a initial config file")
|
||||||
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644)
|
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
|
return fmt.Errorf("can't create file %s: %w", C.Path.Config(), err)
|
||||||
}
|
}
|
||||||
f.Write([]byte(`mixed-port: 7890`))
|
_, _ = f.Write([]byte(`mixed-port: 7890`))
|
||||||
f.Close()
|
_ = f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial mmdb
|
// initial mmdb
|
||||||
|
@ -18,10 +18,21 @@ var StackTypeMapping = map[string]TUNStack{
|
|||||||
const (
|
const (
|
||||||
TunGvisor TUNStack = iota
|
TunGvisor TUNStack = iota
|
||||||
TunSystem
|
TunSystem
|
||||||
|
|
||||||
|
TunDisabled TUNState = iota
|
||||||
|
TunEnabled
|
||||||
|
TunPaused
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUNStack int
|
type TUNStack int
|
||||||
|
|
||||||
|
type TUNState int
|
||||||
|
|
||||||
|
type TUNChangeCallback interface {
|
||||||
|
Pause()
|
||||||
|
Resume()
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalYAML unserialize TUNStack with yaml
|
// UnmarshalYAML unserialize TUNStack with yaml
|
||||||
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
var tp string
|
var tp string
|
||||||
|
9
go.mod
9
go.mod
@ -18,9 +18,9 @@ require (
|
|||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
go.uber.org/automaxprocs v1.5.1
|
go.uber.org/automaxprocs v1.5.1
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||||
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
|
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
|
||||||
@ -28,8 +28,8 @@ require (
|
|||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -44,5 +44,4 @@ require (
|
|||||||
golang.org/x/tools v0.1.10 // indirect
|
golang.org/x/tools v0.1.10 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
|
||||||
)
|
)
|
||||||
|
18
go.sum
18
go.sum
@ -80,8 +80,8 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@ -99,8 +99,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||||
@ -157,10 +157,8 @@ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscL
|
|||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e h1:NfZ2QezHUJrGICokWPLyOtn5ZOyTAmIB89zvyZb5ibU=
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
|
||||||
gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||||
|
@ -385,7 +385,13 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn)
|
callback := &tunChangeCallback{
|
||||||
|
tunConf: *tunConf,
|
||||||
|
tcpIn: tcpIn,
|
||||||
|
udpIn: udpIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn, callback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -563,6 +569,24 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tunChangeCallback struct {
|
||||||
|
tunConf config.Tun
|
||||||
|
tcpIn chan<- C.ConnContext
|
||||||
|
udpIn chan<- *inbound.PacketAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunChangeCallback) Pause() {
|
||||||
|
conf := t.tunConf
|
||||||
|
conf.Enable = false
|
||||||
|
ReCreateTun(&conf, t.tcpIn, t.udpIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunChangeCallback) Resume() {
|
||||||
|
conf := t.tunConf
|
||||||
|
conf.Enable = true
|
||||||
|
ReCreateTun(&conf, t.tcpIn, t.udpIn)
|
||||||
|
}
|
||||||
|
|
||||||
func initCert() error {
|
func initCert() error {
|
||||||
if _, err := os.Stat(C.Path.RootCA()); os.IsNotExist(err) {
|
if _, err := os.Stat(C.Path.RootCA()); os.IsNotExist(err) {
|
||||||
log.Infoln("Can't find mitm_ca.crt, start generate")
|
log.Infoln("Can't find mitm_ca.crt, start generate")
|
||||||
|
BIN
listener/tun/device/tun/driver/amd64/wintun.dll
Executable file
BIN
listener/tun/device/tun/driver/amd64/wintun.dll
Executable file
Binary file not shown.
BIN
listener/tun/device/tun/driver/arm/wintun.dll
Executable file
BIN
listener/tun/device/tun/driver/arm/wintun.dll
Executable file
Binary file not shown.
BIN
listener/tun/device/tun/driver/arm64/wintun.dll
Executable file
BIN
listener/tun/device/tun/driver/arm64/wintun.dll
Executable file
Binary file not shown.
233
listener/tun/device/tun/driver/dll_windows.go
Normal file
233
listener/tun/device/tun/driver/dll_windows.go
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.zx2c4.com/wireguard/windows/driver/memmod"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname modwintun golang.zx2c4.com/wintun.modwintun
|
||||||
|
|
||||||
|
//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter
|
||||||
|
|
||||||
|
//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter
|
||||||
|
|
||||||
|
//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter
|
||||||
|
|
||||||
|
//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver
|
||||||
|
|
||||||
|
//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID
|
||||||
|
|
||||||
|
//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion
|
||||||
|
|
||||||
|
//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket
|
||||||
|
|
||||||
|
//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession
|
||||||
|
|
||||||
|
//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent
|
||||||
|
|
||||||
|
//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket
|
||||||
|
|
||||||
|
//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket
|
||||||
|
|
||||||
|
//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket
|
||||||
|
|
||||||
|
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
|
||||||
|
|
||||||
|
var (
|
||||||
|
modwintun *lazyDLL
|
||||||
|
procWintunCreateAdapter *lazyProc
|
||||||
|
procWintunOpenAdapter *lazyProc
|
||||||
|
procWintunCloseAdapter *lazyProc
|
||||||
|
procWintunDeleteDriver *lazyProc
|
||||||
|
procWintunGetAdapterLUID *lazyProc
|
||||||
|
procWintunGetRunningDriverVersion *lazyProc
|
||||||
|
procWintunAllocateSendPacket *lazyProc
|
||||||
|
procWintunEndSession *lazyProc
|
||||||
|
procWintunGetReadWaitEvent *lazyProc
|
||||||
|
procWintunReceivePacket *lazyProc
|
||||||
|
procWintunReleaseReceivePacket *lazyProc
|
||||||
|
procWintunSendPacket *lazyProc
|
||||||
|
procWintunStartSession *lazyProc
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggerLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
logInfo loggerLevel = iota
|
||||||
|
logWarn
|
||||||
|
logErr
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
modwintun = newLazyDLL("wintun.dll", setupLogger)
|
||||||
|
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
|
||||||
|
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
|
||||||
|
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
|
||||||
|
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
|
||||||
|
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
|
||||||
|
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
|
||||||
|
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
|
||||||
|
procWintunEndSession = modwintun.NewProc("WintunEndSession")
|
||||||
|
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
|
||||||
|
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
|
||||||
|
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
|
||||||
|
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
|
||||||
|
procWintunStartSession = modwintun.NewProc("WintunStartSession")
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitWintun() (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("init wintun.dll error: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = modwintun.Load(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
procWintunCreateAdapter.Addr()
|
||||||
|
procWintunOpenAdapter.Addr()
|
||||||
|
procWintunCloseAdapter.Addr()
|
||||||
|
procWintunDeleteDriver.Addr()
|
||||||
|
procWintunGetAdapterLUID.Addr()
|
||||||
|
procWintunGetRunningDriverVersion.Addr()
|
||||||
|
procWintunAllocateSendPacket.Addr()
|
||||||
|
procWintunEndSession.Addr()
|
||||||
|
procWintunGetReadWaitEvent.Addr()
|
||||||
|
procWintunReceivePacket.Addr()
|
||||||
|
procWintunReleaseReceivePacket.Addr()
|
||||||
|
procWintunSendPacket.Addr()
|
||||||
|
procWintunStartSession.Addr()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
||||||
|
return &lazyDLL{Name: name, onLoad: onLoad}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
|
||||||
|
switch level {
|
||||||
|
case logInfo:
|
||||||
|
log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||||
|
case logWarn:
|
||||||
|
log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||||
|
case logErr:
|
||||||
|
log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||||
|
default:
|
||||||
|
log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupLogger(dll *lazyDLL) {
|
||||||
|
var callback uintptr
|
||||||
|
if runtime.GOARCH == "386" {
|
||||||
|
callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int {
|
||||||
|
return logMessage(level, 0, msg)
|
||||||
|
})
|
||||||
|
} else if runtime.GOARCH == "arm" {
|
||||||
|
callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int {
|
||||||
|
return logMessage(level, 0, msg)
|
||||||
|
})
|
||||||
|
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
|
||||||
|
callback = windows.NewCallback(logMessage)
|
||||||
|
}
|
||||||
|
_, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
||||||
|
return &lazyProc{dll: d, Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
type lazyProc struct {
|
||||||
|
Name string
|
||||||
|
mu sync.Mutex
|
||||||
|
dll *lazyDLL
|
||||||
|
addr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *lazyProc) Find() error {
|
||||||
|
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.addr != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.dll.Load()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err)
|
||||||
|
}
|
||||||
|
addr, err := p.nameToAddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting %s address: %w", p.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *lazyProc) Addr() uintptr {
|
||||||
|
err := p.Find()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return p.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *lazyProc) Load() error {
|
||||||
|
return p.dll.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
type lazyDLL struct {
|
||||||
|
Name string
|
||||||
|
Base windows.Handle
|
||||||
|
mu sync.Mutex
|
||||||
|
module *memmod.Module
|
||||||
|
onLoad func(d *lazyDLL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *lazyDLL) Load() error {
|
||||||
|
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
if d.module != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
module, err := memmod.LoadLibrary(dllData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to load library: %w", err)
|
||||||
|
}
|
||||||
|
d.Base = windows.Handle(module.BaseAddr())
|
||||||
|
|
||||||
|
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||||
|
if d.onLoad != nil {
|
||||||
|
d.onLoad(d)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||||
|
return p.dll.module.ProcAddressByName(p.Name)
|
||||||
|
}
|
13
listener/tun/device/tun/driver/dll_windows_386.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_386.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed x86/wintun.dll
|
||||||
|
var dllData []byte
|
13
listener/tun/device/tun/driver/dll_windows_amd64.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_amd64.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed amd64/wintun.dll
|
||||||
|
var dllData []byte
|
13
listener/tun/device/tun/driver/dll_windows_arm.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_arm.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed arm/wintun.dll
|
||||||
|
var dllData []byte
|
13
listener/tun/device/tun/driver/dll_windows_arm64.go
Normal file
13
listener/tun/device/tun/driver/dll_windows_arm64.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed arm64/wintun.dll
|
||||||
|
var dllData []byte
|
10
listener/tun/device/tun/driver/package_info.go
Normal file
10
listener/tun/device/tun/driver/package_info.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// https://git.zx2c4.com/wireguard-go/tree/tun/wintun
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package driver
|
BIN
listener/tun/device/tun/driver/x86/wintun.dll
Executable file
BIN
listener/tun/device/tun/driver/x86/wintun.dll
Executable file
Binary file not shown.
@ -3,7 +3,6 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -43,11 +42,11 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
|
|||||||
forcedMTU = int(t.mtu)
|
forcedMTU = int(t.mtu)
|
||||||
}
|
}
|
||||||
|
|
||||||
nt, err := tun.CreateTUN(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
|
||||||
|
|
||||||
// retry if abnormal exit on Windows at last time
|
// retry if abnormal exit at last time on Windows
|
||||||
if err != nil && runtime.GOOS == "windows" && errors.Is(err, os.ErrExist) {
|
if err != nil && runtime.GOOS == "windows" && os.IsExist(err) {
|
||||||
nt, err = tun.CreateTUN(t.name, forcedMTU)
|
nt, err = newDevice(t.name, forcedMTU)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
|
import "golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
offset = 4 /* 4 bytes TUN_PI */
|
offset = 4 /* 4 bytes TUN_PI */
|
||||||
defaultMTU = 1500
|
defaultMTU = 1500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func newDevice(name string, mtu int) (tun.Device, error) {
|
||||||
|
return tun.CreateTUN(name, mtu)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Dreamacro/clash/listener/tun/device/tun/driver"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
@ -20,3 +22,11 @@ func init() {
|
|||||||
func (t *TUN) LUID() uint64 {
|
func (t *TUN) LUID() uint64 {
|
||||||
return t.nt.LUID()
|
return t.nt.LUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newDevice(name string, mtu int) (nt tun.Device, err error) {
|
||||||
|
if err = driver.InitWintun(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return tun.CreateTUN(name, mtu)
|
||||||
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package commons
|
package commons
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/component/iface"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -18,6 +17,10 @@ var (
|
|||||||
monitorStarted = false
|
monitorStarted = false
|
||||||
monitorStop = make(chan struct{}, 2)
|
monitorStop = make(chan struct{}, 2)
|
||||||
monitorMux sync.Mutex
|
monitorMux sync.Mutex
|
||||||
|
|
||||||
|
tunStatus = C.TunDisabled
|
||||||
|
tunChangeCallback C.TUNChangeCallback
|
||||||
|
errInterfaceNotFound = errors.New("default interface not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
func ipv4MaskString(bits int) string {
|
func ipv4MaskString(bits int) string {
|
||||||
@ -29,54 +32,6 @@ func ipv4MaskString(bits int) string {
|
|||||||
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartDefaultInterfaceChangeMonitor() {
|
func SetTunChangeCallback(callback C.TUNChangeCallback) {
|
||||||
monitorMux.Lock()
|
tunChangeCallback = callback
|
||||||
if monitorStarted {
|
|
||||||
monitorMux.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
monitorStarted = true
|
|
||||||
monitorMux.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-monitorStop:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.NewTicker(monitorDuration)
|
|
||||||
defer t.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
interfaceName, err := GetAutoDetectInterface()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("[TUN] default interface monitor err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
old := dialer.DefaultInterface.Load()
|
|
||||||
if interfaceName == old {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer.DefaultInterface.Store(interfaceName)
|
|
||||||
|
|
||||||
iface.FlushCache()
|
|
||||||
|
|
||||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
|
||||||
case <-monitorStop:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func StopDefaultInterfaceChangeMonitor() {
|
|
||||||
monitorMux.Lock()
|
|
||||||
defer monitorMux.Unlock()
|
|
||||||
|
|
||||||
if monitorStarted {
|
|
||||||
monitorStop <- struct{}{}
|
|
||||||
monitorStarted = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,22 +6,26 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cmd"
|
"github.com/Dreamacro/clash/common/cmd"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"golang.org/x/net/route"
|
"golang.org/x/net/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
func GetAutoDetectInterface() (string, error) {
|
||||||
iface, err := defaultRouteInterface()
|
ifaceM, err := defaultRouteInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return iface.Name, nil
|
return ifaceM.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, _ int, autoRoute bool) error {
|
||||||
if !addr.Addr().Is4() {
|
if !addr.Addr().Is4() {
|
||||||
return fmt.Errorf("supported ipv4 only")
|
return fmt.Errorf("supported ipv4 only")
|
||||||
}
|
}
|
||||||
@ -96,17 +100,69 @@ func defaultRouteInterface() (*net.Interface, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err1 := net.InterfaceByIndex(routeMessage.Index)
|
ifaceM, err1 := net.InterfaceByIndex(routeMessage.Index)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(iface.Name, "utun") {
|
if strings.HasPrefix(ifaceM.Name, "utun") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return iface, nil
|
return ifaceM, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("ambiguous gateway interfaces found")
|
return nil, fmt.Errorf("ambiguous gateway interfaces found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartDefaultInterfaceChangeMonitor() {
|
||||||
|
monitorMux.Lock()
|
||||||
|
if monitorStarted {
|
||||||
|
monitorMux.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
monitorStarted = true
|
||||||
|
monitorMux.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-monitorStop:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.NewTicker(monitorDuration)
|
||||||
|
defer t.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
interfaceName, err := GetAutoDetectInterface()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
old := dialer.DefaultInterface.Load()
|
||||||
|
if interfaceName == old {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.DefaultInterface.Store(interfaceName)
|
||||||
|
|
||||||
|
iface.FlushCache()
|
||||||
|
|
||||||
|
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||||
|
case <-monitorStop:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopDefaultInterfaceChangeMonitor() {
|
||||||
|
monitorMux.Lock()
|
||||||
|
defer monitorMux.Unlock()
|
||||||
|
|
||||||
|
if monitorStarted {
|
||||||
|
monitorStop <- struct{}{}
|
||||||
|
monitorStarted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,16 +3,20 @@ package commons
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/cmd"
|
"github.com/Dreamacro/clash/common/cmd"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
func GetAutoDetectInterface() (string, error) {
|
||||||
return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
|
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, _ int, autoRoute bool) error {
|
||||||
var (
|
var (
|
||||||
interfaceName = dev.Name()
|
interfaceName = dev.Name()
|
||||||
ip = addr.Masked().Addr().Next()
|
ip = addr.Masked().Addr().Next()
|
||||||
@ -51,3 +55,55 @@ func execRouterCmd(action, route string, interfaceName string, linkIP string) er
|
|||||||
_, err := cmd.ExecCmd(cmdStr)
|
_, err := cmd.ExecCmd(cmdStr)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartDefaultInterfaceChangeMonitor() {
|
||||||
|
monitorMux.Lock()
|
||||||
|
if monitorStarted {
|
||||||
|
monitorMux.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
monitorStarted = true
|
||||||
|
monitorMux.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-monitorStop:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.NewTicker(monitorDuration)
|
||||||
|
defer t.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-t.C:
|
||||||
|
interfaceName, err := GetAutoDetectInterface()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
old := dialer.DefaultInterface.Load()
|
||||||
|
if interfaceName == old {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.DefaultInterface.Store(interfaceName)
|
||||||
|
|
||||||
|
iface.FlushCache()
|
||||||
|
|
||||||
|
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
|
||||||
|
case <-monitorStop:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopDefaultInterfaceChangeMonitor() {
|
||||||
|
monitorMux.Lock()
|
||||||
|
defer monitorMux.Unlock()
|
||||||
|
|
||||||
|
if monitorStarted {
|
||||||
|
monitorStop <- struct{}{}
|
||||||
|
monitorStarted = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,3 +17,7 @@ func GetAutoDetectInterface() (string, error) {
|
|||||||
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
|
||||||
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StartDefaultInterfaceChangeMonitor() {}
|
||||||
|
|
||||||
|
func StopDefaultInterfaceChangeMonitor() {}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package commons
|
package commons
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/iface"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/listener/tun/device"
|
"github.com/Dreamacro/clash/listener/tun/device"
|
||||||
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
"github.com/Dreamacro/clash/listener/tun/device/tun"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
@ -16,7 +20,11 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wintunInterfaceName string
|
var (
|
||||||
|
wintunInterfaceName string
|
||||||
|
unicastAddressChangeCallback *winipcfg.UnicastAddressChangeCallback
|
||||||
|
unicastAddressChangeLock sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
func GetAutoDetectInterface() (string, error) {
|
func GetAutoDetectInterface() (string, error) {
|
||||||
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
|
||||||
@ -220,15 +228,15 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, iface := range interfaces {
|
for _, ifaceM := range interfaces {
|
||||||
if iface.OperStatus == winipcfg.IfOperStatusUp {
|
if ifaceM.OperStatus == winipcfg.IfOperStatusUp {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
|
for address := ifaceM.FirstUnicastAddress; address != nil; address = address.Next {
|
||||||
if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] {
|
if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] {
|
||||||
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
|
||||||
log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
|
log.Infoln("[TUN] cleaning up stale address %s from interface ‘%s’", prefix.String(), ifaceM.FriendlyName())
|
||||||
_ = iface.LUID.DeleteIPAddress(prefix)
|
_ = ifaceM.LUID.DeleteIPAddress(prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +245,7 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
|
|||||||
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
|
||||||
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("get ethernet interface failure. %w", err)
|
return "", fmt.Errorf("get default interface failure. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var destination netip.Prefix
|
var destination netip.Prefix
|
||||||
@ -247,25 +255,96 @@ func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, erro
|
|||||||
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, iface := range interfaces {
|
for _, ifaceM := range interfaces {
|
||||||
if iface.OperStatus != winipcfg.IfOperStatusUp {
|
if ifaceM.OperStatus != winipcfg.IfOperStatusUp {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ifname := iface.FriendlyName()
|
ifname := ifaceM.FriendlyName()
|
||||||
|
|
||||||
if wintunInterfaceName == ifname {
|
if wintunInterfaceName == ifname {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
for gatewayAddress := ifaceM.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
|
||||||
nextHop := nnip.IpToAddr(gatewayAddress.Address.IP())
|
nextHop := nnip.IpToAddr(gatewayAddress.Address.IP())
|
||||||
|
|
||||||
if _, err = iface.LUID.Route(destination, nextHop); err == nil {
|
if _, err = ifaceM.LUID.Route(destination, nextHop); err == nil {
|
||||||
return ifname, nil
|
return ifname, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("ethernet interface not found")
|
return "", errInterfaceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func unicastAddressChange(_ winipcfg.MibNotificationType, unicastAddress *winipcfg.MibUnicastIPAddressRow) {
|
||||||
|
unicastAddressChangeLock.Lock()
|
||||||
|
defer unicastAddressChangeLock.Unlock()
|
||||||
|
|
||||||
|
interfaceName, err := GetAutoDetectInterface()
|
||||||
|
if err != nil {
|
||||||
|
if err == errInterfaceNotFound && tunStatus == C.TunEnabled {
|
||||||
|
log.Warnln("[TUN] lost the default interface, pause tun adapter")
|
||||||
|
|
||||||
|
tunStatus = C.TunPaused
|
||||||
|
tunChangeCallback.Pause()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaceM, err := net.InterfaceByIndex(int(unicastAddress.InterfaceIndex))
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[TUN] default interface monitor err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newName := ifaceM.Name
|
||||||
|
|
||||||
|
if newName != interfaceName {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.DefaultInterface.Store(interfaceName)
|
||||||
|
|
||||||
|
iface.FlushCache()
|
||||||
|
|
||||||
|
if tunStatus == C.TunPaused {
|
||||||
|
log.Warnln("[TUN] found interface %s(%s), resume tun adapter", interfaceName, unicastAddress.Address.Addr())
|
||||||
|
|
||||||
|
tunStatus = C.TunEnabled
|
||||||
|
tunChangeCallback.Resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warnln("[TUN] default interface changed to %s(%s) by monitor", interfaceName, unicastAddress.Address.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartDefaultInterfaceChangeMonitor() {
|
||||||
|
if unicastAddressChangeCallback != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
unicastAddressChangeCallback, err = winipcfg.RegisterUnicastAddressChangeCallback(unicastAddressChange)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("[TUN] register uni-cast address change callback failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunStatus = C.TunEnabled
|
||||||
|
|
||||||
|
log.Infoln("[TUN] register uni-cast address change callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopDefaultInterfaceChangeMonitor() {
|
||||||
|
if unicastAddressChangeCallback == nil || tunStatus == C.TunPaused {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = unicastAddressChangeCallback.Unregister()
|
||||||
|
unicastAddressChangeCallback = nil
|
||||||
|
tunChangeCallback = nil
|
||||||
|
tunStatus = C.TunDisabled
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New TunAdapter
|
// New TunAdapter
|
||||||
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
|
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, tunChangeCallback C.TUNChangeCallback) (ipstack.Stack, error) {
|
||||||
var (
|
var (
|
||||||
tunAddress = netip.Prefix{}
|
tunAddress = netip.Prefix{}
|
||||||
devName = tunConf.Device
|
devName = tunConf.Device
|
||||||
@ -98,6 +98,7 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tunConf.AutoDetectInterface {
|
if tunConf.AutoDetectInterface {
|
||||||
|
commons.SetTunChangeCallback(tunChangeCallback)
|
||||||
go commons.StartDefaultInterfaceChangeMonitor()
|
go commons.StartDefaultInterfaceChangeMonitor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
test/.golangci.yaml
Normal file
16
test/.golangci.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- gofumpt
|
||||||
|
- govet
|
||||||
|
- gci
|
||||||
|
- staticcheck
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- prefix(github.com/Dreamacro/clash)
|
||||||
|
- default
|
||||||
|
staticcheck:
|
||||||
|
go: '1.18'
|
@ -1,8 +1,9 @@
|
|||||||
lint:
|
lint:
|
||||||
golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./...
|
GOOS=darwin golangci-lint run ./...
|
||||||
|
GOOS=linux golangci-lint run ./...
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -p 1 -v ./...
|
go test -p 1 -v ./...
|
||||||
|
|
||||||
benchmark:
|
benchmark:
|
||||||
go test -benchmem -run=^$ -bench .
|
go test -benchmem -run=^$$ -bench .
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -53,13 +54,10 @@ var (
|
|||||||
{HostPort: "10002", HostIP: "0.0.0.0"},
|
{HostPort: "10002", HostIP: "0.0.0.0"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
isDarwin = runtime.GOOS == "darwin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
isDarwin = true
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDir, err := os.Getwd()
|
currentDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -112,6 +110,7 @@ func init() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("pulling image:", image)
|
||||||
imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{})
|
imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -216,46 +215,35 @@ func testPingPongWithSocksPort(t *testing.T, port int) {
|
|||||||
pingCh, pongCh, test := newPingPongPair()
|
pingCh, pongCh, test := newPingPongPair()
|
||||||
go func() {
|
go func() {
|
||||||
l, err := Listen("tcp", ":10001")
|
l, err := Listen("tcp", ":10001")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
c, err := l.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 4)
|
buf := make([]byte, 4)
|
||||||
if _, err := io.ReadFull(c, buf); err != nil {
|
_, err = io.ReadFull(c, buf)
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
pingCh <- buf
|
pingCh <- buf
|
||||||
if _, err := c.Write([]byte("pong")); err != nil {
|
_, err = c.Write([]byte("pong"))
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil {
|
_, err = socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil)
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := c.Write([]byte("ping")); err != nil {
|
_, err = c.Write([]byte("ping"))
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 4)
|
buf := make([]byte, 4)
|
||||||
if _, err := io.ReadFull(c, buf); err != nil {
|
_, err = io.ReadFull(c, buf)
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
pongCh <- buf
|
pongCh <- buf
|
||||||
}()
|
}()
|
||||||
@ -306,9 +294,7 @@ func testPingPongWithConn(t *testing.T, c net.Conn) error {
|
|||||||
|
|
||||||
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
||||||
l, err := ListenPacket("udp", ":10001")
|
l, err := ListenPacket("udp", ":10001")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
|
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
|
||||||
@ -351,9 +337,7 @@ type hashPair struct {
|
|||||||
|
|
||||||
func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
||||||
l, err := Listen("tcp", ":10001")
|
l, err := Listen("tcp", ":10001")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
times := 100
|
times := 100
|
||||||
@ -445,9 +429,7 @@ func testLargeDataWithConn(t *testing.T, c net.Conn) error {
|
|||||||
|
|
||||||
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
||||||
l, err := ListenPacket("udp", ":10001")
|
l, err := ListenPacket("udp", ":10001")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
|
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
|
||||||
@ -543,7 +525,7 @@ func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
|
|||||||
|
|
||||||
func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
|
func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
|
||||||
err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
|
err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
@ -566,9 +548,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
DstPort: "10001",
|
DstPort: "10001",
|
||||||
AddrType: socks5.AtypDomainName,
|
AddrType: socks5.AtypDomainName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
assert.NoError(t, testPingPongWithConn(t, conn))
|
assert.NoError(t, testPingPongWithConn(t, conn))
|
||||||
|
|
||||||
@ -577,9 +557,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
DstPort: "10001",
|
DstPort: "10001",
|
||||||
AddrType: socks5.AtypDomainName,
|
AddrType: socks5.AtypDomainName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
assert.NoError(t, testLargeDataWithConn(t, conn))
|
assert.NoError(t, testLargeDataWithConn(t, conn))
|
||||||
|
|
||||||
@ -593,9 +571,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
DstPort: "10001",
|
DstPort: "10001",
|
||||||
AddrType: socks5.AtypIPv4,
|
AddrType: socks5.AtypIPv4,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer pc.Close()
|
defer pc.Close()
|
||||||
|
|
||||||
assert.NoError(t, testPingPongWithPacketConn(t, pc))
|
assert.NoError(t, testPingPongWithPacketConn(t, pc))
|
||||||
@ -606,9 +582,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
DstPort: "10001",
|
DstPort: "10001",
|
||||||
AddrType: socks5.AtypIPv4,
|
AddrType: socks5.AtypIPv4,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer pc.Close()
|
defer pc.Close()
|
||||||
|
|
||||||
assert.NoError(t, testLargeDataWithPacketConn(t, pc))
|
assert.NoError(t, testLargeDataWithPacketConn(t, pc))
|
||||||
@ -619,9 +593,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
DstPort: "10001",
|
DstPort: "10001",
|
||||||
AddrType: socks5.AtypIPv4,
|
AddrType: socks5.AtypIPv4,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
defer pc.Close()
|
defer pc.Close()
|
||||||
|
|
||||||
assert.NoError(t, testPacketConnTimeout(t, pc))
|
assert.NoError(t, testPacketConnTimeout(t, pc))
|
||||||
@ -629,40 +601,55 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
|
|||||||
|
|
||||||
func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
|
func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
|
||||||
l, err := Listen("tcp", ":10001")
|
l, err := Listen("tcp", ":10001")
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
|
|
||||||
go func() {
|
|
||||||
c, err := l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
io.Copy(io.Discard, c)
|
|
||||||
}()
|
|
||||||
|
|
||||||
chunkSize := int64(16 * 1024)
|
chunkSize := int64(16 * 1024)
|
||||||
chunk := make([]byte, chunkSize)
|
chunk := make([]byte, chunkSize)
|
||||||
rand.Read(chunk)
|
rand.Read(chunk)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, err := c.Write(chunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
io.Copy(io.Discard, c)
|
||||||
|
}()
|
||||||
|
|
||||||
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
|
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
|
||||||
Host: localIP.String(),
|
Host: localIP.String(),
|
||||||
DstPort: "10001",
|
DstPort: "10001",
|
||||||
AddrType: socks5.AtypDomainName,
|
AddrType: socks5.AtypDomainName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.SetBytes(chunkSize)
|
_, err = conn.Write([]byte("skip protocol handshake"))
|
||||||
b.ResetTimer()
|
require.NoError(b, err)
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if _, err := conn.Write(chunk); err != nil {
|
b.Run("Write", func(b *testing.B) {
|
||||||
assert.FailNow(b, err.Error())
|
b.SetBytes(chunkSize)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
conn.Write(chunk)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
|
b.Run("Read", func(b *testing.B) {
|
||||||
|
b.SetBytes(chunkSize)
|
||||||
|
buf := make([]byte, chunkSize)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
io.ReadFull(conn, buf)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClash_Basic(t *testing.T) {
|
func TestClash_Basic(t *testing.T) {
|
||||||
@ -671,12 +658,11 @@ mixed-port: 10000
|
|||||||
log-level: silent
|
log-level: silent
|
||||||
`
|
`
|
||||||
|
|
||||||
if err := parseAndApply(basic); err != nil {
|
err := parseAndApply(basic)
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
require.True(t, TCPing(net.JoinHostPort(localIP.String(), "10000")))
|
||||||
testPingPongWithSocksPort(t, 10000)
|
testPingPongWithSocksPort(t, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func exchange(address, domain string, tp uint16) ([]dns.RR, error) {
|
func exchange(address, domain string, tp uint16) ([]dns.RR, error) {
|
||||||
@ -30,18 +31,15 @@ dns:
|
|||||||
- 119.29.29.29
|
- 119.29.29.29
|
||||||
`
|
`
|
||||||
|
|
||||||
if err := parseAndApply(basic); err != nil {
|
err := parseAndApply(basic)
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
|
|
||||||
rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA)
|
rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if !assert.NotEmpty(t, rr) {
|
assert.NotEmptyf(t, rr, "record empty")
|
||||||
assert.FailNow(t, "record empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
record := rr[0].(*dns.A)
|
record := rr[0].(*dns.A)
|
||||||
assert.Equal(t, record.A.String(), "1.1.1.1")
|
assert.Equal(t, record.A.String(), "1.1.1.1")
|
||||||
@ -68,9 +66,8 @@ dns:
|
|||||||
- 119.29.29.29
|
- 119.29.29.29
|
||||||
`
|
`
|
||||||
|
|
||||||
if err := parseAndApply(basic); err != nil {
|
err := parseAndApply(basic)
|
||||||
assert.FailNow(t, err.Error())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
|
@ -8,8 +8,6 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
var isDarwin = false
|
|
||||||
|
|
||||||
func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) {
|
func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) {
|
||||||
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
22
test/go.mod
22
test/go.mod
@ -3,31 +3,29 @@ module clash-test
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/clash v1.7.2-0.20211108085948-bd2ea2b917aa
|
github.com/Dreamacro/clash v0.0.0
|
||||||
github.com/docker/docker v20.10.13+incompatible
|
github.com/docker/docker v20.10.16+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/miekg/dns v1.1.49
|
github.com/miekg/dns v1.1.49
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/Dreamacro/clash => ../
|
replace github.com/Dreamacro/clash => ../
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 // indirect
|
github.com/Dreamacro/go-shadowsocks2 v0.1.8 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
github.com/containerd/containerd v1.6.1 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
|
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/oschwald/geoip2-golang v1.7.0 // indirect
|
github.com/oschwald/geoip2-golang v1.7.0 // indirect
|
||||||
@ -39,7 +37,7 @@ require (
|
|||||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
|
||||||
@ -51,11 +49,9 @@ require (
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
|
||||||
google.golang.org/grpc v1.45.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.1.0 // indirect
|
gotest.tools/v3 v3.1.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e // indirect
|
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 // indirect
|
||||||
)
|
)
|
||||||
|
1301
test/go.sum
1301
test/go.sum
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClash_SnellObfsHTTP(t *testing.T) {
|
func TestClash_SnellObfsHTTP(t *testing.T) {
|
||||||
@ -24,9 +24,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "snell-http")
|
id, err := startContainer(cfg, hostCfg, "snell-http")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -41,9 +39,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) {
|
|||||||
"mode": "http",
|
"mode": "http",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -61,9 +57,7 @@ func TestClash_SnellObfsTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "snell-tls")
|
id, err := startContainer(cfg, hostCfg, "snell-tls")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -78,9 +72,7 @@ func TestClash_SnellObfsTLS(t *testing.T) {
|
|||||||
"mode": "tls",
|
"mode": "tls",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -98,9 +90,7 @@ func TestClash_Snell(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "snell")
|
id, err := startContainer(cfg, hostCfg, "snell")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -112,9 +102,7 @@ func TestClash_Snell(t *testing.T) {
|
|||||||
Port: 10002,
|
Port: 10002,
|
||||||
Psk: "password",
|
Psk: "password",
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -132,9 +120,7 @@ func TestClash_Snellv3(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "snell")
|
id, err := startContainer(cfg, hostCfg, "snell")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -148,9 +134,7 @@ func TestClash_Snellv3(t *testing.T) {
|
|||||||
UDP: true,
|
UDP: true,
|
||||||
Version: 3,
|
Version: 3,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -167,10 +151,8 @@ func Benchmark_Snell(b *testing.B) {
|
|||||||
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))},
|
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))},
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "snell-http")
|
id, err := startContainer(cfg, hostCfg, "snell-bench")
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -185,9 +167,7 @@ func Benchmark_Snell(b *testing.B) {
|
|||||||
"mode": "http",
|
"mode": "http",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
benchmarkProxy(b, proxy)
|
benchmarkProxy(b, proxy)
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClash_Shadowsocks(t *testing.T) {
|
func TestClash_Shadowsocks(t *testing.T) {
|
||||||
@ -22,9 +23,7 @@ func TestClash_Shadowsocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "ss")
|
id, err := startContainer(cfg, hostCfg, "ss")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -38,9 +37,7 @@ func TestClash_Shadowsocks(t *testing.T) {
|
|||||||
Cipher: "chacha20-ietf-poly1305",
|
Cipher: "chacha20-ietf-poly1305",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -60,9 +57,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "ss-obfs-http")
|
id, err := startContainer(cfg, hostCfg, "ss-obfs-http")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -80,9 +75,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) {
|
|||||||
"mode": "http",
|
"mode": "http",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -102,9 +95,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "ss-obfs-tls")
|
id, err := startContainer(cfg, hostCfg, "ss-obfs-tls")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -122,9 +113,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) {
|
|||||||
"mode": "tls",
|
"mode": "tls",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -144,9 +133,7 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin")
|
id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -164,9 +151,7 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
|
|||||||
"mode": "websocket",
|
"mode": "websocket",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -183,10 +168,8 @@ func Benchmark_Shadowsocks(b *testing.B) {
|
|||||||
PortBindings: defaultPortBindings,
|
PortBindings: defaultPortBindings,
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "ss")
|
id, err := startContainer(cfg, hostCfg, "ss-bench")
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -200,10 +183,8 @@ func Benchmark_Shadowsocks(b *testing.B) {
|
|||||||
Cipher: "aes-256-gcm",
|
Cipher: "aes-256-gcm",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
|
||||||
benchmarkProxy(b, proxy)
|
benchmarkProxy(b, proxy)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClash_Trojan(t *testing.T) {
|
func TestClash_Trojan(t *testing.T) {
|
||||||
@ -27,9 +28,7 @@ func TestClash_Trojan(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "trojan")
|
id, err := startContainer(cfg, hostCfg, "trojan")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -44,9 +43,7 @@ func TestClash_Trojan(t *testing.T) {
|
|||||||
SkipCertVerify: true,
|
SkipCertVerify: true,
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -67,10 +64,10 @@ func TestClash_TrojanGrpc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "trojan-grpc")
|
id, err := startContainer(cfg, hostCfg, "trojan-grpc")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
||||||
Name: "trojan",
|
Name: "trojan",
|
||||||
@ -85,9 +82,7 @@ func TestClash_TrojanGrpc(t *testing.T) {
|
|||||||
GrpcServiceName: "example",
|
GrpcServiceName: "example",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -108,10 +103,10 @@ func TestClash_TrojanWebsocket(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "trojan-ws")
|
id, err := startContainer(cfg, hostCfg, "trojan-ws")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
||||||
Name: "trojan",
|
Name: "trojan",
|
||||||
@ -123,9 +118,7 @@ func TestClash_TrojanWebsocket(t *testing.T) {
|
|||||||
UDP: true,
|
UDP: true,
|
||||||
Network: "ws",
|
Network: "ws",
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -146,10 +139,11 @@ func TestClash_TrojanXTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "trojan-xtls")
|
id, err := startContainer(cfg, hostCfg, "trojan-xtls")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
t.Cleanup(func() {
|
||||||
defer cleanContainer(id)
|
cleanContainer(id)
|
||||||
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
|
||||||
Name: "trojan",
|
Name: "trojan",
|
||||||
@ -163,9 +157,7 @@ func TestClash_TrojanXTLS(t *testing.T) {
|
|||||||
Flow: "xtls-rprx-direct",
|
Flow: "xtls-rprx-direct",
|
||||||
FlowShow: true,
|
FlowShow: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -185,10 +177,8 @@ func Benchmark_Trojan(b *testing.B) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "trojan")
|
id, err := startContainer(cfg, hostCfg, "trojan-bench")
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -203,10 +193,8 @@ func Benchmark_Trojan(b *testing.B) {
|
|||||||
SkipCertVerify: true,
|
SkipCertVerify: true,
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
|
||||||
benchmarkProxy(b, proxy)
|
benchmarkProxy(b, proxy)
|
||||||
}
|
}
|
||||||
|
13
test/util.go
13
test/util.go
@ -35,3 +35,16 @@ func ListenPacket(network, address string) (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
return nil, lastErr
|
return nil, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TCPing(addr string) bool {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err == nil {
|
||||||
|
conn.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClash_VlessTLS(t *testing.T) {
|
func TestClash_VlessTLS(t *testing.T) {
|
||||||
@ -27,10 +27,10 @@ func TestClash_VlessTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vless-tls")
|
id, err := startContainer(cfg, hostCfg, "vless-tls")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||||
Name: "vless",
|
Name: "vless",
|
||||||
@ -41,9 +41,7 @@ func TestClash_VlessTLS(t *testing.T) {
|
|||||||
ServerName: "example.org",
|
ServerName: "example.org",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -64,10 +62,10 @@ func TestClash_VlessXTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vless-xtls")
|
id, err := startContainer(cfg, hostCfg, "vless-xtls")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||||
Name: "vless",
|
Name: "vless",
|
||||||
@ -80,9 +78,7 @@ func TestClash_VlessXTLS(t *testing.T) {
|
|||||||
Flow: "xtls-rprx-direct",
|
Flow: "xtls-rprx-direct",
|
||||||
FlowShow: true,
|
FlowShow: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -103,10 +99,10 @@ func TestClash_VlessWS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vless-ws")
|
id, err := startContainer(cfg, hostCfg, "vless-ws")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVless(outbound.VlessOption{
|
proxy, err := outbound.NewVless(outbound.VlessOption{
|
||||||
Name: "vless",
|
Name: "vless",
|
||||||
@ -118,9 +114,7 @@ func TestClash_VlessWS(t *testing.T) {
|
|||||||
Network: "ws",
|
Network: "ws",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClash_Vmess(t *testing.T) {
|
func TestClash_Vmess(t *testing.T) {
|
||||||
@ -25,9 +25,7 @@ func TestClash_Vmess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess")
|
id, err := startContainer(cfg, hostCfg, "vmess")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -41,9 +39,7 @@ func TestClash_Vmess(t *testing.T) {
|
|||||||
Cipher: "auto",
|
Cipher: "auto",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -64,10 +60,10 @@ func TestClash_VmessTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-tls")
|
id, err := startContainer(cfg, hostCfg, "vmess-tls")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -80,9 +76,7 @@ func TestClash_VmessTLS(t *testing.T) {
|
|||||||
ServerName: "example.org",
|
ServerName: "example.org",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -103,10 +97,10 @@ func TestClash_VmessHTTP2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-http2")
|
id, err := startContainer(cfg, hostCfg, "vmess-http2")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -124,9 +118,7 @@ func TestClash_VmessHTTP2(t *testing.T) {
|
|||||||
Path: "/test",
|
Path: "/test",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -145,10 +137,10 @@ func TestClash_VmessHTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-http")
|
id, err := startContainer(cfg, hostCfg, "vmess-http")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -176,9 +168,7 @@ func TestClash_VmessHTTP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -197,10 +187,10 @@ func TestClash_VmessWebsocket(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-ws")
|
id, err := startContainer(cfg, hostCfg, "vmess-ws")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -211,9 +201,7 @@ func TestClash_VmessWebsocket(t *testing.T) {
|
|||||||
Network: "ws",
|
Network: "ws",
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -234,10 +222,10 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-ws")
|
id, err := startContainer(cfg, hostCfg, "vmess-ws")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -250,9 +238,7 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
|
|||||||
SkipCertVerify: true,
|
SkipCertVerify: true,
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -273,10 +259,10 @@ func TestClash_VmessGrpc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-grpc")
|
id, err := startContainer(cfg, hostCfg, "vmess-grpc")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -293,9 +279,7 @@ func TestClash_VmessGrpc(t *testing.T) {
|
|||||||
GrpcServiceName: "example!",
|
GrpcServiceName: "example!",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -314,10 +298,10 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt")
|
id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -333,9 +317,7 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
|
|||||||
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
|
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
@ -354,10 +336,10 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt")
|
id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt")
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
t.Cleanup(func() {
|
||||||
}
|
cleanContainer(id)
|
||||||
defer cleanContainer(id)
|
})
|
||||||
|
|
||||||
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
proxy, err := outbound.NewVmess(outbound.VmessOption{
|
||||||
Name: "vmess",
|
Name: "vmess",
|
||||||
@ -372,16 +354,14 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
|
|||||||
Path: "/?ed=2048",
|
Path: "/?ed=2048",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
assert.FailNow(t, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
testSuit(t, proxy)
|
testSuit(t, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark_Vmess(b *testing.B) {
|
func Benchmark_Vmess(b *testing.B) {
|
||||||
configPath := C.Path.Resolve("vmess-aead.json")
|
configPath := C.Path.Resolve("vmess.json")
|
||||||
|
|
||||||
cfg := &container.Config{
|
cfg := &container.Config{
|
||||||
Image: ImageVmess,
|
Image: ImageVmess,
|
||||||
@ -392,10 +372,8 @@ func Benchmark_Vmess(b *testing.B) {
|
|||||||
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := startContainer(cfg, hostCfg, "vmess-aead")
|
id, err := startContainer(cfg, hostCfg, "vmess-bench")
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Cleanup(func() {
|
b.Cleanup(func() {
|
||||||
cleanContainer(id)
|
cleanContainer(id)
|
||||||
@ -410,9 +388,7 @@ func Benchmark_Vmess(b *testing.B) {
|
|||||||
AlterID: 0,
|
AlterID: 0,
|
||||||
UDP: true,
|
UDP: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(b, err)
|
||||||
assert.FailNow(b, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(waitTime)
|
time.Sleep(waitTime)
|
||||||
benchmarkProxy(b, proxy)
|
benchmarkProxy(b, proxy)
|
||||||
|
Reference in New Issue
Block a user