Android: patch

This commit is contained in:
wwqgtxx 2023-10-23 16:50:43 +08:00
parent 79a29826a4
commit a551d7ebd9
30 changed files with 512 additions and 59 deletions

View File

@ -12,7 +12,7 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions
metadata.Type = source
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
if p, ok := packet.(C.UDPPacketInAddr); ok {
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
ApplyAdditions(metadata, WithInAddr(p.InAddr()), WithDstAddr(metadata.RawDstAddr))
}
ApplyAdditions(metadata, additions...)

View File

@ -0,0 +1,7 @@
package outbound
import "net"
func (c *conn) RawConn() (net.Conn, bool) {
return c.ExtendedConn, true
}

View File

@ -0,0 +1,62 @@
package outboundgroup
import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type ProxyGroup interface {
C.ProxyAdapter
Providers() []provider.ProxyProvider
Proxies() []C.Proxy
Now() string
}
func (f *Fallback) Providers() []provider.ProxyProvider {
return f.providers
}
func (lb *LoadBalance) Providers() []provider.ProxyProvider {
return lb.providers
}
func (f *Fallback) Proxies() []C.Proxy {
return f.GetProxies(false)
}
func (lb *LoadBalance) Proxies() []C.Proxy {
return lb.GetProxies(false)
}
func (lb *LoadBalance) Now() string {
return ""
}
func (r *Relay) Providers() []provider.ProxyProvider {
return r.providers
}
func (r *Relay) Proxies() []C.Proxy {
return r.GetProxies(false)
}
func (r *Relay) Now() string {
return ""
}
func (s *Selector) Providers() []provider.ProxyProvider {
return s.providers
}
func (s *Selector) Proxies() []C.Proxy {
return s.GetProxies(false)
}
func (u *URLTest) Providers() []provider.ProxyProvider {
return u.providers
}
func (u *URLTest) Proxies() []C.Proxy {
return u.GetProxies(false)
}

View File

@ -18,6 +18,7 @@ import (
const (
defaultURLTestTimeout = time.Second * 5
defaultURLTestURL = "https://www.gstatic.com/generate_204"
)
type HealthCheckOption struct {
@ -148,6 +149,11 @@ func (hc *HealthCheck) stop() {
}
func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 {
return
}
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String()
log.Debugln("Start New Health Checking {%s}", id)
@ -223,6 +229,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
if len(url) == 0 {
interval = 0
expectedStatus = nil
url = defaultURLTestURL
}
return &HealthCheck{

View File

@ -27,7 +27,7 @@ type healthCheckSchema struct {
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path,omitempty"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
@ -59,23 +59,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
hcInterval = uint(schema.HealthCheck.Interval)
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus)
path := C.Path.Resolve(schema.Path)
var vehicle types.Vehicle
switch schema.Type {
case "file":
path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path)
case "http":
if schema.Path != "" {
path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("proxies", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path)
default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}

34
adapter/provider/patch.go Normal file
View File

@ -0,0 +1,34 @@
package provider
import (
"time"
)
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (pp *proxySetProvider) UpdatedAt() time.Time {
return pp.Fetcher.UpdatedAt
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
pp.Fetcher.Destroy()
return nil
}
func (cp *compatibleProvider) Close() error {
cp.healthCheck.close()
return nil
}
func Suspend(s bool) {
suspended = s
}

11
common/net/patch.go Normal file
View File

@ -0,0 +1,11 @@
package net
import "net"
func (c *BufferedConn) RawConn() (net.Conn, bool) {
if c.r.Buffered() == 0 {
return c.ExtendedConn, true
}
return nil, false
}

View File

@ -70,6 +70,9 @@ func DialContext(ctx context.Context, network, address string, options ...Option
}
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
if DefaultSocketHook != nil {
return listenPacketHooked(ctx, network, address)
}
cfg := applyOptions(options...)
lc := &net.ListenConfig{}
@ -110,6 +113,9 @@ func GetTcpConcurrent() bool {
}
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
if DefaultSocketHook != nil {
return dialContextHooked(ctx, network, destination, port)
}
address := net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer

47
component/dialer/patch.go Normal file
View File

@ -0,0 +1,47 @@
package dialer
import (
"context"
"net"
"net/netip"
"syscall"
)
type TunnelDialer func(context context.Context, network, address string) (net.Conn, error)
type SocketControl func(network, address string, conn syscall.RawConn) error
var DefaultTunnelDialer TunnelDialer
var DefaultSocketHook SocketControl
func DialTunnelContext(ctx context.Context, network, address string) (net.Conn, error) {
if dialer := DefaultTunnelDialer; dialer != nil {
return dialer(ctx, network, address)
}
return DialContext(ctx, network, address)
}
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
dialer := &net.Dialer{
Control: DefaultSocketHook,
}
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
if err != nil {
return nil, err
}
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
return conn, nil
}
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
lc := &net.ListenConfig{
Control: DefaultSocketHook,
}
return lc.ListenPacket(ctx, network, address)
}

View File

@ -54,7 +54,7 @@ func Verify() bool {
return err == nil
}
func Instance() Reader {
func DefaultInstance() Reader {
once.Do(func() {
mmdbPath := C.Path.MMDB()
log.Debugln("Load MMDB file: %s", mmdbPath)

31
component/mmdb/patch.go Normal file
View File

@ -0,0 +1,31 @@
package mmdb
import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/oschwald/geoip2-golang"
"github.com/oschwald/maxminddb-golang"
)
var overrideMmdb *geoip2.Reader
func InstallOverride(override *geoip2.Reader) {
overrideMmdb = overrideMmdb
}
func Instance() Reader {
once.Do(func() {
mmdb, err := maxminddb.Open(C.Path.MMDB())
if err != nil {
log.Fatalln("Can't load mmdb: %s", err.Error())
}
reader = Reader{Reader: mmdb}
if mmdb.Metadata.DatabaseType == "sing-geoip" {
reader.databaseType = typeSing
} else {
reader.databaseType = typeMaxmind
}
})
return reader
}

View File

@ -0,0 +1,15 @@
package process
import "github.com/Dreamacro/clash/constant"
type PackageNameResolver func(metadata *constant.Metadata) (string, error)
var DefaultPackageNameResolver PackageNameResolver
func FindPackageName(metadata *constant.Metadata) (string, error) {
if resolver := DefaultPackageNameResolver; resolver != nil {
return resolver(metadata)
}
return "", ErrPlatformNotSupport
}

View File

@ -13,6 +13,10 @@ import (
"github.com/samber/lo"
)
const (
minInterval = time.Minute * 5
)
var (
fileMode os.FileMode = 0o666
dirMode os.FileMode = 0o755
@ -24,8 +28,7 @@ type Fetcher[V any] struct {
resourceType string
name string
vehicle types.Vehicle
UpdatedAt *time.Time
ticker *time.Ticker
UpdatedAt time.Time
done chan struct{}
hash [16]byte
parser Parser[V]
@ -56,7 +59,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil {
buf, err = os.ReadFile(f.vehicle.Path())
modTime := stat.ModTime()
f.UpdatedAt = &modTime
f.UpdatedAt = modTime
isLocal = true
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name())
@ -64,6 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
}
} else {
buf, err = f.vehicle.Read()
f.UpdatedAt = time.Now()
}
if err != nil {
@ -113,7 +117,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
f.hash = md5.Sum(buf)
// pull contents automatically
if f.ticker != nil {
if f.interval > 0 {
go f.pullLoop()
}
@ -129,7 +133,7 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) {
f.UpdatedAt = &now
f.UpdatedAt = now
_ = os.Chtimes(f.vehicle.Path(), now, now)
return lo.Empty[V](), true, nil
}
@ -145,23 +149,31 @@ func (f *Fetcher[V]) Update() (V, bool, error) {
}
}
f.UpdatedAt = &now
f.UpdatedAt = now
f.hash = hash
return contents, false, nil
}
func (f *Fetcher[V]) Destroy() error {
if f.ticker != nil {
if f.interval > 0 {
f.done <- struct{}{}
}
return nil
}
func (f *Fetcher[V]) pullLoop() {
initialInterval := f.interval - time.Since(f.UpdatedAt)
if initialInterval < minInterval {
initialInterval = minInterval
}
timer := time.NewTimer(initialInterval)
defer timer.Stop()
for {
select {
case <-f.ticker.C:
case <-timer.C:
timer.Reset(f.interval)
elm, same, err := f.Update()
if err != nil {
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
@ -178,7 +190,6 @@ func (f *Fetcher[V]) pullLoop() {
f.OnUpdate(elm)
}
case <-f.done:
f.ticker.Stop()
return
}
}
@ -197,17 +208,12 @@ func safeWrite(path string, buf []byte) error {
}
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
return &Fetcher[V]{
name: name,
ticker: ticker,
vehicle: vehicle,
parser: parser,
done: make(chan struct{}, 1),
done: make(chan struct{}, 8),
OnUpdate: onUpdate,
interval: interval,
}

View File

@ -59,7 +59,6 @@ type General struct {
TCPConcurrent bool `json:"tcp-concurrent"`
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
Sniffing bool `json:"sniffing"`
EBpf EBpf `json:"-"`
GlobalClientFingerprint string `json:"global-client-fingerprint"`
GlobalUA string `json:"global-ua"`
}
@ -212,11 +211,16 @@ type RawDNS struct {
}
type RawFallbackFilter struct {
GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"`
IPCIDR []string `yaml:"ipcidr"`
Domain []string `yaml:"domain"`
GeoSite []string `yaml:"geosite"`
GeoIP bool `yaml:"geoip" json:"geoip"`
GeoIPCode string `yaml:"geoip-code" json:"geoip-code"`
IPCIDR []string `yaml:"ipcidr" json:"ipcidr"`
Domain []string `yaml:"domain" json:"domain"`
GeoSite []string `yaml:"geosite" json:"geosite"`
}
type RawClashForAndroid struct {
AppendSystemDNS bool `yaml:"append-system-dns" json:"append-system-dns"`
UiSubtitlePattern string `yaml:"ui-subtitle-pattern" json:"ui-subtitle-pattern"`
}
type RawTun struct {
@ -482,7 +486,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
startTime := time.Now()
config.Experimental = &rawCfg.Experimental
config.Profile = &rawCfg.Profile
config.IPTables = &rawCfg.IPTables
config.TLS = &rawCfg.RawTLS
general, err := parseGeneral(rawCfg)
@ -543,11 +546,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.DNS = dnsCfg
err = parseTun(rawCfg.Tun, config.General)
if err != nil {
return nil, err
}
err = parseTuicServer(rawCfg.TuicServer, config.General)
if err != nil {
return nil, err
@ -644,7 +642,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode,
EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
GlobalUA: cfg.GlobalUA,
}, nil

View File

@ -2,7 +2,7 @@ package constant
var (
GeodataMode bool
GeoIpUrl string
MmdbUrl string
GeoSiteUrl string
GeoIpUrl = "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat"
MmdbUrl = "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb"
GeoSiteUrl = "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat"
)

View File

@ -147,6 +147,9 @@ type Metadata struct {
SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"`
RawSrcAddr net.Addr `json:"-"`
RawDstAddr net.Addr `json:"-"`
// Only domain rule
SniffHost string `json:"sniffHost"`
}

11
constant/patch.go Normal file
View File

@ -0,0 +1,11 @@
package constant
import "net"
type WrappedConn interface {
RawConn() (net.Conn, bool)
}
type WrappedPacketConn interface {
RawPacketConn() (net.PacketConn, bool)
}

7
context/patch.go Normal file
View File

@ -0,0 +1,7 @@
package context
import "net"
func (c *ConnContext) RawConn() (net.Conn, bool) {
return c.conn, true
}

View File

@ -1,3 +1,6 @@
//go:build disabled
// +build disabled
package dns
import (

79
dns/patch.go Normal file
View File

@ -0,0 +1,79 @@
package dns
import (
"context"
D "github.com/miekg/dns"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/resolver"
)
const SystemDNSPlaceholder = "system"
var systemResolver *Resolver
var isolateHandler handler
var _ dnsClient = (*dhcpClient)(nil)
type dhcpClient struct {
enable bool
}
func (d *dhcpClient) Address() string {
return SystemDNSPlaceholder
}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return d.ExchangeContext(context.Background(), m)
}
func (d *dhcpClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if s := systemResolver; s != nil {
return s.ExchangeContext(ctx, m)
}
return nil, dhcp.ErrNotFound
}
func ServeDNSWithDefaultServer(msg *D.Msg) (*D.Msg, error) {
if h := isolateHandler; h != nil {
return handlerWithContext(context.Background(), h, msg)
}
return nil, D.ErrTime
}
func FlushCacheWithDefaultResolver() {
if r := resolver.DefaultResolver; r != nil {
r.(*Resolver).lruCache = cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true))
}
}
func UpdateSystemDNS(addr []string) {
if len(addr) == 0 {
systemResolver = nil
}
ns := make([]NameServer, 0, len(addr))
for _, d := range addr {
ns = append(ns, NameServer{Addr: d})
}
systemResolver = NewResolver(Config{Main: ns})
}
func UpdateIsolateHandler(resolver *Resolver, mapper *ResolverEnhancer) {
if resolver == nil {
isolateHandler = nil
return
}
isolateHandler = NewHandler(resolver, mapper)
}
func newDHCPClient(ifaceName string) *dhcpClient {
return &dhcpClient{enable: ifaceName == SystemDNSPlaceholder}
}

View File

@ -49,6 +49,7 @@ func (s *Server) SetHandler(handler handler) {
}
func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
UpdateIsolateHandler(resolver, mapper)
if addr == address && resolver != nil {
handler := NewHandler(resolver, mapper)
server.SetHandler(handler)

View File

@ -172,7 +172,7 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
listener.ReCreateHTTP(general.Port, tunnel.Tunnel)
listener.ReCreateSocks(general.SocksPort, tunnel.Tunnel)
listener.ReCreateRedir(general.RedirPort, tunnel.Tunnel)
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
// listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tunnel.Tunnel)
listener.ReCreateTProxy(general.TProxyPort, tunnel.Tunnel)
listener.ReCreateMixed(general.MixedPort, tunnel.Tunnel)
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)

7
listener/http/patch.go Normal file
View File

@ -0,0 +1,7 @@
package http
import "net"
func (l *Listener) Listener() net.Listener {
return l.listener
}

View File

@ -65,6 +65,9 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi
}
continue
}
if t, ok := conn.(*net.TCPConn); ok {
t.SetKeepAlive(false)
}
go HandleConn(conn, tunnel, c, additions...)
}
}()

View File

@ -18,7 +18,7 @@ var (
type ruleProviderSchema struct {
Type string `provider:"type"`
Behavior string `provider:"behavior"`
Path string `provider:"path,omitempty"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Format string `provider:"format,omitempty"`
Interval int `provider:"interval,omitempty"`
@ -54,23 +54,13 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
return nil, fmt.Errorf("unsupported format type: %s", schema.Format)
}
path := C.Path.Resolve(schema.Path)
var vehicle P.Vehicle
switch schema.Type {
case "file":
path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path)
case "http":
if schema.Path != "" {
path := C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("rules", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
}
vehicle = resource.NewHTTPVehicle(schema.URL, path)
default:
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
}

25
rules/provider/patch.go Normal file
View File

@ -0,0 +1,25 @@
package provider
import "time"
var (
suspended bool
)
type UpdatableProvider interface {
UpdatedAt() time.Time
}
func (f *ruleSetProvider) UpdatedAt() time.Time {
return f.Fetcher.UpdatedAt
}
func (rp *ruleSetProvider) Close() error {
rp.Fetcher.Destroy()
return nil
}
func Suspend(s bool) {
suspended = s
}

View File

@ -83,5 +83,11 @@ func closeAllLocalCoon(lAddr string) {
}
func handleSocket(ctx C.ConnContext, outbound net.Conn) {
left := unwrap(ctx.Conn())
right := unwrap(outbound)
if relayHijack(left, right) {
return
}
N.Relay(ctx.Conn(), outbound)
}

73
tunnel/patch.go Normal file
View File

@ -0,0 +1,73 @@
package tunnel
import (
"net"
C "github.com/Dreamacro/clash/constant"
)
func relayHijack(left net.Conn, right net.Conn) bool {
var l *net.TCPConn
var r *net.TCPConn
var ok bool
if l, ok = left.(*net.TCPConn); !ok {
return false
}
if r, ok = right.(*net.TCPConn); !ok {
return false
}
closed := make(chan struct{})
go func() {
defer close(closed)
r.ReadFrom(l)
r.Close()
}()
l.ReadFrom(r)
l.Close()
<-closed
return true
}
func unwrap(conn net.Conn) net.Conn {
r := conn
for {
w, ok := r.(C.WrappedConn)
if !ok {
break
}
rc, ok := w.RawConn()
if !ok {
break
}
r = rc
}
return r
}
func unwrapPacket(conn net.PacketConn) net.PacketConn {
r := conn
for {
w, ok := r.(C.WrappedPacketConn)
if !ok {
break
}
rc, ok := w.RawPacketConn()
if !ok {
break
}
r = rc
}
return r
}

25
tunnel/statistic/patch.go Normal file
View File

@ -0,0 +1,25 @@
package statistic
import (
"net"
)
func (m *Manager) Total() (up, down int64) {
return m.uploadTotal.Load(), m.downloadTotal.Load()
}
func (tt *tcpTracker) RawConn() (net.Conn, bool) {
if tt.Chain.Last() == "DIRECT" {
return tt.Conn, true
}
return nil, false
}
func (ut *udpTracker) RawPacketConn() (net.PacketConn, bool) {
if ut.Chain.Last() == "DIRECT" {
return ut.PacketConn, true
}
return nil, false
}

View File

@ -47,6 +47,8 @@ var (
findProcessMode P.FindProcessMode
fakeIPRange netip.Prefix
procesCache string
)
type tunnel struct{}
@ -627,6 +629,10 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
metadata.Process = filepath.Base(path)
metadata.ProcessPath = path
metadata.Uid = uid
if procesCache != metadata.Process {
log.Debugln("[Process] %s from process %s", metadata.String(), path)
}
procesCache = metadata.Process
}
}