From d42dc46b8cca32d333d3d364400a22c7009d7cfe Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 28 Mar 2020 12:30:26 +0800 Subject: [PATCH] use tun2socket instead of netstack --- core/src/main/golang/bridge/init.go | 6 +- core/src/main/golang/bridge/profiles.go | 16 +- core/src/main/golang/bridge/proxies.go | 72 ++---- core/src/main/golang/bridge/tun.go | 8 +- core/src/main/golang/clash | 2 +- .../{profile/download.go => config/fetch.go} | 47 ++-- .../main/golang/{profile => config}/load.go | 18 +- .../main/golang/{profile => config}/patch.go | 19 +- core/src/main/golang/go.mod | 5 +- core/src/main/golang/go.sum | 18 +- core/src/main/golang/tun/dns.go | 239 ++++++++++++++++++ core/src/main/golang/tun/tun.go | 89 ++++--- core/src/main/golang/tun/udp.go | 55 ++++ core/src/main/golang/utils/close.go | 7 + .../java/com/github/kr328/clash/core/Clash.kt | 4 +- .../github/kr328/clash/service/TunService.kt | 18 +- 16 files changed, 468 insertions(+), 155 deletions(-) rename core/src/main/golang/{profile/download.go => config/fetch.go} (65%) rename core/src/main/golang/{profile => config}/load.go (88%) rename core/src/main/golang/{profile => config}/patch.go (66%) create mode 100644 core/src/main/golang/tun/dns.go create mode 100644 core/src/main/golang/tun/udp.go create mode 100644 core/src/main/golang/utils/close.go diff --git a/core/src/main/golang/bridge/init.go b/core/src/main/golang/bridge/init.go index d35ea69..0c0ffbc 100644 --- a/core/src/main/golang/bridge/init.go +++ b/core/src/main/golang/bridge/init.go @@ -7,7 +7,7 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" - "github.com/kr328/cfa/profile" + "github.com/kr328/cfa/config" ) var ( @@ -31,12 +31,12 @@ func SetHome(homeDir string) { } func Reset() { - profile.LoadDefault() + config.LoadDefault() tunnel.DefaultManager.ResetStatistic() } func SetApplicationVersion(version string) { - profile.ApplicationVersion = version + config.ApplicationVersion = version } func SetLogCallback(callback LogCallback) { diff --git a/core/src/main/golang/bridge/profiles.go b/core/src/main/golang/bridge/profiles.go index 3b17dc4..345f8fa 100644 --- a/core/src/main/golang/bridge/profiles.go +++ b/core/src/main/golang/bridge/profiles.go @@ -3,40 +3,40 @@ package bridge import ( "strings" - "github.com/kr328/cfa/profile" + "github.com/kr328/cfa/config" ) func ResetDnsAppend(dns string) { if len(dns) == 0 { - profile.NameServersAppend = make([]string, 0) + config.NameServersAppend = make([]string, 0) } else { - profile.NameServersAppend = strings.Split(dns, ",") + config.NameServersAppend = strings.Split(dns, ",") } } func SetDnsOverrideEnabled(enabled bool) { if enabled { - profile.DnsPatch = profile.OptionalDnsPatch + config.DnsPatch = config.OptionalDnsPatch } else { - profile.DnsPatch = nil + config.DnsPatch = nil } } func LoadProfileFile(path, baseDir string, callback DoneCallback) { go func() { - call(profile.LoadFromFile(path, baseDir), callback) + call(config.LoadFromFile(path, baseDir), callback) }() } func DownloadProfileAndCheck(url, output, baseDir string, callback DoneCallback) { go func() { - call(profile.DownloadAndCheck(url, output, baseDir), callback) + call(config.DownloadRemote(url, output, baseDir), callback) }() } func ReadProfileAndCheck(fd int, output, baseDir string, callback DoneCallback) { go func() { - call(profile.ReadAndCheck(fd, output, baseDir), callback) + call(config.DownloadLocal(fd, output, baseDir), callback) }() } diff --git a/core/src/main/golang/bridge/proxies.go b/core/src/main/golang/bridge/proxies.go index bcb3752..b0a5d57 100644 --- a/core/src/main/golang/bridge/proxies.go +++ b/core/src/main/golang/bridge/proxies.go @@ -53,26 +53,18 @@ func StartUrlTest(group string, callback DoneCallback) { p := tunnel.Proxies()[group] - pa, ok := p.(*outbound.Proxy) + pi, ok := p.(*outbound.Proxy) if !ok { return } - var providers []provider.ProxyProvider - - switch group := pa.ProxyAdapter.(type) { - case *outboundgroup.Fallback: - providers = group.GetProviders() - case *outboundgroup.URLTest: - providers = group.GetProviders() - case *outboundgroup.LoadBalance: - providers = group.GetProviders() - case *outboundgroup.Selector: - providers = group.GetProviders() - default: + group, ok := pi.ProxyAdapter.(outboundgroup.ProxyGroup) + if !ok { return } + providers := group.GetProxyProviders() + wg := &sync.WaitGroup{} wg.Add(len(providers)) @@ -91,55 +83,23 @@ func QueryAllProxyGroups(collection ProxyGroupCollection) { ps := tunnel.Proxies() for _, p := range ps { - pa, ok := p.(*outbound.Proxy) + pi, ok := p.(*outbound.Proxy) if !ok { continue } - switch group := pa.ProxyAdapter.(type) { - case *outboundgroup.Fallback: - collection.Add( - &ProxyGroupItem{ - Name: group.Name(), - Type: group.Type().String(), - Current: group.Now(), - Delay: int(p.LastDelay()), - providers: group.GetProviders(), - }, - ) - case *outboundgroup.URLTest: - collection.Add( - &ProxyGroupItem{ - Name: group.Name(), - Type: group.Type().String(), - Current: group.Now(), - Delay: int(p.LastDelay()), - providers: group.GetProviders(), - }, - ) - case *outboundgroup.LoadBalance: - collection.Add( - &ProxyGroupItem{ - Name: group.Name(), - Type: group.Type().String(), - Current: "", - Delay: int(p.LastDelay()), - providers: group.GetProviders(), - }, - ) - case *outboundgroup.Selector: - collection.Add( - &ProxyGroupItem{ - Name: group.Name(), - Type: group.Type().String(), - Current: group.Now(), - Delay: int(p.LastDelay()), - providers: group.GetProviders(), - }, - ) - default: + group, ok := pi.ProxyAdapter.(outboundgroup.ProxyGroup) + if !ok { continue } + + collection.Add(&ProxyGroupItem{ + Name: group.Name(), + Type: group.Type().String(), + Current: group.Now(), + Delay: 0, + providers: group.GetProxyProviders(), + }) } } diff --git a/core/src/main/golang/bridge/tun.go b/core/src/main/golang/bridge/tun.go index a081e30..1ef794b 100644 --- a/core/src/main/golang/bridge/tun.go +++ b/core/src/main/golang/bridge/tun.go @@ -28,9 +28,9 @@ func onNewListenConfig(listen *net.ListenConfig) { listen.Control = onNewSocket } -func onNewSocket(network, address string, c syscall.RawConn) error { +func onNewSocket(_, _ string, c syscall.RawConn) error { if cb := callback; cb != nil { - c.Control(func(fd uintptr) { + _ = c.Control(func(fd uintptr) { cb.OnCreateSocket(int(fd)) }) } @@ -38,10 +38,10 @@ func onNewSocket(network, address string, c syscall.RawConn) error { return nil } -func StartTunDevice(fd, mtu int, dns string, cb TunCallback) error { +func StartTunDevice(fd, mtu int, gateway, mirror, dns string, cb TunCallback) error { callback = cb - return tun.StartTunDevice(fd, mtu, dns) + return tun.StartTunDevice(fd, mtu, gateway, mirror, dns) } func StopTunDevice() { diff --git a/core/src/main/golang/clash b/core/src/main/golang/clash index b8f879f..518354e 160000 --- a/core/src/main/golang/clash +++ b/core/src/main/golang/clash @@ -1 +1 @@ -Subproject commit b8f879f34dbaa227e970088f8088ce0f726198c9 +Subproject commit 518354e7ebd3362315f02f34a6a9ce21131f8fc9 diff --git a/core/src/main/golang/profile/download.go b/core/src/main/golang/config/fetch.go similarity index 65% rename from core/src/main/golang/profile/download.go rename to core/src/main/golang/config/fetch.go index 994f78c..b917acf 100644 --- a/core/src/main/golang/profile/download.go +++ b/core/src/main/golang/config/fetch.go @@ -1,8 +1,9 @@ -package profile +package config import ( "context" "errors" + "github.com/kr328/cfa/utils" "io/ioutil" "net" "net/http" @@ -37,8 +38,8 @@ var client = &http.Client{ <-ctx.Done() - client.Close() - server.Close() + _ = client.Close() + _ = server.Close() }() return client, nil @@ -46,50 +47,64 @@ var client = &http.Client{ }, } -func DownloadAndCheck(url, output, baseDir string) error { +func fetchRemote(url string) ([]byte, error) { request, err := http.NewRequest("GET", url, nil) if err != nil { - return err + return nil, err } request.Header.Set("User-Agent", "ClashForAndroid/"+ApplicationVersion) response, err := client.Do(request) if err != nil { - return err + return nil, err } - defer response.Body.Close() + defer utils.CloseSilent(response.Body) + data, err := ioutil.ReadAll(response.Body) if err != nil { - return err + return nil, err } - return SaveAndCheck(data, output, baseDir) + return data, nil } -func ReadAndCheck(fd int, output, baseDir string) error { - syscall.SetNonblock(fd, true) +func fetchLocal(fd int) ([]byte, error) { + _ = syscall.SetNonblock(fd, true) file := os.NewFile(uintptr(fd), "/dev/null") - defer file.Close() + defer utils.CloseSilent(file) - data, err := ioutil.ReadAll(file) + return ioutil.ReadAll(file) +} + +func DownloadRemote(url, output, baseDir string) error { + data, err := fetchRemote(url) if err != nil { return err } - return SaveAndCheck(data, output, baseDir) + return save(data, output, baseDir) } -func SaveAndCheck(data []byte, output, baseDir string) error { +func DownloadLocal(fd int, output, baseDir string) error { + data, err := fetchLocal(fd) + if err != nil { + return err + } + + return save(data, output, baseDir) +} + +func save(data []byte, output, baseDir string) error { cfg, err := parseConfig(data, baseDir) if err != nil { return err } for _, v := range cfg.Providers { - v.Destroy() + _ = v.Destroy() } return ioutil.WriteFile(output, data, defaultFileMode) diff --git a/core/src/main/golang/profile/load.go b/core/src/main/golang/config/load.go similarity index 88% rename from core/src/main/golang/profile/load.go rename to core/src/main/golang/config/load.go index 2dfa0e9..8763672 100644 --- a/core/src/main/golang/profile/load.go +++ b/core/src/main/golang/config/load.go @@ -1,7 +1,7 @@ -package profile +package config import ( - "fmt" + "errors" "io/ioutil" "github.com/Dreamacro/clash/config" @@ -70,7 +70,7 @@ func LoadDefault() { executor.ApplyConfig(defaultC, true) - tun.ResetDnsRedirect() + tun.InitialResolver() } // LoadFromFile - load file @@ -91,7 +91,7 @@ func LoadFromFile(path, baseDir string) error { executor.ApplyConfig(cfg, true) - tun.ResetDnsRedirect() + tun.InitialResolver() log.Infoln("Profile " + path + " loaded") @@ -104,13 +104,13 @@ func parseConfig(data []byte, baseDir string) (*config.Config, error) { return nil, err } - raw.Experimental.Interface = "" - raw.ExternalUI = "" - raw.ExternalController = "" - raw.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT,no-resolve", tunAddress)}, raw.Rule...) - patchRawConfig(raw) + if len(raw.Proxy) == 0 && len(raw.ProxyProvider) == 0 && + len(raw.ProxyOld) == 0 && len(raw.ProxyProviderOld) == 0 { + return nil, errors.New("Empty Profile") + } + cfg, err := config.ParseRawConfig(raw, baseDir) if err != nil { return nil, err diff --git a/core/src/main/golang/profile/patch.go b/core/src/main/golang/config/patch.go similarity index 66% rename from core/src/main/golang/profile/patch.go rename to core/src/main/golang/config/patch.go index 3e1ac36..782421b 100644 --- a/core/src/main/golang/profile/patch.go +++ b/core/src/main/golang/config/patch.go @@ -1,6 +1,7 @@ -package profile +package config import ( + "fmt" "strings" "github.com/Dreamacro/clash/component/fakeip" @@ -16,6 +17,12 @@ var ( ) func patchRawConfig(rawConfig *config.RawConfig) { + rawConfig.DNS.FakeIPRange = "198.18.0.0/16" + rawConfig.Experimental.Interface = "" + rawConfig.ExternalUI = "" + rawConfig.ExternalController = "" + rawConfig.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT,no-resolve", tunAddress)}, rawConfig.Rule...) + if d := DnsPatch; d != nil { rawConfig.DNS = *d } else if d := OptionalDnsPatch; d != nil { @@ -24,13 +31,13 @@ func patchRawConfig(rawConfig *config.RawConfig) { } } - if append := NameServersAppend; len(append) > 0 { + if nameServersAppend := NameServersAppend; len(nameServersAppend) > 0 { d := &rawConfig.DNS - nameservers := make([]string, len(append)+len(d.NameServer)) - copy(nameservers, append) - copy(nameservers[len(append):], d.NameServer) + nameServers := make([]string, len(nameServersAppend)+len(d.NameServer)) + copy(nameServers, nameServersAppend) + copy(nameServers[len(nameServersAppend):], d.NameServer) - d.NameServer = nameservers + d.NameServer = nameServers } providers := rawConfig.ProxyProvider diff --git a/core/src/main/golang/go.mod b/core/src/main/golang/go.mod index 7367428..1be95ac 100644 --- a/core/src/main/golang/go.mod +++ b/core/src/main/golang/go.mod @@ -4,8 +4,7 @@ go 1.13 require ( github.com/Dreamacro/clash v0.0.0 // local -) + github.com/kr328/tun2socket v0.0.0-20200327164759-45012c372532 // indirect +) replace github.com/Dreamacro/clash => ./clash - -replace github.com/google/netstack => github.com/comzyh/netstack v0.0.0-20191217044024-67c27819ada4 diff --git a/core/src/main/golang/go.sum b/core/src/main/golang/go.sum index 5425f96..b7a660e 100644 --- a/core/src/main/golang/go.sum +++ b/core/src/main/golang/go.sum @@ -1,16 +1,17 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.5/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= -github.com/comzyh/netstack v0.0.0-20191217044024-67c27819ada4/go.mod h1:jMMWEkl1smElz5KtnrIWDHxc8gtMIO/Pd8+pLyGRzT8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/cors v1.0.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/kr328/tun2socket v0.0.0-20200327111356-c3122f7f9742/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o= +github.com/kr328/tun2socket v0.0.0-20200327164759-45012c372532 h1:IfYT+yTTvdxweErpymqCmjd3XKHgcyIIcQ+sftmZuuY= +github.com/kr328/tun2socket v0.0.0-20200327164759-45012c372532/go.mod h1:FWfSixjrLgtK+dHkDoN6lHMNhvER24gnjUZd/wt8Z9o= +github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -23,21 +24,20 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/core/src/main/golang/tun/dns.go b/core/src/main/golang/tun/dns.go new file mode 100644 index 0000000..1184232 --- /dev/null +++ b/core/src/main/golang/tun/dns.go @@ -0,0 +1,239 @@ +package tun + +import ( + "encoding/binary" + "github.com/Dreamacro/clash/component/resolver" + "github.com/Dreamacro/clash/dns" + "github.com/kr328/cfa/utils" + "github.com/kr328/tun2socket/binding" + "github.com/kr328/tun2socket/redirect" + D "github.com/miekg/dns" + "io" + "net" + "sync" + "time" +) + +const ( + defaultDNSReadTimeout = time.Second * 10 +) + +var lock sync.Mutex +var hijackAddress net.IP +var dnsHandler dns.Handler + +func setHijackAddress(hijackAddr net.IP) { + lock.Lock() + defer lock.Unlock() + + hijackAddress = hijackAddr +} + +func InitialResolver() { + lock.Lock() + defer lock.Unlock() + + rawResolver := resolver.DefaultResolver + if rawResolver == nil { + hijackAddress = nil + dnsHandler = nil + return + } + r, ok := rawResolver.(*dns.Resolver) + if !ok || r == nil { + hijackAddress = nil + dnsHandler = nil + return + } + + dnsHandler = dns.NewHandler(r) +} + +func hijackTCPDNS(conn net.Conn, endpoint *binding.Endpoint) bool { + if endpoint.Target.Port != 53 { + return false + } + + if !hijackAddress.Equal(net.IPv4zero) && !hijackAddress.Equal(net.IPv6zero) && !hijackAddress.Equal(endpoint.Target.IP) { + return false + } + + go func() { + defer utils.CloseSilent(conn) + + for { + if err := conn.SetReadDeadline(time.Now().Add(defaultDNSReadTimeout)); err != nil { + return + } + + var length uint16 + if err := binary.Read(conn, binary.BigEndian, &length); err != nil { + return + } + + data := make([]byte, length) + msg := &D.Msg{} + + if err := msg.Unpack(data); err != nil { + return + } + + handler := dnsHandler + if handler == nil { + return + } + + handler(&tcpWriter{ + conn: conn, + endpoint: endpoint, + }, msg) + } + }() + + return true +} + +func hijackDNS(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) bool { + if endpoint.Target.Port != 53 { + return false + } + + if !hijackAddress.Equal(net.IPv4zero) && !hijackAddress.Equal(net.IPv6zero) && !hijackAddress.Equal(endpoint.Target.IP) { + return false + } + + go func() { + msg := &D.Msg{} + if err := msg.Unpack(payload); err != nil { + return + } + + handler := dnsHandler + handler(&udpWriter{ + endpoint: endpoint, + sender: sender, + }, msg) + }() + + return true +} + +type tcpWriter struct { + conn net.Conn + endpoint *binding.Endpoint +} + +func (r *tcpWriter) LocalAddr() net.Addr { + return &net.TCPAddr{ + IP: r.endpoint.Target.IP, + Port: int(r.endpoint.Target.Port), + Zone: "", + } +} + +func (r *tcpWriter) RemoteAddr() net.Addr { + return &net.TCPAddr{ + IP: r.endpoint.Source.IP, + Port: int(r.endpoint.Source.Port), + Zone: "", + } +} + +func (r *tcpWriter) Write(b []byte) (int, error) { + if len(b) > 65535 { + return 0, io.ErrShortBuffer + } + + var length [2]byte + binary.BigEndian.PutUint16(length[:], uint16(len(b))) + + n, err := (&net.Buffers{length[:], b}).WriteTo(r.conn) + + return int(n), err +} + +func (r *tcpWriter) Close() error { + return nil +} + +func (r *tcpWriter) WriteMsg(d *D.Msg) error { + msg, err := d.Pack() + if err != nil { + return err + } + + _, err = r.Write(msg) + + return err +} + +func (r *tcpWriter) TsigStatus() error { + // Unsupported + return nil +} + +func (r *tcpWriter) TsigTimersOnly(bool) { + // Unsupported +} + +func (r *tcpWriter) Hijack() { + // Unsupported +} + +type udpWriter struct { + endpoint *binding.Endpoint + sender redirect.UDPSender +} + +func (r *udpWriter) LocalAddr() net.Addr { + return &net.UDPAddr{ + IP: r.endpoint.Target.IP, + Port: int(r.endpoint.Target.Port), + Zone: "", + } +} + +func (r *udpWriter) RemoteAddr() net.Addr { + return &net.UDPAddr{ + IP: r.endpoint.Source.IP, + Port: int(r.endpoint.Source.Port), + Zone: "", + } +} + +func (r *udpWriter) WriteMsg(d *D.Msg) error { + msg, err := d.Pack() + if err != nil { + return err + } + + _, err = r.Write(msg) + + return err +} + +func (r *udpWriter) Write(msg []byte) (int, error) { + ep := &binding.Endpoint{ + Source: r.endpoint.Target, + Target: r.endpoint.Source, + } + + return len(msg), r.sender(msg, ep) +} + +func (r *udpWriter) Close() error { + return nil +} + +func (r *udpWriter) TsigStatus() error { + // Unsupported + return nil +} + +func (r *udpWriter) TsigTimersOnly(bool) { + // Unsupported +} + +func (r *udpWriter) Hijack() { + // Unsupported +} diff --git a/core/src/main/golang/tun/tun.go b/core/src/main/golang/tun/tun.go index e2624d1..7cd5eca 100644 --- a/core/src/main/golang/tun/tun.go +++ b/core/src/main/golang/tun/tun.go @@ -1,36 +1,74 @@ package tun import ( - "strconv" + adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/component/socks5" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" + "github.com/kr328/tun2socket" + "github.com/kr328/tun2socket/binding" + "github.com/kr328/tun2socket/redirect" + "net" + "os" "sync" + "syscall" - "github.com/Dreamacro/clash/component/resolver" - "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/proxy/tun" ) -var tunInstance *tun.TunAdapter -var dnsAddress string +var adapter *tun2socket.Tun2Socket var mutex sync.Mutex -func StartTunDevice(fd, mtu int, dns string) error { +func StartTunDevice(fd, mtu int, gateway, mirror, dnsAddress string) error { mutex.Lock() defer mutex.Unlock() - if tunInstance != nil { - return nil + if adapter != nil { + adapter.Close() + adapter = nil } - t, err := tun.NewTunProxy("fd://" + strconv.Itoa(fd) + "?mtu=" + strconv.Itoa(mtu)) - if err != nil { - return err - } + file := os.NewFile(uintptr(fd), "/dev/tun") + _ = syscall.SetNonblock(fd, true) - tunInstance = &t - dnsAddress = dns + adapter = tun2socket.NewTun2Socket(file, mtu, net.ParseIP(gateway).To4(), net.ParseIP(mirror).To4()) - ResetDnsRedirect() + adapter.SetTCPHandler(func(conn net.Conn, endpoint *binding.Endpoint) { + if hijackTCPDNS(conn, endpoint) { + return + } + + addr := socks5.ParseAddrToSocksAddr(&net.TCPAddr{ + IP: endpoint.Target.IP, + Port: int(endpoint.Target.Port), + Zone: "", + }) + + tunnel.Add(adapters.NewSocket(addr, conn, C.SOCKS, C.TCP)) + }) + adapter.SetUDPHandler(func(payload []byte, endpoint *binding.Endpoint, sender redirect.UDPSender) { + if hijackDNS(payload, endpoint, sender) { + return + } + + addr := socks5.ParseAddrToSocksAddr(&net.TCPAddr{ + IP: endpoint.Target.IP, + Port: int(endpoint.Target.Port), + Zone: "", + }) + pkt := &udpPacket{ + payload: payload, + endpoint: endpoint, + sender: sender, + } + + tunnel.AddPacket(adapters.NewPacket(addr, pkt, C.SOCKS)) + }) + + setHijackAddress(net.ParseIP(dnsAddress)) + InitialResolver() + + adapter.Start() log.Infoln("Android tun started") @@ -41,21 +79,10 @@ func StopTunDevice() { mutex.Lock() defer mutex.Unlock() - t := tunInstance - if t == nil { - return + if adapter != nil { + adapter.Close() + adapter = nil } - (*t).Close() - tunInstance = nil - log.Infoln("Android tun stopped") -} - -func ResetDnsRedirect() { - if tunInstance == nil { - return - } - - (*tunInstance).ReCreateDNSServer(resolver.DefaultResolver.(*dns.Resolver), dnsAddress) -} +} \ No newline at end of file diff --git a/core/src/main/golang/tun/udp.go b/core/src/main/golang/tun/udp.go new file mode 100644 index 0000000..22cf2ba --- /dev/null +++ b/core/src/main/golang/tun/udp.go @@ -0,0 +1,55 @@ +package tun + +import ( + "errors" + "github.com/kr328/tun2socket/binding" + "github.com/kr328/tun2socket/redirect" + "net" +) + +type udpPacket struct { + payload []byte + endpoint *binding.Endpoint + sender redirect.UDPSender +} + +func (conn *udpPacket) Data() []byte { + return conn.payload +} + +func (conn *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) { + if addr == nil { + addr = &net.UDPAddr{ + IP: conn.endpoint.Target.IP, + Port: int(conn.endpoint.Target.Port), + Zone: "", + } + } + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errors.New("Invalid udp address") + } + + ep := &binding.Endpoint{ + Source: binding.Address{ + IP: udpAddr.IP, + Port: uint16(udpAddr.Port), + }, + Target: conn.endpoint.Source, + } + + return len(b), conn.sender(b, ep) +} + +func (conn *udpPacket) Close() error { + return nil +} + +func (conn *udpPacket) LocalAddr() net.Addr { + return &net.UDPAddr{ + IP: conn.endpoint.Source.IP, + Port: int(conn.endpoint.Source.Port), + Zone: "", + } +} diff --git a/core/src/main/golang/utils/close.go b/core/src/main/golang/utils/close.go new file mode 100644 index 0000000..1e025ef --- /dev/null +++ b/core/src/main/golang/utils/close.go @@ -0,0 +1,7 @@ +package utils + +import "io" + +func CloseSilent(closer io.Closer) { + _ = closer.Close() +} diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index f52dc5f..a9c4562 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -46,11 +46,13 @@ object Clash { fun startTunDevice( fd: Int, mtu: Int, + gateway: String, + mirror: String, dns: String, onNewSocket: (Int) -> Boolean, onTunStop: () -> Unit ) { - Bridge.startTunDevice(fd.toLong(), mtu.toLong(), dns, object : TunCallback { + Bridge.startTunDevice(fd.toLong(), mtu.toLong(), gateway, mirror, dns, object : TunCallback { override fun onCreateSocket(fd: Long) { onNewSocket(fd.toInt()) } diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt index 9069e14..851d58a 100644 --- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt +++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt @@ -15,9 +15,12 @@ class TunService : VpnService(), CoroutineScope by MainScope() { // from https://github.com/shadowsocks/shadowsocks-android/blob/master/core/src/main/java/com/github/shadowsocks/bg/VpnService.kt private const val VPN_MTU = 1500 private const val PRIVATE_VLAN4_SUBNET = 30 + private const val PRIVATE_VLAN6_SUBNET = 126 private const val PRIVATE_VLAN4_CLIENT = "172.31.255.253" private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1" - private const val PRIVATE_VLAN_DNS = "172.31.255.254" + private const val PRIVATE_VLAN4_MIRROR = "172.31.255.254" + private const val PRIVATE_VLAN6_MIRROR = "fdfe:dcba:9876::2" + private const val PRIVATE_VLAN_DNS = "198.18.0.1" private const val VLAN4_ANY = "0.0.0.0" } @@ -38,17 +41,16 @@ class TunService : VpnService(), CoroutineScope by MainScope() { .establish() ?: throw NullPointerException("Unable to create VPN Service") - val dnsAddress = - if (settings.get(ServiceSettings.DNS_HIJACKING)) - "$VLAN4_ANY:53" - else - "$PRIVATE_VLAN_DNS:53" + val dnsAddress = if (settings.get(ServiceSettings.DNS_HIJACKING)) VLAN4_ANY else PRIVATE_VLAN_DNS Log.i("TunService.startTun ${fd.fd}") Clash.setDnsOverrideEnabled(settings.get(ServiceSettings.OVERRIDE_DNS)) - Clash.startTunDevice(fd.fd, VPN_MTU, dnsAddress, this::protect, this::stopSelf) + Clash.startTunDevice(fd.detachFd(), VPN_MTU, + PRIVATE_VLAN4_CLIENT, PRIVATE_VLAN4_MIRROR, + dnsAddress, + this::protect, this::stopSelf) fd.close() } @@ -182,7 +184,7 @@ class TunService : VpnService(), CoroutineScope by MainScope() { addAddress(PRIVATE_VLAN4_CLIENT, PRIVATE_VLAN4_SUBNET) if (settings.get(ServiceSettings.IPV6_SUPPORT)) - addAddress(PRIVATE_VLAN6_CLIENT, 126) + addAddress(PRIVATE_VLAN6_CLIENT, PRIVATE_VLAN6_SUBNET) return this }