diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 2491f013..78d9d4b2 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -43,7 +43,7 @@ jobs: env: NAME: Clash.Meta BINDIR: bin - run: make -j releases + run: make -j$(($(nproc) + 1)) releases - name: Delete current release assets uses: andreaswilli/delete-release-assets-action@v2.0.0 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 45d0841f..4b2b60da 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -33,7 +33,7 @@ jobs: env: NAME: Clash.Meta BINDIR: bin - run: make -j releases + run: make -j$(($(nproc) + 1)) releases - name: Upload Release uses: softprops/action-gh-release@v1 diff --git a/adapter/adapter.go b/adapter/adapter.go index 84895852..b1867998 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -4,6 +4,9 @@ import ( "context" "encoding/json" "fmt" + "github.com/Dreamacro/clash/common/queue" + "github.com/Dreamacro/clash/component/dialer" + C "github.com/Dreamacro/clash/constant" "net" "net/http" "net/netip" @@ -11,10 +14,6 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/common/queue" - "github.com/Dreamacro/clash/component/dialer" - C "github.com/Dreamacro/clash/constant" - "go.uber.org/atomic" ) diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index 0d86c2da..0666d306 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -51,6 +51,10 @@ func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return c, errors.New("no support") } +func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + return nil, errors.New("no support") +} + // ListenPacketContext implements C.ProxyAdapter func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { return nil, errors.New("no support") diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index a03b24f9..4aa290c0 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -1,6 +1,8 @@ package outboundgroup import ( + "context" + "fmt" "github.com/Dreamacro/clash/adapter/outbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" @@ -105,6 +107,34 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { return proxies } +func (gb *GroupBase) URLTest(ctx context.Context, url string) (map[string]uint16, error) { + var wg sync.WaitGroup + var lock sync.Mutex + mp := map[string]uint16{} + proxies := gb.GetProxies(false) + for _, proxy := range proxies { + proxy := proxy + wg.Add(1) + go func() { + delay, err := proxy.URLTest(ctx, url) + lock.Lock() + if err == nil { + mp[proxy.Name()] = delay + } + lock.Unlock() + + wg.Done() + }() + } + wg.Wait() + + if len(mp) == 0 { + return mp, fmt.Errorf("get delay: all proxies timeout") + } else { + return mp, nil + } +} + func (gb *GroupBase) onDialFailed() { if gb.failedTesting.Load() { return diff --git a/component/process/process.go b/component/process/process.go index 252541cb..32c992c4 100644 --- a/component/process/process.go +++ b/component/process/process.go @@ -38,7 +38,9 @@ func FindUid(network string, srcIP netip.Addr, srcPort int) (int32, error) { } func ShouldFindProcess(metadata *C.Metadata) bool { - if !enableFindProcess || metadata.Process != "" || metadata.ProcessPath != "" { + if !enableFindProcess || + metadata.Process != "" || + metadata.ProcessPath != "" { return false } for _, ip := range localIPs { diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index 9fe6309f..bd1b262b 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -89,24 +89,39 @@ func ResolveIP(host string) (netip.Addr, error) { // ResolveIPv4ProxyServerHost proxies server host only func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) { if ProxyServerHostResolver != nil { - return ResolveIPv4WithResolver(host, ProxyServerHostResolver) + if ip, err := ResolveIPv4WithResolver(host, ProxyServerHostResolver); err != nil { + return ResolveIPv4(host) + } else { + return ip, nil + } } + return ResolveIPv4(host) } // ResolveIPv6ProxyServerHost proxies server host only func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) { if ProxyServerHostResolver != nil { - return ResolveIPv6WithResolver(host, ProxyServerHostResolver) + if ip, err := ResolveIPv6WithResolver(host, ProxyServerHostResolver); err != nil { + return ResolveIPv6(host) + } else { + return ip, nil + } } + return ResolveIPv6(host) } // ResolveProxyServerHost proxies server host only func ResolveProxyServerHost(host string) (netip.Addr, error) { if ProxyServerHostResolver != nil { - return ResolveIPWithResolver(host, ProxyServerHostResolver) + if ip, err := ResolveIPWithResolver(host, ProxyServerHostResolver); err != nil { + return ResolveIP(host) + } else { + return ip, err + } } + return ResolveIP(host) } diff --git a/constant/adapters.go b/constant/adapters.go index 4e40f21e..9d089424 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -108,6 +108,11 @@ type ProxyAdapter interface { Unwrap(metadata *Metadata) Proxy } +type Group interface { + URLTest(ctx context.Context, url string) (mp map[string]uint16, err error) + GetProxies(touch bool) []Proxy +} + type DelayHistory struct { Time time.Time `json:"time"` Delay uint16 `json:"delay"` diff --git a/constant/provider/interface.go b/constant/provider/interface.go index c00a0ba2..646e459b 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -1,7 +1,7 @@ package provider import ( - "github.com/Dreamacro/clash/constant" + C "github.com/Dreamacro/clash/constant" ) // Vehicle Type @@ -65,10 +65,10 @@ type Provider interface { // ProxyProvider interface type ProxyProvider interface { Provider - Proxies() []constant.Proxy + Proxies() []C.Proxy // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. // Commonly used in DialContext and DialPacketConn - ProxiesWithTouch() []constant.Proxy + ProxiesWithTouch() []C.Proxy HealthCheck() Version() uint } @@ -100,7 +100,7 @@ func (rt RuleType) String() string { type RuleProvider interface { Provider Behavior() RuleType - Match(*constant.Metadata) bool + Match(*C.Metadata) bool ShouldResolveIP() bool - AsRule(adaptor string) constant.Rule + AsRule(adaptor string) C.Rule } diff --git a/constant/rule.go b/constant/rule.go index 7c7eea96..2cc60cdd 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -9,6 +9,8 @@ const ( GEOIP IPCIDR SrcIPCIDR + IPSuffix + SrcIPSuffix SrcPort DstPort Process @@ -41,6 +43,10 @@ func (rt RuleType) String() string { return "IPCIDR" case SrcIPCIDR: return "SrcIPCIDR" + case IPSuffix: + return "IPSuffix" + case SrcIPSuffix: + return "SrcIPSuffix" case SrcPort: return "SrcPort" case DstPort: diff --git a/go.mod b/go.mod index e4a64444..b99d1f06 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,4 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) -replace golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 => github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e - replace github.com/vishvananda/netlink v1.2.0-beta.0.20220404152918-5e915e014938 => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 diff --git a/go.sum b/go.sum index 5079b59e..49033985 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviK github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y= github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc= github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e h1:GRfT5Lf8HP7RNczKIwTYLoCh1PPuIs/sY9hj+W+3deg= -github.com/MetaCubeX/wintun-go v0.0.0-20220319102620-bbc5e6b2015e/go.mod h1:ARUuShAtcziEJ/vnZ2hgoP+zc0J7Ukcca2S/NPDoQCc= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -345,6 +343,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= +golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0= golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 h1:SEYkJAIuYAsSAPkCffOiYLtq5brBDSI+L0mRjSsvSTY= diff --git a/hub/route/groups.go b/hub/route/groups.go new file mode 100644 index 00000000..e877bfd0 --- /dev/null +++ b/hub/route/groups.go @@ -0,0 +1,79 @@ +package route + +import ( + "context" + "github.com/Dreamacro/clash/adapter" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "net/http" + "strconv" + "time" +) + +func GroupRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getGroups) + + r.Route("/{name}", func(r chi.Router) { + r.Use(parseProxyName, findProxyByName) + r.Get("/", getGroup) + r.Get("/delay", getGroupDelay) + }) + return r +} + +func getGroups(w http.ResponseWriter, r *http.Request) { + var gs []C.Proxy + for _, p := range tunnel.Proxies() { + if _, ok := p.(*adapter.Proxy).ProxyAdapter.(C.Group); ok { + gs = append(gs, p) + } + } + render.JSON(w, r, render.M{ + "proxies": gs, + }) +} + +func getGroup(w http.ResponseWriter, r *http.Request) { + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + if _, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group); ok { + render.JSON(w, r, proxy) + return + } + render.Status(r, http.StatusNotFound) + render.JSON(w, r, ErrNotFound) +} + +func getGroupDelay(w http.ResponseWriter, r *http.Request) { + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + group, ok := proxy.(*adapter.Proxy).ProxyAdapter.(C.Group) + if !ok { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, ErrNotFound) + return + } + + query := r.URL.Query() + url := query.Get("url") + timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) + defer cancel() + + dm, err := group.URLTest(ctx, url) + + if err != nil { + render.Status(r, http.StatusGatewayTimeout) + render.JSON(w, r, newError(err.Error())) + return + } + + render.JSON(w, r, dm) +} diff --git a/hub/route/server.go b/hub/route/server.go index ac12fed3..7ebc4ceb 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -68,6 +68,7 @@ func Start(addr string, secret string) { r.Get("/version", version) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) + r.Mount("/group", GroupRouter()) r.Mount("/rules", ruleRouter()) r.Mount("/connections", connectionRouter()) r.Mount("/providers/proxies", proxyProviderRouter()) diff --git a/listener/tun/device/tun/wintun/embed_dll/amd64/wintun.dll b/listener/tun/device/tun/driver/amd64/wintun.dll similarity index 100% rename from listener/tun/device/tun/wintun/embed_dll/amd64/wintun.dll rename to listener/tun/device/tun/driver/amd64/wintun.dll diff --git a/listener/tun/device/tun/wintun/embed_dll/arm/wintun.dll b/listener/tun/device/tun/driver/arm/wintun.dll similarity index 100% rename from listener/tun/device/tun/wintun/embed_dll/arm/wintun.dll rename to listener/tun/device/tun/driver/arm/wintun.dll diff --git a/listener/tun/device/tun/wintun/embed_dll/arm64/wintun.dll b/listener/tun/device/tun/driver/arm64/wintun.dll similarity index 100% rename from listener/tun/device/tun/wintun/embed_dll/arm64/wintun.dll rename to listener/tun/device/tun/driver/arm64/wintun.dll diff --git a/listener/tun/device/tun/driver/dll_windows.go b/listener/tun/device/tun/driver/dll_windows.go new file mode 100644 index 00000000..5f4a5dae --- /dev/null +++ b/listener/tun/device/tun/driver/dll_windows.go @@ -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(dllContent) + 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) +} diff --git a/listener/tun/device/tun/driver/dll_windows_386.go b/listener/tun/device/tun/driver/dll_windows_386.go new file mode 100644 index 00000000..12ace4e8 --- /dev/null +++ b/listener/tun/device/tun/driver/dll_windows_386.go @@ -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 dllContent []byte diff --git a/listener/tun/device/tun/driver/dll_windows_amd64.go b/listener/tun/device/tun/driver/dll_windows_amd64.go new file mode 100644 index 00000000..58f3e01e --- /dev/null +++ b/listener/tun/device/tun/driver/dll_windows_amd64.go @@ -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 dllContent []byte diff --git a/listener/tun/device/tun/driver/dll_windows_arm.go b/listener/tun/device/tun/driver/dll_windows_arm.go new file mode 100644 index 00000000..bb941068 --- /dev/null +++ b/listener/tun/device/tun/driver/dll_windows_arm.go @@ -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 dllContent []byte diff --git a/listener/tun/device/tun/driver/dll_windows_arm64.go b/listener/tun/device/tun/driver/dll_windows_arm64.go new file mode 100644 index 00000000..05bd1cbd --- /dev/null +++ b/listener/tun/device/tun/driver/dll_windows_arm64.go @@ -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 dllContent []byte diff --git a/listener/tun/device/tun/wintun/package_info.go b/listener/tun/device/tun/driver/package_info.go old mode 100755 new mode 100644 similarity index 54% rename from listener/tun/device/tun/wintun/package_info.go rename to listener/tun/device/tun/driver/package_info.go index 22e9bd05..0c1bf7e4 --- a/listener/tun/device/tun/wintun/package_info.go +++ b/listener/tun/device/tun/driver/package_info.go @@ -1,11 +1,10 @@ //go:build windows -// +build windows -// Modified from: https://git.zx2c4.com/wireguard-go/tree/tun/wintun +// https://git.zx2c4.com/wireguard-go/tree/tun/wintun /* SPDX-License-Identifier: MIT * * Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved. */ -package wintun +package driver diff --git a/listener/tun/device/tun/wintun/embed_dll/x86/wintun.dll b/listener/tun/device/tun/driver/x86/wintun.dll similarity index 100% rename from listener/tun/device/tun/wintun/embed_dll/x86/wintun.dll rename to listener/tun/device/tun/driver/x86/wintun.dll diff --git a/listener/tun/device/tun/tun_wireguard.go b/listener/tun/device/tun/tun_wireguard.go index 529a1054..1aafae54 100644 --- a/listener/tun/device/tun/tun_wireguard.go +++ b/listener/tun/device/tun/tun_wireguard.go @@ -3,7 +3,6 @@ package tun import ( - "errors" "fmt" "os" "runtime" @@ -43,11 +42,11 @@ func Open(name string, mtu uint32) (_ device.Device, err error) { 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 - if err != nil && runtime.GOOS == "windows" && errors.Is(err, os.ErrExist) { - nt, err = tun.CreateTUN(t.name, forcedMTU) + // retry if abnormal exit at last time on Windows + if err != nil && runtime.GOOS == "windows" && os.IsExist(err) { + nt, err = newDevice(t.name, forcedMTU) } if err != nil { diff --git a/listener/tun/device/tun/tun_wireguard_unix.go b/listener/tun/device/tun/tun_wireguard_unix.go index d88787fb..b6d3addf 100644 --- a/listener/tun/device/tun/tun_wireguard_unix.go +++ b/listener/tun/device/tun/tun_wireguard_unix.go @@ -2,7 +2,13 @@ package tun +import "golang.zx2c4.com/wireguard/tun" + const ( offset = 4 /* 4 bytes TUN_PI */ defaultMTU = 1500 ) + +func newDevice(name string, mtu int) (tun.Device, error) { + return tun.CreateTUN(name, mtu) +} diff --git a/listener/tun/device/tun/tun_wireguard_windows.go b/listener/tun/device/tun/tun_wireguard_windows.go index f73286d8..c5c16a1d 100644 --- a/listener/tun/device/tun/tun_wireguard_windows.go +++ b/listener/tun/device/tun/tun_wireguard_windows.go @@ -1,6 +1,8 @@ package tun import ( + "github.com/Dreamacro/clash/listener/tun/device/tun/driver" + "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/tun" ) @@ -20,3 +22,11 @@ func init() { func (t *TUN) LUID() uint64 { 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) +} diff --git a/listener/tun/device/tun/wintun/dll_windows.go b/listener/tun/device/tun/wintun/dll_windows.go deleted file mode 100755 index 4368100b..00000000 --- a/listener/tun/device/tun/wintun/dll_windows.go +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package wintun - -import ( - "fmt" - "golang.zx2c4.com/wintun/embed_dll" - "golang.zx2c4.com/wireguard/windows/driver/memmod" - "sync" - "sync/atomic" - "unsafe" -) - -func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL { - return &lazyDLL{Name: name, onLoad: onLoad} -} - -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 %v DLL: %w", p.dll.Name, err) - } - addr, err := p.nameToAddr() - if err != nil { - return fmt.Errorf("Error getting %v 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 -} - -type lazyDLL struct { - Name string - 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(embed_dll.DDlContent) - if err != nil { - return fmt.Errorf("unable to load library: %w", err) - } - - 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) -} diff --git a/listener/tun/device/tun/wintun/embed_dll/windows_386.go b/listener/tun/device/tun/wintun/embed_dll/windows_386.go deleted file mode 100755 index 05fab743..00000000 --- a/listener/tun/device/tun/wintun/embed_dll/windows_386.go +++ /dev/null @@ -1,21 +0,0 @@ -package embed_dll - -// Copyright 2020 MeshStep Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -import ( - _ "embed" -) - -//go:embed x86/wintun.dll -var DDlContent []byte diff --git a/listener/tun/device/tun/wintun/embed_dll/windows_amd64.go b/listener/tun/device/tun/wintun/embed_dll/windows_amd64.go deleted file mode 100755 index 2ee9f791..00000000 --- a/listener/tun/device/tun/wintun/embed_dll/windows_amd64.go +++ /dev/null @@ -1,21 +0,0 @@ -package embed_dll - -// Copyright 2020 MeshStep Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -import ( - _ "embed" -) - -//go:embed amd64/wintun.dll -var DDlContent []byte diff --git a/listener/tun/device/tun/wintun/embed_dll/windows_arm.go b/listener/tun/device/tun/wintun/embed_dll/windows_arm.go deleted file mode 100755 index 0636f21b..00000000 --- a/listener/tun/device/tun/wintun/embed_dll/windows_arm.go +++ /dev/null @@ -1,21 +0,0 @@ -package embed_dll - -// Copyright 2020 MeshStep Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -import ( - _ "embed" -) - -//go:embed arm/wintun.dll -var DDlContent []byte diff --git a/listener/tun/device/tun/wintun/embed_dll/windows_arm64.go b/listener/tun/device/tun/wintun/embed_dll/windows_arm64.go deleted file mode 100755 index 1e5d605d..00000000 --- a/listener/tun/device/tun/wintun/embed_dll/windows_arm64.go +++ /dev/null @@ -1,21 +0,0 @@ -package embed_dll - -// Copyright 2020 MeshStep Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -import ( - _ "embed" -) - -//go:embed arm64/wintun.dll -var DDlContent []byte diff --git a/listener/tun/device/tun/wintun/go.mod b/listener/tun/device/tun/wintun/go.mod deleted file mode 100755 index 33d4b961..00000000 --- a/listener/tun/device/tun/wintun/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module golang.zx2c4.com/wintun - -go 1.18 - -require ( - golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 - golang.zx2c4.com/wireguard/windows v0.5.3 -) diff --git a/listener/tun/device/tun/wintun/go.sum b/listener/tun/device/tun/wintun/go.sum deleted file mode 100755 index e1f1e5ec..00000000 --- a/listener/tun/device/tun/wintun/go.sum +++ /dev/null @@ -1,17 +0,0 @@ -github.com/MetaCubeX/Clash.Meta v1.9.1 h1:jHZhVRBxFuaCRBN9vxB/FL5R16wY4kIgNqjszdXPeLs= -github.com/MetaCubeX/Clash.Meta v1.9.1/go.mod h1:/I4cSh+PcgmtS5SEnFp8RANL6aVRd3i9YOult+mKLhU= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf h1:Fm4IcnUL803i92qDlmB0obyHmosDrxZWxJL3gIeNqOw= -golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= -golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/listener/tun/device/tun/wintun/session_windows.go b/listener/tun/device/tun/wintun/session_windows.go deleted file mode 100755 index f023baf7..00000000 --- a/listener/tun/device/tun/wintun/session_windows.go +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package wintun - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -type Session struct { - handle uintptr -} - -const ( - PacketSizeMax = 0xffff // Maximum packet size - RingCapacityMin = 0x20000 // Minimum ring capacity (128 kiB) - RingCapacityMax = 0x4000000 // Maximum ring capacity (64 MiB) -) - -// Packet with data -type Packet struct { - Next *Packet // Pointer to next packet in queue - Size uint32 // Size of packet (max WINTUN_MAX_IP_PACKET_SIZE) - Data *[PacketSizeMax]byte // Pointer to layer 3 IPv4 or IPv6 packet -} - -var ( - 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 (wintun *Adapter) StartSession(capacity uint32) (session Session, err error) { - r0, _, e1 := syscall.Syscall(procWintunStartSession.Addr(), 2, uintptr(wintun.handle), uintptr(capacity), 0) - if r0 == 0 { - err = e1 - } else { - session = Session{r0} - } - return -} - -func (session Session) End() { - syscall.Syscall(procWintunEndSession.Addr(), 1, session.handle, 0, 0) - session.handle = 0 -} - -func (session Session) ReadWaitEvent() (handle windows.Handle) { - r0, _, _ := syscall.Syscall(procWintunGetReadWaitEvent.Addr(), 1, session.handle, 0, 0) - handle = windows.Handle(r0) - return -} - -func (session Session) ReceivePacket() (packet []byte, err error) { - var packetSize uint32 - r0, _, e1 := syscall.Syscall(procWintunReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packetSize)), 0) - if r0 == 0 { - err = e1 - return - } - packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize) - return -} - -func (session Session) ReleaseReceivePacket(packet []byte) { - syscall.Syscall(procWintunReleaseReceivePacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0) -} - -func (session Session) AllocateSendPacket(packetSize int) (packet []byte, err error) { - r0, _, e1 := syscall.Syscall(procWintunAllocateSendPacket.Addr(), 2, session.handle, uintptr(packetSize), 0) - if r0 == 0 { - err = e1 - return - } - packet = unsafe.Slice((*byte)(unsafe.Pointer(r0)), packetSize) - return -} - -func (session Session) SendPacket(packet []byte) { - syscall.Syscall(procWintunSendPacket.Addr(), 2, session.handle, uintptr(unsafe.Pointer(&packet[0])), 0) -} diff --git a/listener/tun/device/tun/wintun/wintun_windows.go b/listener/tun/device/tun/wintun/wintun_windows.go deleted file mode 100755 index 254dc1e8..00000000 --- a/listener/tun/device/tun/wintun/wintun_windows.go +++ /dev/null @@ -1,152 +0,0 @@ -//go:build windows - -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved. - */ - -package wintun - -import ( - "log" - "runtime" - "syscall" - "unsafe" - - "golang.org/x/sys/windows" -) - -type loggerLevel int - -const ( - logInfo loggerLevel = iota - logWarn - logErr -) - -const AdapterNameMax = 128 - -type Adapter struct { - handle uintptr -} - -var ( - 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") -) - -type TimestampedWriter interface { - WriteWithTimestamp(p []byte, ts int64) (n int, err error) -} - -func logMessage(level loggerLevel, timestamp uint64, msg *uint16) int { - if tw, ok := log.Default().Writer().(TimestampedWriter); ok { - tw.WriteWithTimestamp([]byte(log.Default().Prefix()+windows.UTF16PtrToString(msg)), (int64(timestamp)-116444736000000000)*100) - } else { - log.Println(windows.UTF16PtrToString(msg)) - } - return 0 -} - -func setupLogger(dll *lazyDLL) { - var callback uintptr - if runtime.GOARCH == "386" { - callback = windows.NewCallback(func(level loggerLevel, timestampLow, timestampHigh uint32, msg *uint16) int { - return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg) - }) - } else if runtime.GOARCH == "arm" { - callback = windows.NewCallback(func(level loggerLevel, _, timestampLow, timestampHigh uint32, msg *uint16) int { - return logMessage(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg) - }) - } else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { - callback = windows.NewCallback(logMessage) - } - syscall.Syscall(dll.NewProc("WintunSetLogger").Addr(), 1, callback, 0, 0) -} - -func closeAdapter(wintun *Adapter) { - syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0) -} - -// CreateAdapter creates a Wintun adapter. name is the cosmetic name of the adapter. -// tunnelType represents the type of adapter and should be "Wintun". requestedGUID is -// the GUID of the created network adapter, which then influences NLA generation -// deterministically. If it is set to nil, the GUID is chosen by the system at random, -// and hence a new NLA entry is created for each new adapter. -func CreateAdapter(name string, tunnelType string, requestedGUID *windows.GUID) (wintun *Adapter, err error) { - var name16 *uint16 - name16, err = windows.UTF16PtrFromString(name) - if err != nil { - return - } - var tunnelType16 *uint16 - tunnelType16, err = windows.UTF16PtrFromString(tunnelType) - if err != nil { - return - } - r0, _, e1 := syscall.Syscall(procWintunCreateAdapter.Addr(), 3, uintptr(unsafe.Pointer(name16)), uintptr(unsafe.Pointer(tunnelType16)), uintptr(unsafe.Pointer(requestedGUID))) - if r0 == 0 { - err = e1 - return - } - wintun = &Adapter{handle: r0} - runtime.SetFinalizer(wintun, closeAdapter) - return -} - -// OpenAdapter opens an existing Wintun adapter by name. -func OpenAdapter(name string) (wintun *Adapter, err error) { - var name16 *uint16 - name16, err = windows.UTF16PtrFromString(name) - if err != nil { - return - } - r0, _, e1 := syscall.Syscall(procWintunOpenAdapter.Addr(), 1, uintptr(unsafe.Pointer(name16)), 0, 0) - if r0 == 0 { - err = e1 - return - } - wintun = &Adapter{handle: r0} - runtime.SetFinalizer(wintun, closeAdapter) - return -} - -// Close closes a Wintun adapter. -func (wintun *Adapter) Close() (err error) { - runtime.SetFinalizer(wintun, nil) - r1, _, e1 := syscall.Syscall(procWintunCloseAdapter.Addr(), 1, wintun.handle, 0, 0) - if r1 == 0 { - err = e1 - } - return -} - -// Uninstall removes the driver from the system if no drivers are currently in use. -func Uninstall() (err error) { - r1, _, e1 := syscall.Syscall(procWintunDeleteDriver.Addr(), 0, 0, 0, 0) - if r1 == 0 { - err = e1 - } - return -} - -// RunningVersion returns the version of the loaded driver. -func RunningVersion() (version uint32, err error) { - r0, _, e1 := syscall.Syscall(procWintunGetRunningDriverVersion.Addr(), 0, 0, 0, 0) - version = uint32(r0) - if version == 0 { - err = e1 - } - return -} - -// LUID returns the LUID of the adapter. -func (wintun *Adapter) LUID() (luid uint64) { - syscall.Syscall(procWintunGetAdapterLUID.Addr(), 2, uintptr(wintun.handle), uintptr(unsafe.Pointer(&luid)), 0) - return -} diff --git a/listener/tun/ipstack/commons/router_android.go b/listener/tun/ipstack/commons/router_android.go index 172c9ce3..139dbca9 100644 --- a/listener/tun/ipstack/commons/router_android.go +++ b/listener/tun/ipstack/commons/router_android.go @@ -44,7 +44,7 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, return err } - if err = netlink.AddrAdd(metaLink, naddr); err != nil { + if err = netlink.AddrAdd(metaLink, naddr); err != nil && err.Error() != "file exists" { return err } diff --git a/listener/tun/ipstack/commons/router_linux.go b/listener/tun/ipstack/commons/router_linux.go index d5f3edab..37a92d2e 100644 --- a/listener/tun/ipstack/commons/router_linux.go +++ b/listener/tun/ipstack/commons/router_linux.go @@ -4,7 +4,6 @@ package commons import ( "fmt" - "github.com/Dreamacro/clash/common/cmd" "github.com/Dreamacro/clash/listener/tun/device" "github.com/vishvananda/netlink" "net" @@ -46,12 +45,12 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, return err } - naddr, err := netlink.ParseAddr(addr.String()) + nlAddr, err := netlink.ParseAddr(addr.String()) if err != nil { return err } - if err = netlink.AddrAdd(metaLink, naddr); err != nil { + if err = netlink.AddrAdd(metaLink, nlAddr); err != nil && err.Error() != "file exists" { return err } @@ -59,23 +58,13 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, return err } - if err = netlink.RouteAdd(&netlink.Route{ - LinkIndex: metaLink.Attrs().Index, - Scope: netlink.SCOPE_LINK, - Protocol: 2, - Src: ip.AsSlice(), - Table: 254, - }); err != nil { - return err - } - if autoRoute { - _ = configInterfaceRouting(metaLink.Attrs().Index, interfaceName, ip) + _ = configInterfaceRouting(metaLink.Attrs().Index, ip) } return nil } -func configInterfaceRouting(index int, interfaceName string, ip netip.Addr) error { +func configInterfaceRouting(index int, ip netip.Addr) error { for _, route := range defaultRoutes { _, ipn, err := net.ParseCIDR(route) if err != nil { @@ -97,11 +86,4 @@ func configInterfaceRouting(index int, interfaceName string, ip netip.Addr) erro return nil } -func execRouterCmd(action, route, interfaceName, linkIP, table string) error { - cmdStr := fmt.Sprintf("ip route %s %s dev %s proto kernel scope link src %s table %s", action, route, interfaceName, linkIP, table) - - _, err := cmd.ExecCmd(cmdStr) - return err -} - func CleanupRule() {} diff --git a/rule/common/base.go b/rule/common/base.go index 97a46eb7..381eab82 100644 --- a/rule/common/base.go +++ b/rule/common/base.go @@ -10,8 +10,8 @@ import ( var ( errPayload = errors.New("payload error") - - noResolve = "no-resolve" + initFlag bool + noResolve = "no-resolve" ) type Base struct { diff --git a/rule/common/geosite.go b/rule/common/geosite.go index 3e6fbe42..211cb570 100644 --- a/rule/common/geosite.go +++ b/rule/common/geosite.go @@ -2,6 +2,9 @@ package common import ( "fmt" + "io" + "net/http" + "os" "github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata/router" @@ -50,6 +53,14 @@ func (gs *GEOSITE) GetRecodeSize() int { } func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { + if !initFlag { + if err := initGeoSite(); err != nil { + log.Errorln("can't initial GeoSite: %s", err) + return nil, err + } + initFlag = true + } + matcher, size, err := geodata.LoadGeoSiteMatcher(country) if err != nil { return nil, fmt.Errorf("load GeoSite data error, %s", err.Error()) @@ -69,3 +80,45 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { } var _ C.Rule = (*GEOSITE)(nil) + +func downloadGeoSite(path string) (err error) { + resp, err := http.Get(C.GeoSiteUrl) + if err != nil { + return + } + defer resp.Body.Close() + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + + return err +} + +func initGeoSite() error { + if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { + log.Infoln("Can't find GeoSite.dat, start download") + if err := downloadGeoSite(C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) + } + log.Infoln("Download GeoSite.dat finish") + } + if !initFlag { + err := geodata.Verify(C.GeositeName) + if err != nil { + log.Warnln("GeoSite.dat invalid, remove and download: %s", err) + if err := os.Remove(C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error()) + } + if err := downloadGeoSite(C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) + } + } else { + initFlag = true + } + } + return nil +} diff --git a/rule/common/ipsuffix.go b/rule/common/ipsuffix.go new file mode 100644 index 00000000..18271244 --- /dev/null +++ b/rule/common/ipsuffix.go @@ -0,0 +1,79 @@ +package common + +import ( + C "github.com/Dreamacro/clash/constant" + "net/netip" +) + +type IPSuffix struct { + *Base + ipBytes []byte + bits int + payload string + adapter string + isSourceIP bool + noResolveIP bool +} + +func (is *IPSuffix) RuleType() C.RuleType { + if is.isSourceIP { + return C.SrcIPSuffix + } + return C.IPSuffix +} + +func (is *IPSuffix) Match(metadata *C.Metadata) bool { + ip := metadata.DstIP + if is.isSourceIP { + ip = metadata.SrcIP + } + + mIPBytes := ip.AsSlice() + if len(is.ipBytes) != len(mIPBytes) { + return false + } + + size := len(mIPBytes) + bits := is.bits + + for i := bits / 8; i > 0; i-- { + if is.ipBytes[size-i] != mIPBytes[size-i] { + return false + } + } + + if (is.ipBytes[size-bits/8-1] << (8 - bits%8)) != (mIPBytes[size-bits/8-1] << (8 - bits%8)) { + return false + } + + return true +} + +func (is *IPSuffix) Adapter() string { + return is.adapter +} + +func (is *IPSuffix) Payload() string { + return is.payload +} + +func (is *IPSuffix) ShouldResolveIP() bool { + return !is.noResolveIP +} + +func NewIPSuffix(payload, adapter string, isSrc, noResolveIP bool) (*IPSuffix, error) { + ipnet, err := netip.ParsePrefix(payload) + if err != nil { + return nil, errPayload + } + + return &IPSuffix{ + Base: &Base{}, + payload: payload, + ipBytes: ipnet.Addr().AsSlice(), + bits: ipnet.Bits(), + adapter: adapter, + isSourceIP: isSrc, + noResolveIP: noResolveIP, + }, nil +} diff --git a/rule/logic/and.go b/rule/logic/and.go index f2d54379..876bd71b 100644 --- a/rule/logic/and.go +++ b/rule/logic/and.go @@ -29,15 +29,14 @@ func NewAND(payload string, adapter string) (*AND, error) { and.rules = rules payloads := make([]string, 0, len(rules)) for _, rule := range rules { - payloads = append(payloads, fmt.Sprintf("(%s)", rule.Payload())) + payloads = append(payloads, fmt.Sprintf("(%s,%s)", rule.RuleType().String(), rule.Payload())) if rule.ShouldResolveIP() { and.needIP = true break } } - and.payload = strings.Join(payloads, " && ") - + and.payload = fmt.Sprintf("(%s)", strings.Join(payloads, " && ")) return and, nil } diff --git a/rule/logic/common.go b/rule/logic/common.go index 4272b943..0f43e688 100644 --- a/rule/logic/common.go +++ b/rule/logic/common.go @@ -4,12 +4,9 @@ import ( "fmt" "github.com/Dreamacro/clash/common/collections" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" RC "github.com/Dreamacro/clash/rule/common" - "github.com/Dreamacro/clash/rule/provider" - "io" - "net/http" - "os" + RP "github.com/Dreamacro/clash/rule/provider" + "github.com/Dreamacro/clash/rule/ruleparser" "regexp" "strings" ) @@ -60,64 +57,23 @@ func payloadToRule(subPayload string) (C.Rule, error) { if tp == "NOT" || tp == "OR" || tp == "AND" { return parseRule(tp, payload, nil) } - if tp == "GEOSITE" { - if err := initGeoSite(); err != nil { - log.Errorln("can't initial GeoSite: %s", err) - } - } - param := strings.Split(payload, ",") return parseRule(tp, param[0], param[1:]) } -func parseRule(tp, payload string, params []string) (C.Rule, error) { - var ( - parseErr error - parsed C.Rule - ) - +func parseRule(tp, payload string, params []string) (parsed C.Rule, parseErr error) { switch tp { - case "DOMAIN": - parsed = RC.NewDomain(payload, "") - case "DOMAIN-SUFFIX": - parsed = RC.NewDomainSuffix(payload, "") - case "DOMAIN-KEYWORD": - parsed = RC.NewDomainKeyword(payload, "") - case "GEOSITE": - parsed, parseErr = RC.NewGEOSITE(payload, "") - case "GEOIP": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, "", noResolve) - case "IP-CIDR", "IP-CIDR6": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPCIDR(payload, "", RC.WithIPCIDRNoResolve(noResolve)) - case "SRC-IP-CIDR": - parsed, parseErr = RC.NewIPCIDR(payload, "", RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) - case "SRC-PORT": - parsed, parseErr = RC.NewPort(payload, "", true) - case "DST-PORT": - parsed, parseErr = RC.NewPort(payload, "", false) - case "PROCESS-NAME": - parsed, parseErr = RC.NewProcess(payload, "", true) - case "PROCESS-PATH": - parsed, parseErr = RC.NewProcess(payload, "", false) - case "RULE-SET": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = provider.NewRuleSet(payload, "", noResolve) - case "UID": - parsed, parseErr = RC.NewUid(payload, "") - case "IN-TYPE": - parsed, parseErr = RC.NewInType(payload, "") - case "NOT": - parsed, parseErr = NewNOT(payload, "") case "AND": parsed, parseErr = NewAND(payload, "") case "OR": parsed, parseErr = NewOR(payload, "") - case "NETWORK": - parsed, parseErr = RC.NewNetworkType(payload, "") + case "NOT": + parsed, parseErr = NewNOT(payload, "") + case "RULE-SET": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RP.NewRuleSet(payload, "", noResolve) default: - parsed, parseErr = nil, fmt.Errorf("unsupported rule type %s", tp) + parsed, parseErr = ruleparser.ParseSameRule(tp, payload, "", params) } if parseErr != nil { @@ -202,32 +158,3 @@ func findSubRuleRange(payload string, ruleRanges []Range) []Range { return subRuleRange } - -func downloadGeoSite(path string) (err error) { - resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat") - if err != nil { - return - } - defer resp.Body.Close() - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, resp.Body) - - return err -} - -func initGeoSite() error { - if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { - log.Infoln("Need GeoSite but can't find GeoSite.dat, start download") - if err := downloadGeoSite(C.Path.GeoSite()); err != nil { - return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) - } - log.Infoln("Download GeoSite.dat finish") - } - - return nil -} diff --git a/rule/logic/not.go b/rule/logic/not.go index 20fb90c5..4259acbd 100644 --- a/rule/logic/not.go +++ b/rule/logic/not.go @@ -29,7 +29,7 @@ func NewNOT(payload string, adapter string) (*NOT, error) { } not.rule = rule[0] - not.payload = fmt.Sprintf("!(%s)", rule[0].Payload()) + not.payload = fmt.Sprintf("(!(%s,%s))", rule[0].RuleType(), rule[0].Payload()) return not, nil } diff --git a/rule/logic/or.go b/rule/logic/or.go index b79188c3..80b8eef3 100644 --- a/rule/logic/or.go +++ b/rule/logic/or.go @@ -55,13 +55,13 @@ func NewOR(payload string, adapter string) (*OR, error) { or.rules = rules payloads := make([]string, 0, len(rules)) for _, rule := range rules { - payloads = append(payloads, fmt.Sprintf("(%s)", rule.Payload())) + payloads = append(payloads, fmt.Sprintf("(%s,%s)", rule.RuleType(), rule.Payload())) if rule.ShouldResolveIP() { or.needIP = true break } } - or.payload = strings.Join(payloads, " || ") + or.payload = fmt.Sprintf("(%s)", strings.Join(payloads, " || ")) return or, nil } diff --git a/rule/parser.go b/rule/parser.go index 660f1575..05d01e32 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -1,50 +1,15 @@ package rules import ( - "fmt" C "github.com/Dreamacro/clash/constant" RC "github.com/Dreamacro/clash/rule/common" "github.com/Dreamacro/clash/rule/logic" RP "github.com/Dreamacro/clash/rule/provider" + "github.com/Dreamacro/clash/rule/ruleparser" ) -func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { - var ( - parseErr error - parsed C.Rule - ) - +func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { - case "DOMAIN": - parsed = RC.NewDomain(payload, target) - case "DOMAIN-SUFFIX": - parsed = RC.NewDomainSuffix(payload, target) - case "DOMAIN-KEYWORD": - parsed = RC.NewDomainKeyword(payload, target) - case "GEOSITE": - parsed, parseErr = RC.NewGEOSITE(payload, target) - case "GEOIP": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) - case "IP-CIDR", "IP-CIDR6": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) - case "SRC-IP-CIDR": - parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) - case "SRC-PORT": - parsed, parseErr = RC.NewPort(payload, target, true) - case "DST-PORT": - parsed, parseErr = RC.NewPort(payload, target, false) - case "PROCESS-NAME": - parsed, parseErr = RC.NewProcess(payload, target, true) - case "PROCESS-PATH": - parsed, parseErr = RC.NewProcess(payload, target, false) - case "NETWORK": - parsed, parseErr = RC.NewNetworkType(payload, target) - case "UID": - parsed, parseErr = RC.NewUid(payload, target) - case "IN-TYPE": - parsed, parseErr = RC.NewInType(payload, target) case "AND": parsed, parseErr = logic.NewAND(payload, target) case "OR": @@ -56,8 +21,9 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed, parseErr = RP.NewRuleSet(payload, target, noResolve) case "MATCH": parsed = RC.NewMatch(target) + parseErr = nil default: - parseErr = fmt.Errorf("unsupported rule type %s", tp) + parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params) } if parseErr != nil { @@ -72,5 +38,5 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed.SetRuleExtra(ruleExtra) - return parsed, nil + return } diff --git a/rule/provider/parse.go b/rule/provider/parse.go index ae53203b..1cbe79c2 100644 --- a/rule/provider/parse.go +++ b/rule/provider/parse.go @@ -7,6 +7,7 @@ import ( C "github.com/Dreamacro/clash/constant" P "github.com/Dreamacro/clash/constant/provider" RC "github.com/Dreamacro/clash/rule/common" + "github.com/Dreamacro/clash/rule/ruleparser" "time" ) @@ -51,46 +52,9 @@ func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvi return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil } -func parseRule(tp, payload, target string, params []string) (C.Rule, error) { - var ( - parseErr error - parsed C.Rule - ) +func parseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { + parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params) - switch tp { - case "DOMAIN": - parsed = RC.NewDomain(payload, target) - case "DOMAIN-SUFFIX": - parsed = RC.NewDomainSuffix(payload, target) - case "DOMAIN-KEYWORD": - parsed = RC.NewDomainKeyword(payload, target) - case "GEOIP": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) - case "GEOSITE": - parsed, parseErr = RC.NewGEOSITE(payload, target) - case "IP-CIDR", "IP-CIDR6": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) - case "SRC-IP-CIDR": - parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) - case "SRC-PORT": - parsed, parseErr = RC.NewPort(payload, target, true) - case "DST-PORT": - parsed, parseErr = RC.NewPort(payload, target, false) - case "PROCESS-NAME": - parsed, parseErr = RC.NewProcess(payload, target, true) - case "PROCESS-PATH": - parsed, parseErr = RC.NewProcess(payload, target, false) - case "NETWORK": - parsed, parseErr = RC.NewNetworkType(payload, target) - case "UID": - parsed, parseErr = RC.NewUid(payload, target) - case "IN-TYPE": - parsed, parseErr = RC.NewInType(payload, target) - default: - parseErr = fmt.Errorf("unsupported rule type %s", tp) - } if parseErr != nil { return nil, parseErr } diff --git a/rule/ruleparser/ruleparser.go b/rule/ruleparser/ruleparser.go new file mode 100644 index 00000000..55f63f82 --- /dev/null +++ b/rule/ruleparser/ruleparser.go @@ -0,0 +1,50 @@ +package ruleparser + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" + RC "github.com/Dreamacro/clash/rule/common" +) + +func ParseSameRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { + switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, target) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, target) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, target) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, target) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "IP-SUFFIX": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve) + case "SRC-IP-SUFFIX": + parsed, parseErr = RC.NewIPSuffix(payload, target, true, true) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, target, true) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, target, false) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, target, true) + case "PROCESS-PATH": + parsed, parseErr = RC.NewProcess(payload, target, false) + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, target) + case "UID": + parsed, parseErr = RC.NewUid(payload, target) + case "IN-TYPE": + parsed, parseErr = RC.NewInType(payload, target) + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + return +}