Update On Wed Aug 28 20:33:34 CEST 2024

This commit is contained in:
github-action[bot]
2024-08-28 20:33:34 +02:00
parent 66248b55b9
commit 891d5cb988
424 changed files with 59564 additions and 7914 deletions

1
.github/update.log vendored
View File

@@ -747,3 +747,4 @@ Update On Sat Aug 24 20:31:41 CEST 2024
Update On Sun Aug 25 20:30:10 CEST 2024
Update On Mon Aug 26 20:34:29 CEST 2024
Update On Tue Aug 27 20:32:11 CEST 2024
Update On Wed Aug 28 20:33:23 CEST 2024

View File

@@ -46,6 +46,14 @@ func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
return set.ToIPSet().Contains(ip.WithZone(""))
}
// MatchIp implements C.IpMatcher
func (set *IpCidrSet) MatchIp(ip netip.Addr) bool {
if set.IsEmpty() {
return false
}
return set.IsContain(ip)
}
func (set *IpCidrSet) Merge() error {
var b netipx.IPSetBuilder
b.AddSet(set.ToIPSet())

View File

@@ -35,7 +35,7 @@ type Pool struct {
offset netip.Addr
cycle bool
mux sync.Mutex
host []C.Rule
host []C.DomainMatcher
ipnet netip.Prefix
store store
}
@@ -66,8 +66,8 @@ func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
// ShouldSkipped return if domain should be skipped
func (p *Pool) ShouldSkipped(domain string) bool {
for _, rule := range p.host {
if match, _ := rule.Match(&C.Metadata{Host: domain}); match {
for _, matcher := range p.host {
if matcher.MatchDomain(domain) {
return true
}
}
@@ -156,7 +156,7 @@ func (p *Pool) restoreState() {
type Options struct {
IPNet netip.Prefix
Host []C.Rule
Host []C.DomainMatcher
// Size sets the maximum number of entries in memory
// and does not work if Persistence is true

View File

@@ -10,7 +10,6 @@ import (
"github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
RP "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/bbolt"
"github.com/stretchr/testify/assert"
@@ -157,7 +156,7 @@ func TestPool_Skip(t *testing.T) {
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
Size: 10,
Host: []C.Rule{RP.NewDomainSet(tree.NewDomainSet(), "")},
Host: []C.DomainMatcher{tree.NewDomainSet()},
})
assert.Nil(t, err)
defer os.Remove(tempfile)

View File

@@ -22,23 +22,23 @@ var (
type Dispatcher struct {
enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain []C.Rule
skipSrcAddress []C.Rule
skipDstAddress []C.Rule
skipDomain []C.Rule
forceDomain []C.DomainMatcher
skipSrcAddress []C.IpMatcher
skipDstAddress []C.IpMatcher
skipDomain []C.DomainMatcher
skipList *lru.LruCache[netip.AddrPort, uint8]
forceDnsMapping bool
parsePureIp bool
}
func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
for _, rule := range sd.skipDstAddress {
if ok, _ := rule.Match(&C.Metadata{DstIP: metadata.DstIP}); ok {
for _, matcher := range sd.skipDstAddress {
if matcher.MatchIp(metadata.DstIP) {
return false
}
}
for _, rule := range sd.skipSrcAddress {
if ok, _ := rule.Match(&C.Metadata{DstIP: metadata.SrcIP}); ok {
for _, matcher := range sd.skipSrcAddress {
if matcher.MatchIp(metadata.SrcIP) {
return false
}
}
@@ -48,8 +48,8 @@ func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
return true
}
for _, rule := range sd.forceDomain {
if ok, _ := rule.Match(&C.Metadata{Host: metadata.Host}); ok {
for _, matcher := range sd.forceDomain {
if matcher.MatchDomain(metadata.Host) {
return true
}
}
@@ -112,8 +112,8 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
return false
}
for _, rule := range sd.skipDomain {
if ok, _ := rule.Match(&C.Metadata{Host: host}); ok {
for _, matcher := range sd.skipDomain {
if matcher.MatchDomain(host) {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return false
}
@@ -200,10 +200,10 @@ func (sd *Dispatcher) cacheSniffFailed(metadata *C.Metadata) {
type Config struct {
Enable bool
Sniffers map[sniffer.Type]SnifferConfig
ForceDomain []C.Rule
SkipSrcAddress []C.Rule
SkipDstAddress []C.Rule
SkipDomain []C.Rule
ForceDomain []C.DomainMatcher
SkipSrcAddress []C.IpMatcher
SkipDstAddress []C.IpMatcher
SkipDomain []C.DomainMatcher
ForceDnsMapping bool
ParsePureIp bool
}

View File

@@ -172,6 +172,11 @@ func (ss *DomainSet) Foreach(f func(key string) bool) {
})
}
// MatchDomain implements C.DomainMatcher
func (ss *DomainSet) MatchDomain(domain string) bool {
return ss.Has(domain)
}
func setBit(bm *[]uint64, i int, v int) {
for i>>6 >= len(*bm) {
*bm = append(*bm, 0)

View File

@@ -143,8 +143,8 @@ type DNS struct {
UseSystemHosts bool
NameServer []dns.NameServer
Fallback []dns.NameServer
FallbackIPFilter []C.Rule
FallbackDomainFilter []C.Rule
FallbackIPFilter []C.IpMatcher
FallbackDomainFilter []C.DomainMatcher
Listen string
EnhancedMode C.DNSMode
DefaultNameserver []dns.NameServer
@@ -640,7 +640,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.Hosts = hosts
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
dnsCfg, err := parseDNS(rawCfg, hosts, ruleProviders)
if err != nil {
return nil, err
}
@@ -1297,7 +1297,7 @@ func parsePureDNSServer(server string) string {
}
}
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) {
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) {
var policy []dns.Policy
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
@@ -1350,18 +1350,18 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules [
if strings.HasPrefix(domain, "rule-set:") {
domainSetName := domain[9:]
rule, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders)
matcher, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders)
if err != nil {
return nil, err
}
policy[idx] = dns.Policy{Rule: rule, NameServers: nameservers}
policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers}
} else if strings.HasPrefix(domain, "geosite:") {
country := domain[8:]
rule, err := RC.NewGEOSITE(country, "dns.nameserver-policy")
matcher, err := RC.NewGEOSITE(country, "dns.nameserver-policy")
if err != nil {
return nil, err
}
policy[idx] = dns.Policy{Rule: rule, NameServers: nameservers}
policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers}
} else {
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
@@ -1372,7 +1372,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules [
return policy, nil
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@@ -1400,7 +1400,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err
}
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, rules, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
return nil, err
}
@@ -1467,14 +1467,13 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
dnsCfg.FakeIPRange = pool
}
var rule C.Rule
if len(cfg.Fallback) != 0 {
if cfg.FallbackFilter.GeoIP {
rule, err = RC.NewGEOIP(cfg.FallbackFilter.GeoIPCode, "dns.fallback-filter.geoip", false, true)
matcher, err := RC.NewGEOIP(cfg.FallbackFilter.GeoIPCode, "dns.fallback-filter.geoip", false, true)
if err != nil {
return nil, fmt.Errorf("load GeoIP dns fallback filter error, %w", err)
}
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, matcher)
}
if len(cfg.FallbackFilter.IPCIDR) > 0 {
cidrSet := cidr.NewIpCidrSet()
@@ -1488,8 +1487,8 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
if err != nil {
return nil, err
}
rule = RP.NewIpCidrSet(cidrSet, "dns.fallback-filter.ipcidr")
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
matcher := cidrSet // dns.fallback-filter.ipcidr
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, matcher)
}
if len(cfg.FallbackFilter.Domain) > 0 {
domainTrie := trie.New[struct{}]()
@@ -1499,17 +1498,17 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, fmt.Errorf("DNS FallbackDomain[%d] format error: %w", idx, err)
}
}
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), "dns.fallback-filter.domain")
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, rule)
matcher := domainTrie.NewDomainSet() // dns.fallback-filter.domain
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, matcher)
}
if len(cfg.FallbackFilter.GeoSite) > 0 {
log.Warnln("replace fallback-filter.geosite with nameserver-policy, it will be removed in the future")
for idx, geoSite := range cfg.FallbackFilter.GeoSite {
rule, err = RC.NewGEOSITE(geoSite, "dns.fallback-filter.geosite")
matcher, err := RC.NewGEOSITE(geoSite, "dns.fallback-filter.geosite")
if err != nil {
return nil, fmt.Errorf("DNS FallbackGeosite[%d] format error: %w", idx, err)
}
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, rule)
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, matcher)
}
}
}
@@ -1701,8 +1700,8 @@ func parseSniffer(snifferRaw RawSniffer, ruleProviders map[string]providerTypes.
return snifferConfig, nil
}
func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (ipRules []C.Rule, err error) {
var rule C.Rule
func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (matchers []C.IpMatcher, err error) {
var matcher C.IpMatcher
for _, ipcidr := range addresses {
ipcidrLower := strings.ToLower(ipcidr)
if strings.Contains(ipcidrLower, "geoip:") {
@@ -1710,22 +1709,22 @@ func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, country := range subkeys {
rule, err = RC.NewGEOIP(country, adapterName, false, false)
matcher, err = RC.NewGEOIP(country, adapterName, false, false)
if err != nil {
return nil, err
}
ipRules = append(ipRules, rule)
matchers = append(matchers, matcher)
}
} else if strings.Contains(ipcidrLower, "rule-set:") {
subkeys := strings.Split(ipcidr, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, domainSetName := range subkeys {
rule, err = parseIPRuleSet(domainSetName, adapterName, ruleProviders)
matcher, err = parseIPRuleSet(domainSetName, adapterName, ruleProviders)
if err != nil {
return nil, err
}
ipRules = append(ipRules, rule)
matchers = append(matchers, matcher)
}
} else {
if cidrSet == nil {
@@ -1742,14 +1741,14 @@ func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string
if err != nil {
return nil, err
}
rule = RP.NewIpCidrSet(cidrSet, adapterName)
ipRules = append(ipRules, rule)
matcher = cidrSet
matchers = append(matchers, matcher)
}
return
}
func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (domainRules []C.Rule, err error) {
var rule C.Rule
func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (matchers []C.DomainMatcher, err error) {
var matcher C.DomainMatcher
for _, domain := range domains {
domainLower := strings.ToLower(domain)
if strings.Contains(domainLower, "geosite:") {
@@ -1757,22 +1756,22 @@ func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapte
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, country := range subkeys {
rule, err = RC.NewGEOSITE(country, adapterName)
matcher, err = RC.NewGEOSITE(country, adapterName)
if err != nil {
return nil, err
}
domainRules = append(domainRules, rule)
matchers = append(matchers, matcher)
}
} else if strings.Contains(domainLower, "rule-set:") {
subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, domainSetName := range subkeys {
rule, err = parseDomainRuleSet(domainSetName, adapterName, ruleProviders)
matcher, err = parseDomainRuleSet(domainSetName, adapterName, ruleProviders)
if err != nil {
return nil, err
}
domainRules = append(domainRules, rule)
matchers = append(matchers, matcher)
}
} else {
if domainTrie == nil {
@@ -1785,13 +1784,13 @@ func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapte
}
}
if !domainTrie.IsEmpty() {
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), adapterName)
domainRules = append(domainRules, rule)
matcher = domainTrie.NewDomainSet()
matchers = append(matchers, matcher)
}
return
}
func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.Rule, error) {
func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.IpMatcher, error) {
if rp, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {
@@ -1806,7 +1805,7 @@ func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[
return RP.NewRuleSet(domainSetName, adapterName, true)
}
func parseDomainRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.Rule, error) {
func parseDomainRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) {
if rp, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {

View File

@@ -0,0 +1,11 @@
package constant
import "net/netip"
type DomainMatcher interface {
MatchDomain(domain string) bool
}
type IpMatcher interface {
MatchIp(ip netip.Addr) bool
}

View File

@@ -133,7 +133,9 @@ type Metadata struct {
Type Type `json:"type"`
SrcIP netip.Addr `json:"sourceIP"`
DstIP netip.Addr `json:"destinationIP"`
SrcGeoIP []string `json:"sourceGeoIP"` // can be nil if never queried, empty slice if got no result
DstGeoIP []string `json:"destinationGeoIP"` // can be nil if never queried, empty slice if got no result
SrcIPASN string `json:"sourceIPASN"`
DstIPASN string `json:"destinationIPASN"`
SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output
DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output

View File

@@ -27,8 +27,6 @@ const (
ProcessNameRegex
ProcessPathRegex
RuleSet
DomainSet
IpCidrSet
Network
Uid
SubRules
@@ -92,10 +90,6 @@ func (rt RuleType) String() string {
return "Match"
case RuleSet:
return "RuleSet"
case DomainSet:
return "DomainSet"
case IpCidrSet:
return "IpCidrSet"
case Network:
return "Network"
case DSCP:

View File

@@ -21,13 +21,13 @@ func (p domainTriePolicy) Match(domain string) []dnsClient {
return nil
}
type domainRulePolicy struct {
rule C.Rule
type domainMatcherPolicy struct {
matcher C.DomainMatcher
dnsClients []dnsClient
}
func (p domainRulePolicy) Match(domain string) []dnsClient {
if ok, _ := p.rule.Match(&C.Metadata{Host: domain}); ok {
func (p domainMatcherPolicy) Match(domain string) []dnsClient {
if p.matcher.MatchDomain(domain) {
return p.dnsClients
}
return nil

View File

@@ -42,8 +42,8 @@ type Resolver struct {
hosts *trie.DomainTrie[resolver.HostValue]
main []dnsClient
fallback []dnsClient
fallbackDomainFilters []C.Rule
fallbackIPFilters []C.Rule
fallbackDomainFilters []C.DomainMatcher
fallbackIPFilters []C.IpMatcher
group singleflight.Group[*D.Msg]
cache dnsCache
policy []dnsPolicy
@@ -119,7 +119,7 @@ func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, e
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
for _, filter := range r.fallbackIPFilters {
if ok, _ := filter.Match(&C.Metadata{DstIP: ip}); ok {
if filter.MatchIp(ip) {
return true
}
}
@@ -275,7 +275,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
}
for _, df := range r.fallbackDomainFilters {
if ok, _ := df.Match(&C.Metadata{Host: domain}); ok {
if df.MatchDomain(domain) {
return true
}
}
@@ -398,7 +398,7 @@ func (ns NameServer) Equal(ns2 NameServer) bool {
type Policy struct {
Domain string
Rule C.Rule
Matcher C.DomainMatcher
NameServers []NameServer
}
@@ -409,8 +409,8 @@ type Config struct {
IPv6 bool
IPv6Timeout uint
EnhancedMode C.DNSMode
FallbackIPFilter []C.Rule
FallbackDomainFilter []C.Rule
FallbackIPFilter []C.IpMatcher
FallbackDomainFilter []C.DomainMatcher
Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue]
Policy []Policy
@@ -495,8 +495,8 @@ func NewResolver(config Config) *Resolver {
}
for _, policy := range config.Policy {
if policy.Rule != nil {
insertPolicy(domainRulePolicy{rule: policy.Rule, dnsClients: cacheTransform(policy.NameServers)})
if policy.Matcher != nil {
insertPolicy(domainMatcherPolicy{matcher: policy.Matcher, dnsClients: cacheTransform(policy.NameServers)})
} else {
if triePolicy == nil {
triePolicy = trie.New[[]dnsClient]()

View File

@@ -3,6 +3,7 @@ package common
import (
"errors"
"fmt"
"net/netip"
"strings"
"github.com/metacubex/mihomo/component/geodata"
@@ -11,6 +12,8 @@ import (
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"golang.org/x/exp/slices"
)
type GEOIP struct {
@@ -41,52 +44,84 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
}
if g.country == "lan" {
return g.isLan(ip), g.adapter
}
if C.GeodataMode {
if g.isSourceIP {
if slices.Contains(metadata.SrcGeoIP, g.country) {
return true, g.adapter
}
} else {
if slices.Contains(metadata.DstGeoIP, g.country) {
return true, g.adapter
}
}
matcher, err := g.getIPMatcher()
if err != nil {
return false, ""
}
match := matcher.Match(ip)
if match {
if g.isSourceIP {
metadata.SrcGeoIP = append(metadata.SrcGeoIP, g.country)
} else {
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
}
}
return match, g.adapter
}
if g.isSourceIP {
if metadata.SrcGeoIP != nil {
return slices.Contains(metadata.SrcGeoIP, g.country), g.adapter
}
} else {
if metadata.DstGeoIP != nil {
return slices.Contains(metadata.DstGeoIP, g.country), g.adapter
}
}
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
if g.isSourceIP {
metadata.SrcGeoIP = codes
} else {
metadata.DstGeoIP = codes
}
if slices.Contains(codes, g.country) {
return true, g.adapter
}
return false, ""
}
// MatchIp implements C.IpMatcher
func (g *GEOIP) MatchIp(ip netip.Addr) bool {
if !ip.IsValid() {
return false
}
if g.country == "lan" {
return g.isLan(ip)
}
if C.GeodataMode {
matcher, err := g.getIPMatcher()
if err != nil {
return false
}
return matcher.Match(ip)
}
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
return slices.Contains(codes, g.country)
}
func (g *GEOIP) isLan(ip netip.Addr) bool {
return ip.IsPrivate() ||
ip.IsUnspecified() ||
ip.IsLoopback() ||
ip.IsMulticast() ||
ip.IsLinkLocalUnicast() ||
resolver.IsFakeBroadcastIP(ip), g.adapter
}
for _, code := range metadata.DstGeoIP {
if g.country == code {
return true, g.adapter
}
}
if !C.GeodataMode {
if g.isSourceIP {
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range codes {
if g.country == code {
return true, g.adapter
}
}
return false, g.adapter
}
if metadata.DstGeoIP != nil {
return false, g.adapter
}
metadata.DstGeoIP = mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range metadata.DstGeoIP {
if g.country == code {
return true, g.adapter
}
}
return false, g.adapter
}
matcher, err := g.GetIPMatcher()
if err != nil {
return false, ""
}
match := matcher.Match(ip)
if match && !g.isSourceIP {
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
}
return match, g.adapter
resolver.IsFakeBroadcastIP(ip)
}
func (g *GEOIP) Adapter() string {
@@ -106,14 +141,19 @@ func (g *GEOIP) GetCountry() string {
}
func (g *GEOIP) GetIPMatcher() (router.IPMatcher, error) {
if g.geodata {
if C.GeodataMode {
return g.getIPMatcher()
}
return nil, errors.New("not geodata mode")
}
func (g *GEOIP) getIPMatcher() (router.IPMatcher, error) {
geoIPMatcher, err := geodata.LoadGeoIPMatcher(g.country)
if err != nil {
return nil, fmt.Errorf("[GeoIP] %w", err)
}
return geoIPMatcher, nil
}
return nil, errors.New("geoip country not set")
}
func (g *GEOIP) GetRecodeSize() int {
@@ -141,12 +181,13 @@ func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP,
return geoip, nil
}
geoip.geodata = true
geoIPMatcher, err := geoip.GetIPMatcher() // test load
if C.GeodataMode {
geoIPMatcher, err := geoip.getIPMatcher() // test load
if err != nil {
return nil, err
}
log.Infoln("Finished initial GeoIP rule %s => %s, records: %d", country, adapter, geoIPMatcher.Count())
}
return geoip, nil
}

View File

@@ -23,15 +23,19 @@ func (gs *GEOSITE) RuleType() C.RuleType {
}
func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) {
domain := metadata.RuleHost()
return gs.MatchDomain(metadata.RuleHost()), gs.adapter
}
// MatchDomain implements C.DomainMatcher
func (gs *GEOSITE) MatchDomain(domain string) bool {
if len(domain) == 0 {
return false, ""
return false
}
matcher, err := gs.GetDomainMatcher()
if err != nil {
return false, ""
return false
}
return matcher.ApplyDomain(domain), gs.adapter
return matcher.ApplyDomain(domain)
}
func (gs *GEOSITE) Adapter() string {

View File

@@ -28,8 +28,11 @@ func (a *ASN) Match(metadata *C.Metadata) (bool, string) {
result := mmdb.ASNInstance().LookupASN(ip.AsSlice())
asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10)
if !a.isSourceIP {
metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization
ipASN := asnNumber + " " + result.AutonomousSystemOrganization
if a.isSourceIP {
metadata.SrcIPASN = ipASN
} else {
metadata.DstIPASN = ipASN
}
match := a.asn == asnNumber

View File

@@ -1,40 +0,0 @@
package provider
import (
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
)
type DomainSet struct {
*domainStrategy
adapter string
}
func (d *DomainSet) ProviderNames() []string {
return nil
}
func (d *DomainSet) RuleType() C.RuleType {
return C.DomainSet
}
func (d *DomainSet) Match(metadata *C.Metadata) (bool, string) {
return d.domainStrategy.Match(metadata), d.adapter
}
func (d *DomainSet) Adapter() string {
return d.adapter
}
func (d *DomainSet) Payload() string {
return ""
}
func NewDomainSet(domainSet *trie.DomainSet, adapter string) *DomainSet {
return &DomainSet{
domainStrategy: &domainStrategy{domainSet: domainSet},
adapter: adapter,
}
}
var _ C.Rule = (*DomainSet)(nil)

View File

@@ -1,40 +0,0 @@
package provider
import (
"github.com/metacubex/mihomo/component/cidr"
C "github.com/metacubex/mihomo/constant"
)
type IpCidrSet struct {
*ipcidrStrategy
adapter string
}
func (d *IpCidrSet) ProviderNames() []string {
return nil
}
func (d *IpCidrSet) RuleType() C.RuleType {
return C.IpCidrSet
}
func (d *IpCidrSet) Match(metadata *C.Metadata) (bool, string) {
return d.ipcidrStrategy.Match(metadata), d.adapter
}
func (d *IpCidrSet) Adapter() string {
return d.adapter
}
func (d *IpCidrSet) Payload() string {
return ""
}
func NewIpCidrSet(cidrSet *cidr.IpCidrSet, adapter string) *IpCidrSet {
return &IpCidrSet{
ipcidrStrategy: &ipcidrStrategy{cidrSet: cidrSet},
adapter: adapter,
}
}
var _ C.Rule = (*IpCidrSet)(nil)

View File

@@ -1,6 +1,8 @@
package provider
import (
"net/netip"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/rules/common"
@@ -35,6 +37,18 @@ func (rs *RuleSet) Match(metadata *C.Metadata) (bool, string) {
return false, ""
}
// MatchDomain implements C.DomainMatcher
func (rs *RuleSet) MatchDomain(domain string) bool {
ok, _ := rs.Match(&C.Metadata{Host: domain})
return ok
}
// MatchIp implements C.IpMatcher
func (rs *RuleSet) MatchIp(ip netip.Addr) bool {
ok, _ := rs.Match(&C.Metadata{DstIP: ip})
return ok
}
func (rs *RuleSet) Adapter() string {
return rs.adapter
}

View File

@@ -47,15 +47,6 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5"
dependencies = [
"memchr",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -199,7 +190,7 @@ dependencies = [
"clipboard-win",
"core-graphics 0.23.2",
"image 0.25.2",
"log 0.4.22",
"log",
"objc2",
"objc2-app-kit",
"objc2-foundation",
@@ -326,7 +317,7 @@ dependencies = [
"cfg-if",
"concurrent-queue",
"futures-lite 1.13.0",
"log 0.4.22",
"log",
"parking",
"polling 2.8.0",
"rustix 0.37.27",
@@ -348,7 +339,7 @@ dependencies = [
"futures-lite 2.3.0",
"parking",
"polling 3.7.3",
"rustix 0.38.34",
"rustix 0.38.35",
"slab",
"tracing",
"windows-sys 0.59.0",
@@ -409,7 +400,7 @@ dependencies = [
"cfg-if",
"event-listener 3.1.0",
"futures-lite 1.13.0",
"rustix 0.38.34",
"rustix 0.38.35",
"windows-sys 0.48.0",
]
@@ -428,7 +419,7 @@ dependencies = [
"cfg-if",
"event-listener 5.3.0",
"futures-lite 2.3.0",
"rustix 0.38.34",
"rustix 0.38.35",
"tracing",
"windows-sys 0.59.0",
]
@@ -456,7 +447,7 @@ dependencies = [
"cfg-if",
"futures-core",
"futures-io",
"rustix 0.38.34",
"rustix 0.38.35",
"signal-hook-registry",
"slab",
"windows-sys 0.59.0",
@@ -545,7 +536,7 @@ checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
dependencies = [
"anyhow",
"arrayvec 0.7.6",
"log 0.4.22",
"log",
"nom 7.1.3",
"num-rational",
"v_frame",
@@ -686,13 +677,13 @@ dependencies = [
"cexpr",
"clang-sys",
"itertools 0.12.1",
"lazy_static 1.5.0",
"lazy_static",
"lazycell",
"log 0.4.22",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex 1.10.6",
"regex",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.76",
@@ -971,9 +962,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.17.0"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2"
dependencies = [
"bytemuck_derive",
]
@@ -1271,7 +1262,7 @@ dependencies = [
"humansize",
"image 0.25.2",
"indexmap 2.4.0",
"log 0.4.22",
"log",
"md-5",
"mime",
"mlua",
@@ -1293,14 +1284,14 @@ dependencies = [
"port_scanner",
"rand 0.8.5",
"redb",
"regex 1.10.6",
"regex",
"relative-path",
"reqwest 0.12.7",
"rfd",
"rs-snowflake",
"runas",
"rust-i18n",
"rustc_version 0.4.0",
"rustc_version 0.4.1",
"semver 1.0.23",
"serde",
"serde_json",
@@ -1442,7 +1433,7 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static 1.5.0",
"lazy_static",
"windows-sys 0.48.0",
]
@@ -1855,7 +1846,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7397f8c48906dd9b5afc75001368c979418e5dff5575998a831eb2319b424e"
dependencies = [
"lazy_static 1.5.0",
"lazy_static",
"pathsearch",
"rand 0.8.5",
"shared_library",
@@ -1883,7 +1874,7 @@ dependencies = [
"dashmap 5.5.3",
"event-listener 5.3.0",
"futures",
"log 0.4.22",
"log",
"lru",
"once_cell",
"rs-snowflake",
@@ -1966,7 +1957,7 @@ dependencies = [
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"rustc_version 0.4.1",
"syn 2.0.76",
]
@@ -2078,7 +2069,7 @@ dependencies = [
"anyhow",
"core-graphics 0.23.2",
"fxhash",
"log 0.4.22",
"log",
"smithay-client-toolkit",
"widestring 1.1.0",
"windows 0.56.0",
@@ -2167,7 +2158,7 @@ checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602"
dependencies = [
"cc",
"memchr",
"rustc_version 0.4.0",
"rustc_version 0.4.1",
"toml 0.8.19",
"vswhom",
"winreg 0.52.0",
@@ -2221,7 +2212,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log 0.4.22",
"log",
]
[[package]]
@@ -2233,7 +2224,7 @@ dependencies = [
"anstream",
"anstyle",
"env_filter",
"log 0.4.22",
"log",
]
[[package]]
@@ -2371,7 +2362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
dependencies = [
"memoffset 0.9.1",
"rustc_version 0.4.0",
"rustc_version 0.4.1",
]
[[package]]
@@ -2387,9 +2378,9 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.24"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
@@ -2733,7 +2724,7 @@ checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
dependencies = [
"cc",
"libc",
"log 0.4.22",
"log",
"rustversion",
"windows 0.48.0",
]
@@ -2885,9 +2876,9 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick 1.1.3",
"aho-corasick",
"bstr",
"log 0.4.22",
"log",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
@@ -3029,15 +3020,14 @@ dependencies = [
[[package]]
name = "handlebars"
version = "0.29.1"
version = "3.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb04af2006ea09d985fef82b81e0eb25337e51b691c76403332378a53d521edc"
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
dependencies = [
"lazy_static 0.2.11",
"log 0.3.9",
"pest 0.3.3",
"quick-error 1.2.3",
"regex 0.2.11",
"log",
"pest",
"pest_derive",
"quick-error",
"serde",
"serde_json",
]
@@ -3121,7 +3111,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [
"log 0.4.22",
"log",
"mac",
"markup5ever",
"proc-macro2",
@@ -3499,7 +3489,7 @@ checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
dependencies = [
"crossbeam-deque",
"globset",
"log 0.4.22",
"log",
"memchr",
"regex-automata 0.4.7",
"same-file",
@@ -3549,7 +3539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
dependencies = [
"byteorder-lite",
"quick-error 2.0.1",
"quick-error",
]
[[package]]
@@ -3615,16 +3605,16 @@ dependencies = [
[[package]]
name = "interfaces"
version = "0.0.8"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec8f50a973916cac3da5057c986db05cd3346f38c78e9bc24f64cc9f6a3978f"
checksum = "bb6250a98af259a26fd5a4a6081fccea9ac116e4c3178acf4aeb86d32d2b7715"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.6.0",
"cc",
"handlebars",
"lazy_static 1.5.0",
"lazy_static",
"libc",
"nix 0.23.2",
"nix 0.26.4",
"serde",
"serde_derive",
]
@@ -3683,13 +3673,13 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "iptools"
version = "0.2.5"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c416c05ba2a10240e022887617af3128fccdbf69713214da0fc81a5690d00df7"
checksum = "9bab2ab6edf9330906c4da149dddc19e850b14c682865d81fb21962bbfc2e0ae"
dependencies = [
"ahash",
"once_cell",
"regex 1.10.6",
"regex",
]
[[package]]
@@ -3732,7 +3722,7 @@ dependencies = [
"event-listener 2.5.3",
"futures-lite 1.13.0",
"http 0.2.12",
"log 0.4.22",
"log",
"mime",
"once_cell",
"polling 2.8.0",
@@ -3824,7 +3814,7 @@ dependencies = [
"cesu8",
"combine",
"jni-sys",
"log 0.4.22",
"log",
"thiserror",
"walkdir",
]
@@ -3908,12 +3898,6 @@ dependencies = [
"selectors",
]
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -4006,7 +3990,7 @@ dependencies = [
"gtk",
"gtk-sys",
"libappindicator-sys",
"log 0.4.22",
"log",
]
[[package]]
@@ -4142,15 +4126,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
[[package]]
name = "log"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
dependencies = [
"log 0.4.22",
]
[[package]]
name = "log"
version = "0.4.22"
@@ -4253,7 +4228,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
dependencies = [
"log 0.4.22",
"log",
"phf 0.10.1",
"phf_codegen 0.10.0",
"string_cache",
@@ -4470,7 +4445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
dependencies = [
"libc",
"log 0.4.22",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
@@ -4537,6 +4512,7 @@ dependencies = [
"cfg-if",
"libc",
"memoffset 0.7.1",
"pin-utils",
]
[[package]]
@@ -4628,7 +4604,7 @@ version = "4.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26a1d03b6305ecefdd9c6c60150179bb8d9f0cd4e64bbcad1e41419e7bf5e414"
dependencies = [
"log 0.4.22",
"log",
"mac-notification-sys",
"serde",
"tauri-winrt-notification",
@@ -4786,7 +4762,7 @@ dependencies = [
[[package]]
name = "nyanpasu-ipc"
version = "1.0.5"
source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#ca20b17dcbccf65c92ae7c07dfa9319d8bc697a1"
source = "git+https://github.com/LibNyanpasu/nyanpasu-service.git#2953e6e31663a9f742efbfd20d8c6f3f9c7641b5"
dependencies = [
"anyhow",
"axum",
@@ -5106,7 +5082,7 @@ version = "3.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092"
dependencies = [
"log 0.4.22",
"log",
"serde",
"windows-sys 0.52.0",
]
@@ -5346,12 +5322,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6dda33d67c26f0aac90d324ab2eb7239c819fc7b2552fe9faa4fe88441edc8"
[[package]]
name = "pest"
version = "2.7.11"
@@ -5363,6 +5333,40 @@ dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.76",
]
[[package]]
name = "pest_meta"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
dependencies = [
"once_cell",
"pest",
"sha2 0.10.8",
]
[[package]]
name = "petgraph"
version = "0.6.5"
@@ -5603,7 +5607,7 @@ dependencies = [
"cfg-if",
"concurrent-queue",
"libc",
"log 0.4.22",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
]
@@ -5618,7 +5622,7 @@ dependencies = [
"concurrent-queue",
"hermit-abi 0.4.0",
"pin-project-lite",
"rustix 0.38.34",
"rustix 0.38.35",
"tracing",
"windows-sys 0.59.0",
]
@@ -5758,12 +5762,6 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
@@ -5961,7 +5959,7 @@ dependencies = [
"itertools 0.12.1",
"libc",
"libfuzzer-sys",
"log 0.4.22",
"log",
"maybe-rayon",
"new_debug_unreachable",
"noop_proc_macro",
@@ -5988,7 +5986,7 @@ dependencies = [
"avif-serialize",
"imgref",
"loop9",
"quick-error 2.0.1",
"quick-error",
"rav1e",
"rgb",
]
@@ -6089,26 +6087,13 @@ dependencies = [
"syn 2.0.76",
]
[[package]]
name = "regex"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
dependencies = [
"aho-corasick 0.6.10",
"memchr",
"regex-syntax 0.5.6",
"thread_local 0.3.6",
"utf8-ranges",
]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick 1.1.3",
"aho-corasick",
"memchr",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
@@ -6129,20 +6114,11 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick 1.1.3",
"aho-corasick",
"memchr",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-syntax"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
dependencies = [
"ucd-util",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
@@ -6189,7 +6165,7 @@ dependencies = [
"hyper-tls 0.5.0",
"ipnet",
"js-sys",
"log 0.4.22",
"log",
"mime",
"native-tls",
"once_cell",
@@ -6234,7 +6210,7 @@ dependencies = [
"hyper-util",
"ipnet",
"js-sys",
"log 0.4.22",
"log",
"mime",
"native-tls",
"once_cell",
@@ -6275,8 +6251,8 @@ dependencies = [
"gobject-sys",
"gtk-sys",
"js-sys",
"lazy_static 1.5.0",
"log 0.4.22",
"lazy_static",
"log",
"objc",
"objc-foundation",
"objc_id",
@@ -6320,8 +6296,7 @@ checksum = "e60ef3b82994702bbe4e134d98aadca4b49ed04440148985678d415c68127666"
[[package]]
name = "runas"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96d6b6c505282b007a9b009f2aa38b2fd0359b81a0430ceacc60f69ade4c6a0"
source = "git+https://github.com/libnyanpasu/rust-runas.git#29bdb2501c05b7d78e63628bedbd4a190b71bad3"
dependencies = [
"libc",
"security-framework-sys",
@@ -6337,7 +6312,7 @@ checksum = "039f57d22229db401af3458ca939300178e99e88b938573cea12b7c2b0f09724"
dependencies = [
"globwalk",
"once_cell",
"regex 1.10.6",
"regex",
"rust-i18n-macro",
"rust-i18n-support",
"smallvec",
@@ -6370,11 +6345,11 @@ dependencies = [
"base62",
"globwalk",
"itertools 0.11.0",
"lazy_static 1.5.0",
"lazy_static",
"normpath",
"once_cell",
"proc-macro2",
"regex 1.10.6",
"regex",
"serde",
"serde_json",
"serde_yml",
@@ -6412,9 +6387,9 @@ dependencies = [
[[package]]
name = "rustc_version"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver 1.0.23",
]
@@ -6435,9 +6410,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.34"
version = "0.38.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
dependencies = [
"bitflags 2.6.0",
"errno",
@@ -6487,9 +6462,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
[[package]]
name = "rustls-webpki"
version = "0.102.6"
version = "0.102.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
dependencies = [
"ring",
"rustls-pki-types",
@@ -6577,7 +6552,7 @@ dependencies = [
"cssparser",
"derive_more",
"fxhash",
"log 0.4.22",
"log",
"matches",
"phf 0.8.0",
"phf_codegen 0.8.0",
@@ -6626,7 +6601,7 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest 2.7.11",
"pest",
]
[[package]]
@@ -6772,7 +6747,7 @@ dependencies = [
"indexmap 2.4.0",
"itoa 1.0.11",
"libyml",
"log 0.4.22",
"log",
"memchr",
"ryu",
"serde",
@@ -6853,7 +6828,7 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static 1.5.0",
"lazy_static",
]
[[package]]
@@ -6872,7 +6847,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
dependencies = [
"lazy_static 1.5.0",
"lazy_static",
"libc",
]
@@ -7004,9 +6979,9 @@ dependencies = [
"bitflags 2.6.0",
"cursor-icon",
"libc",
"log 0.4.22",
"log",
"memmap2",
"rustix 0.38.34",
"rustix 0.38.35",
"thiserror",
"wayland-backend",
"wayland-client",
@@ -7248,13 +7223,13 @@ dependencies = [
[[package]]
name = "sysproxy"
version = "0.3.0"
source = "git+https://github.com/LibNyanpasu/sysproxy-rs.git#33b63f2a7550924cb6a7bc41b02a2738acca65db"
source = "git+https://github.com/LibNyanpasu/sysproxy-rs.git#45fffb0cdddb7c2d0ce30300dd72a934bb458c09"
dependencies = [
"interfaces",
"iptools",
"log 0.4.22",
"log",
"thiserror",
"windows 0.52.0",
"windows 0.58.0",
"winreg 0.52.0",
"xdg",
]
@@ -7354,10 +7329,10 @@ dependencies = [
"image 0.24.9",
"instant",
"jni",
"lazy_static 1.5.0",
"lazy_static",
"libappindicator",
"libc",
"log 0.4.22",
"log",
"ndk",
"ndk-context",
"ndk-sys",
@@ -7447,7 +7422,7 @@ dependencies = [
"png",
"rand 0.8.5",
"raw-window-handle 0.5.2",
"regex 1.10.6",
"regex",
"reqwest 0.11.27",
"rfd",
"semver 1.0.23",
@@ -7508,7 +7483,7 @@ dependencies = [
"png",
"proc-macro2",
"quote",
"regex 1.10.6",
"regex",
"semver 1.0.23",
"serde",
"serde_json",
@@ -7540,7 +7515,7 @@ version = "0.1.2"
dependencies = [
"dirs 5.0.1",
"interprocess",
"log 0.4.22",
"log",
"objc2",
"once_cell",
"tauri-utils",
@@ -7606,7 +7581,7 @@ dependencies = [
"infer",
"json-patch",
"kuchikiki",
"log 0.4.22",
"log",
"memchr",
"phf 0.11.2",
"proc-macro2",
@@ -7651,7 +7626,7 @@ dependencies = [
"cfg-if",
"fastrand 2.1.1",
"once_cell",
"rustix 0.38.34",
"rustix 0.38.35",
"windows-sys 0.59.0",
]
@@ -7700,14 +7675,14 @@ dependencies = [
"cfg-if",
"filedescriptor",
"hex",
"lazy_static 1.5.0",
"lazy_static",
"libc",
"log 0.4.22",
"log",
"memmem",
"num-derive 0.3.3",
"num-traits",
"ordered-float",
"regex 1.10.6",
"regex",
"semver 0.11.0",
"sha2 0.9.9",
"signal-hook",
@@ -7795,15 +7770,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
dependencies = [
"lazy_static 1.5.0",
]
[[package]]
name = "thread_local"
version = "1.1.8"
@@ -7951,7 +7917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
dependencies = [
"futures-util",
"log 0.4.22",
"log",
"tokio",
"tungstenite",
]
@@ -8071,7 +8037,7 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"log 0.4.22",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -8137,7 +8103,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log 0.4.22",
"log",
"once_cell",
"tracing-core",
]
@@ -8162,12 +8128,12 @@ dependencies = [
"nu-ansi-term",
"once_cell",
"parking_lot",
"regex 1.10.6",
"regex",
"serde",
"serde_json",
"sharded-slab",
"smallvec",
"thread_local 1.1.8",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
@@ -8216,7 +8182,7 @@ dependencies = [
"data-encoding",
"http 1.1.0",
"httparse",
"log 0.4.22",
"log",
"rand 0.8.5",
"sha1",
"thiserror",
@@ -8242,12 +8208,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "ucd-util"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003"
[[package]]
name = "uds_windows"
version = "1.1.0"
@@ -8340,12 +8300,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8-ranges"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -8531,7 +8485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log 0.4.22",
"log",
"once_cell",
"proc-macro2",
"quote",
@@ -8601,7 +8555,7 @@ checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993"
dependencies = [
"cc",
"downcast-rs",
"rustix 0.38.34",
"rustix 0.38.35",
"scoped-tls",
"smallvec",
"wayland-sys",
@@ -8614,7 +8568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943"
dependencies = [
"bitflags 2.6.0",
"rustix 0.38.34",
"rustix 0.38.35",
"wayland-backend",
"wayland-scanner",
]
@@ -8636,7 +8590,7 @@ version = "0.31.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95"
dependencies = [
"rustix 0.38.34",
"rustix 0.38.35",
"wayland-client",
"xcursor",
]
@@ -8684,7 +8638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148"
dependencies = [
"dlib",
"log 0.4.22",
"log",
"pkg-config",
]
@@ -8791,7 +8745,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7"
dependencies = [
"regex 1.10.6",
"regex",
"serde",
"serde_json",
"thiserror",
@@ -8815,7 +8769,7 @@ dependencies = [
"either",
"home",
"once_cell",
"rustix 0.38.34",
"rustix 0.38.35",
]
[[package]]
@@ -8826,7 +8780,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
dependencies = [
"either",
"home",
"rustix 0.38.34",
"rustix 0.38.35",
"winsafe",
]
@@ -9503,7 +9457,7 @@ checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb"
dependencies = [
"derive-new",
"libc",
"log 0.4.22",
"log",
"nix 0.28.0",
"os_pipe",
"tempfile",
@@ -9547,7 +9501,7 @@ dependencies = [
"http 0.2.12",
"kuchikiki",
"libc",
"log 0.4.22",
"log",
"objc",
"objc_id",
"once_cell",
@@ -9593,7 +9547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12"
dependencies = [
"gethostname",
"rustix 0.38.34",
"rustix 0.38.35",
"x11rb-protocol",
]
@@ -9611,7 +9565,7 @@ checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
dependencies = [
"libc",
"linux-raw-sys 0.4.14",
"rustix 0.38.34",
"rustix 0.38.35",
]
[[package]]
@@ -9881,7 +9835,7 @@ dependencies = [
"bumpalo",
"crc32fast",
"lockfree-object-pool",
"log 0.4.22",
"log",
"once_cell",
"simd-adler32",
]

View File

@@ -46,6 +46,7 @@ serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }
relative-path = "1.9"
tauri = { version = "1.5.4", features = [
"dialog-all",
"updater",
"fs-all",
"clipboard-all",
@@ -75,7 +76,7 @@ dyn-clone = "1.0.16"
rs-snowflake = "0.6"
thiserror = { workspace = true }
simd-json = "0.13.8"
runas = "1.2.0"
runas = { git = "https://github.com/libnyanpasu/rust-runas.git" }
backon = { version = "1.0.1", features = ["tokio-sleep"] }
rust-i18n = "3"
adler = "1.0.2"

View File

@@ -28,3 +28,15 @@ pub static BUILD_INFO: Lazy<BuildInfo> = Lazy::new(|| BuildInfo {
rustc_version: env!("RUSTC_VERSION"),
llvm_version: env!("LLVM_VERSION"),
});
pub static IS_APPIMAGE: Lazy<bool> = Lazy::new(|| std::env::var("APPIMAGE").is_ok());
pub static IS_PORTABLE: Lazy<bool> = Lazy::new(|| {
if cfg!(windows) {
let dir = crate::utils::dirs::app_install_dir().unwrap();
let portable_file = dir.join(".config/PORTABLE");
portable_file.exists()
} else {
false
}
});

View File

@@ -449,6 +449,11 @@ pub fn open_that(path: String) -> CmdResult {
wrap_err!(crate::utils::open::that(path))
}
#[tauri::command]
pub fn is_appimage() -> CmdResult<bool> {
Ok(*crate::consts::IS_APPIMAGE)
}
#[cfg(windows)]
#[tauri::command]
pub fn get_custom_app_dir() -> CmdResult<Option<String>> {

View File

@@ -243,6 +243,7 @@ fn main() -> std::io::Result<()> {
ipc::url_delay_test,
ipc::get_ipsb_asn,
ipc::open_that,
ipc::is_appimage,
]);
#[cfg(target_os = "macos")]

View File

@@ -36,11 +36,6 @@ pub const NYANPASU_CONFIG: &str = "nyanpasu-config.yaml";
pub const PROFILE_YAML: &str = "profiles.yaml";
pub const STORAGE_DB: &str = "storage.db";
/// portable flag
#[allow(unused)]
#[cfg(target_os = "windows")]
static PORTABLE_FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
pub static APP_VERSION: &str = env!("NYANPASU_VERSION");
pub fn get_app_version() -> &'static str {
@@ -49,27 +44,14 @@ pub fn get_app_version() -> &'static str {
#[cfg(target_os = "windows")]
pub fn get_portable_flag() -> bool {
*PORTABLE_FLAG.get().unwrap_or(&false)
}
/// initialize portable flag
#[cfg(target_os = "windows")]
pub fn init_portable_flag() -> Result<()> {
let dir = app_install_dir()?;
let portable_file = dir.join(".config/PORTABLE");
if portable_file.exists() {
PORTABLE_FLAG.get_or_init(|| true);
return Ok(());
}
PORTABLE_FLAG.get_or_init(|| false);
Ok(())
*crate::consts::IS_PORTABLE
}
pub fn app_config_dir() -> Result<PathBuf> {
let path: Option<PathBuf> = {
#[cfg(target_os = "windows")]
{
if *PORTABLE_FLAG.get().unwrap_or(&false) {
if get_portable_flag() {
let app_dir = app_install_dir()?;
Some(app_dir.join(".config").join(PREVIOUS_APP_NAME))
} else if let Ok(Some(path)) = super::winreg::get_app_dir() {
@@ -99,7 +81,7 @@ pub fn app_data_dir() -> Result<PathBuf> {
let path: Option<PathBuf> = {
#[cfg(target_os = "windows")]
{
if *PORTABLE_FLAG.get().unwrap_or(&false) {
if get_portable_flag() {
let app_dir = app_install_dir()?;
Some(app_dir.join(".data").join(PREVIOUS_APP_NAME))
} else {
@@ -126,7 +108,7 @@ pub fn app_data_dir() -> Result<PathBuf> {
pub fn old_app_home_dir() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
{
if !PORTABLE_FLAG.get().unwrap_or(&false) {
if !get_portable_flag() {
Ok(home_dir()
.ok_or(anyhow::anyhow!("failed to check old app home dir"))?
.join(".config")
@@ -160,7 +142,7 @@ pub fn app_home_dir() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
{
use crate::utils::winreg::get_app_dir;
if !PORTABLE_FLAG.get().unwrap_or(&false) {
if !get_portable_flag() {
let reg_app_dir = get_app_dir()?;
if let Some(reg_app_dir) = reg_app_dir {
return Ok(reg_app_dir);

View File

@@ -103,9 +103,6 @@ pub fn run_pending_migrations() -> Result<()> {
/// Initialize all the config files
/// before tauri setup
pub fn init_config() -> Result<()> {
#[cfg(target_os = "windows")]
let _ = dirs::init_portable_flag();
// Check if old config dir exist and new config dir is not exist
let mut old_app_dir: Option<PathBuf> = None;
let mut app_dir: Option<PathBuf> = None;

View File

@@ -212,18 +212,40 @@ pub fn create_window(app_handle: &AppHandle) {
}
};
#[cfg(target_os = "windows")]
{
use tauri::{PhysicalPosition, PhysicalSize};
use window_shadows::set_shadow;
match builder
#[cfg(windows)]
let win_res = builder
.decorations(false)
.transparent(true)
.visible(false)
.build()
{
.build();
#[cfg(target_os = "macos")]
let win_res = builder
.decorations(true)
.hidden_title(true)
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build();
#[cfg(target_os = "linux")]
let win_res = builder.decorations(true).transparent(true).build();
#[cfg(target_os = "macos")]
fn set_controls_and_log_error(app_handle: &tauri::AppHandle, window_name: &str) {
match app_handle.get_window(window_name).unwrap().ns_window() {
Ok(raw_window) => {
let window_id: cocoa::base::id = raw_window as _;
set_window_controls_pos(window_id, 26.0, 26.0);
}
Err(err) => {
log::error!(target: "app", "failed to get ns_window, {err}");
}
}
}
match win_res {
Ok(win) => {
use tauri::{PhysicalPosition, PhysicalSize};
#[cfg(windows)]
use window_shadows::set_shadow;
if win_state.is_some() {
let state = win_state.as_ref().unwrap();
win.set_position(PhysicalPosition {
@@ -246,6 +268,7 @@ pub fn create_window(app_handle: &AppHandle) {
trace_err!(win.set_fullscreen(true), "set win fullscreen");
}
}
#[cfg(windows)]
trace_err!(set_shadow(&win, true), "set win shadow");
log::trace!("try to calculate the monitor size");
let center = (|| -> Result<bool> {
@@ -280,40 +303,14 @@ pub fn create_window(app_handle: &AppHandle) {
if center.unwrap_or(true) {
trace_err!(win.center(), "set win center");
}
#[cfg(debug_assertions)]
{
win.open_devtools();
}
OPEN_WINDOWS_COUNTER.fetch_add(1, Ordering::Release);
}
Err(err) => log::error!(target: "app", "failed to create window, {err}"),
}
}
#[cfg(target_os = "macos")]
{
fn set_controls_and_log_error(app_handle: &tauri::AppHandle, window_name: &str) {
match app_handle.get_window(window_name).unwrap().ns_window() {
Ok(raw_window) => {
let window_id: cocoa::base::id = raw_window as _;
set_window_controls_pos(window_id, 33.0, 26.0);
}
Err(err) => {
log::error!(target: "app", "failed to get ns_window, {err}");
}
}
}
match builder
.decorations(true)
.hidden_title(true)
.title_bar_style(tauri::TitleBarStyle::Overlay)
.build()
{
Ok(win) => {
#[cfg(debug_assertions)]
win.open_devtools();
set_controls_and_log_error(&app_handle, "main");
let app_handle_clone = app_handle.clone();
@@ -322,17 +319,8 @@ pub fn create_window(app_handle: &AppHandle) {
set_controls_and_log_error(&app_handle_clone, "main");
}
});
OPEN_WINDOWS_COUNTER.fetch_add(1, Ordering::Release);
}
Err(err) => {
log::error!(target: "app", "failed to create window, {err}");
}
}
}
#[cfg(target_os = "linux")]
match builder.decorations(true).transparent(false).build() {
Ok(_) => {
OPEN_WINDOWS_COUNTER.fetch_add(1, Ordering::Release);
}
Err(err) => {

View File

@@ -105,6 +105,9 @@
},
"fs": {
"all": true
},
"dialog": {
"all": true
}
},
"windows": [],

View File

@@ -20,8 +20,13 @@ export const VALID_CORE: Core[] = [
export const fetchCoreVersion = async () => {
return await Promise.all(
VALID_CORE.map(async (item) => {
try {
const version = await getCoreVersion(item.core);
return { ...item, version };
} catch (e) {
console.error("failed to fetch core version", e);
return { ...item, version: "N/A" };
}
}),
);
};

View File

@@ -251,3 +251,7 @@ export const getIpsbASN = async () => invoke<IPSBResponse>("get_ipsb_asn");
export const openThat = async (path: string) => {
return await invoke<void>("open_that", { path });
};
export const isAppImage = async () => {
return await invoke<boolean>("is_appimage");
};

View File

@@ -43,13 +43,13 @@
"react-split-grid": "1.0.4",
"react-use": "17.5.1",
"swr": "2.2.5",
"virtua": "0.33.7"
"virtua": "0.34.0"
},
"devDependencies": {
"@csstools/normalize.css": "12.1.1",
"@emotion/babel-plugin": "11.12.0",
"@emotion/react": "11.13.3",
"@iconify/json": "2.2.241",
"@iconify/json": "2.2.242",
"@types/react": "18.3.4",
"@types/react-dom": "18.3.0",
"@vitejs/plugin-react": "4.3.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -1,4 +1,3 @@
@import "./layout.scss";
@import "./fonts.scss";
body {

View File

@@ -1,9 +0,0 @@
.the-content {
position: absolute;
inset: 36px calc(var(--border-radius) * 2) calc(var(--border-radius) * 2) 0;
}
.the-content-small {
position: absolute;
inset: 36px calc(var(--border-radius) * 2) calc(var(--border-radius) * 2);
}

View File

@@ -51,12 +51,12 @@ export const AppContainer = ({
<Allotment.Pane visible={true} className={styles.container}>
{OS === "windows" && (
<LayoutControl className="!z-top fixed right-6 top-1.5" />
<LayoutControl className="!z-top fixed right-4 top-2" />
)}
{OS === "macos" && (
<div
className="z-top fixed left-6 top-3 h-8 w-[4.5rem] rounded-full"
className="z-top fixed left-4 top-3 h-8 w-[4.5rem] rounded-full"
style={{ backgroundColor: alpha(palette.primary.main, 0.1) }}
/>
)}

View File

@@ -17,7 +17,7 @@ export const AppDrawer = () => {
<div
className={classNames(
"fixed z-10 flex items-center gap-2",
getSystem() === "macos" ? "left-[6.5rem] top-3" : "left-6 top-1.5",
getSystem() === "macos" ? "left-24 top-3" : "left-6 top-1.5",
)}
data-windrag
>

View File

@@ -1,6 +1,8 @@
import { useSize } from "ahooks";
import clsx from "clsx";
import { useCallback, useEffect, useRef, useState } from "react";
import { useAtom } from "jotai";
import { useCallback, useEffect, useRef } from "react";
import { atomIsDrawerOnlyIcon } from "@/store";
import getSystem from "@/utils/get-system";
import { languageQuirks } from "@/utils/language";
import { getRoutesWithIcon } from "@/utils/routes-utils";
@@ -9,7 +11,7 @@ import AnimatedLogo from "../layout/animated-logo";
import RouteListItem from "./modules/route-list-item";
export const DrawerContent = ({ className }: { className?: string }) => {
const [onlyIcon, setOnlyIcon] = useState(false);
const [onlyIcon, setOnlyIcon] = useAtom(atomIsDrawerOnlyIcon);
const { nyanpasuConfig } = useNyanpasu();
@@ -34,7 +36,7 @@ export const DrawerContent = ({ className }: { className?: string }) => {
setOnlyIcon(false);
}
},
[nyanpasuConfig?.language],
[nyanpasuConfig?.language, setOnlyIcon],
);
useEffect(() => {

View File

@@ -6,8 +6,6 @@ import { LogMessage, useClashWS } from "@nyanpasu/interface";
const MAX_LOG_NUM = 1000;
const time = dayjs().format("MM-DD HH:mm:ss");
export const LogProvider = () => {
const {
logs: { latestMessage },
@@ -23,12 +21,11 @@ export const LogProvider = () => {
}
const data = JSON.parse(latestMessage?.data) as LogMessage;
const time = dayjs(data.time).format("MM-DD HH:mm:ss");
setLogData((prev) => {
if (prev.length >= MAX_LOG_NUM) {
prev.shift();
}
return [...prev, { ...data, time }];
});
}, [enableLog, latestMessage?.data, setLogData]);

View File

@@ -238,7 +238,10 @@ export const ProfileDialog = ({
) : (
!isEdit && (
<>
<ReadProfile onSelected={handleProfileSelected} />
<ReadProfile
key="read_profile"
onSelected={handleProfileSelected}
/>
{localProfileMessage && (
<div className="ml-2 text-red-500">{localProfileMessage}</div>

View File

@@ -25,7 +25,7 @@ export const ReadProfile = ({ onSelected }: ReadProfileProps) => {
filters: [
{
name: "profile",
extensions: ["yaml"],
extensions: ["yaml", "yml"],
},
],
});
@@ -42,6 +42,8 @@ export const ReadProfile = ({ onSelected }: ReadProfileProps) => {
} else {
setLabel(selected.split("/").at(-1) as string);
}
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
@@ -51,6 +53,7 @@ export const ReadProfile = ({ onSelected }: ReadProfileProps) => {
<LoadingButton
variant="contained"
loading={loading}
disabled={loading}
onClick={handleSelectFile}
color={label ? "success" : "primary"}
>

View File

@@ -2,6 +2,7 @@ import { useLockFn, useReactive } from "ahooks";
import { motion } from "framer-motion";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { OS } from "@/consts";
import { formatError } from "@/utils";
import { message } from "@/utils/notification";
import { Box, List, ListItem } from "@mui/material";
@@ -17,7 +18,6 @@ export const SettingClashCore = () => {
});
const [expand, setExpand] = useState(false);
const {
nyanpasuConfig,
setClashCore,
@@ -122,12 +122,12 @@ export const SettingClashCore = () => {
labelChildren={<span>{version}</span>}
>
<List disablePadding>
{mergeCores?.map((item, index) => {
{mergeCores?.map((item) => {
const show = expand || item.core == nyanpasuConfig?.clash_core;
return (
<motion.div
key={index}
key={item.name}
animate={show ? "open" : "closed"}
variants={{
open: {
@@ -169,6 +169,8 @@ export const SettingClashCore = () => {
{t("Restart")}
</LoadingButton>
{/** TODO: Support Linux when Manifest v2 released */}
{OS !== "linux" && (
<LoadingButton
variant="contained"
loading={getLatestCore.isLoading}
@@ -176,6 +178,7 @@ export const SettingClashCore = () => {
>
{t("Check Updates")}
</LoadingButton>
)}
</Box>
<ExpandMore expand={expand} onClick={() => setExpand(!expand)} />

View File

@@ -4,6 +4,7 @@ import { useSetAtom } from "jotai";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import LogoSvg from "@/assets/image/logo.svg?react";
import { useUpdaterPlatformSupported } from "@/hooks/use-updater";
import { UpdaterManifestAtom } from "@/store/updater";
import { formatError } from "@/utils";
import { message } from "@/utils/notification";
@@ -47,8 +48,8 @@ export const SettingNyanpasuVersion = () => {
const [loading, setLoading] = useState(false);
const { nyanpasuConfig, setNyanpasuConfig } = useNyanpasu();
const setUpdaterManifest = useSetAtom(UpdaterManifestAtom);
const isPlatformSupported = useUpdaterPlatformSupported();
const onCheckUpdate = useLockFn(async () => {
try {
setLoading(true);
@@ -109,10 +110,11 @@ export const SettingNyanpasuVersion = () => {
</Paper>
</ListItem>
{isPlatformSupported && (
<>
<div className="mb-1 mt-1">
<AutoCheckUpdate />
</div>
<ListItem sx={{ pl: 0, pr: 0 }}>
<LoadingButton
variant="contained"
@@ -124,6 +126,8 @@ export const SettingNyanpasuVersion = () => {
{t("Check for Updates")}
</LoadingButton>
</ListItem>
</>
)}
</List>
</BaseCard>
);

View File

@@ -1,3 +1,7 @@
import { useAtomValue } from "jotai";
import { useWindowSize } from "react-use";
import { useIsAppImage } from "@/hooks/use-consts";
import { atomIsDrawerOnlyIcon } from "@/store";
import Masonry from "@mui/lab/Masonry";
import SettingClashBase from "./setting-clash-base";
import SettingClashCore from "./setting-clash-core";
@@ -15,10 +19,22 @@ import SettingSystemProxy from "./setting-system-proxy";
import SettingSystemService from "./setting-system-service";
export const SettingPage = () => {
const isAppImage = useIsAppImage();
const isDrawerOnlyIcon = useAtomValue(atomIsDrawerOnlyIcon);
const { width } = useWindowSize();
return (
<Masonry
className="w-full"
columns={{ xs: 1, sm: 1, md: 2 }}
columns={{
xs: 1,
sm: 1,
md: isDrawerOnlyIcon ? 2 : width > 1000 ? 2 : 1,
lg: 2,
xl: 2,
}}
spacing={3}
sequential
sx={{ width: "calc(100% + 24px)" }}
@@ -41,7 +57,7 @@ export const SettingPage = () => {
<SettingSystemBehavior />
<SettingSystemService />
{!isAppImage.data && <SettingSystemService />}
<SettingNyanpasuTasks />

View File

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { UpdaterIgnoredAtom } from "@/store/updater";
import { formatError } from "@/utils";
import { message } from "@/utils/notification";
import { Button } from "@mui/material";
import { BaseDialog, BaseDialogProps, cn } from "@nyanpasu/ui";
import { relaunch } from "@tauri-apps/api/process";
import { open as openThat } from "@tauri-apps/api/shell";
@@ -61,12 +62,27 @@ export default function UpdaterDialog({
styles.UpdaterDialog,
)}
>
<div className="flex items-center gap-3 px-2 py-2">
<div className="flex items-center justify-between px-2 py-2">
<div className="flex gap-3">
<span className="text-xl font-bold">{manifest.version}</span>
<span className="text-xs text-slate-500">
{dayjs(manifest.date).format("YYYY-MM-DD HH:mm:ss")}
{dayjs(manifest.date, "YYYY-MM-DD H:mm:ss Z").format(
"YYYY-MM-DD HH:mm:ss",
)}
</span>
</div>
<Button
variant="contained"
size="small"
onClick={() => {
openThat(
`https://github.com/LibNyanpasu/clash-nyanpasu/releases/tag/v${manifest.version}`,
);
}}
>
{t("updater.go")}
</Button>
</div>
<div
className={cn("h-[50vh] overflow-y-auto p-4", styles.MarkdownContent)}
>
@@ -90,7 +106,7 @@ export default function UpdaterDialog({
},
}}
>
{manifest.body}
{manifest.body || "New version available."}
</Markdown>
</Suspense>
</div>

View File

@@ -0,0 +1,3 @@
import { getSystem } from "@nyanpasu/ui";
export const OS = getSystem();

View File

@@ -0,0 +1,11 @@
import useSWR, { SWRConfiguration } from "swr";
import { isAppImage } from "@nyanpasu/interface";
export const useIsAppImage = (config?: Partial<SWRConfiguration>) => {
return useSWR<boolean>("/api/is_appimage", isAppImage, {
...(config || {}),
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: 0,
});
};

View File

@@ -1,17 +1,37 @@
import { useAtomValue, useSetAtom } from "jotai";
import { useMount } from "react-use";
import { useEffect, useState } from "react";
import { OS } from "@/consts";
import { UpdaterIgnoredAtom, UpdaterManifestAtom } from "@/store/updater";
import { useNyanpasu } from "@nyanpasu/interface";
import { checkUpdate } from "@tauri-apps/api/updater";
import { useIsAppImage } from "./use-consts";
export function useUpdaterPlatformSupported() {
const [supported, setSupported] = useState(false);
const isAppImage = useIsAppImage();
useEffect(() => {
switch (OS) {
case "macos":
case "windows":
setSupported(true);
break;
case "linux":
setSupported(!!isAppImage.data);
break;
}
}, [isAppImage.data]);
return supported;
}
export default function useUpdater() {
const { nyanpasuConfig } = useNyanpasu();
const updaterIgnored = useAtomValue(UpdaterIgnoredAtom);
const setUpdaterManifest = useSetAtom(UpdaterManifestAtom);
const isPlatformSupported = useUpdaterPlatformSupported();
useMount(() => {
useEffect(() => {
const run = async () => {
if (nyanpasuConfig?.enable_auto_check_update) {
if (nyanpasuConfig?.enable_auto_check_update && isPlatformSupported) {
const info = await checkUpdate();
if (info?.shouldUpdate && updaterIgnored !== info.manifest?.version) {
setUpdaterManifest(info.manifest || null);
@@ -19,5 +39,10 @@ export default function useUpdater() {
}
};
run().catch(console.error);
});
}, [
isPlatformSupported,
nyanpasuConfig?.enable_auto_check_update,
setUpdaterManifest,
updaterIgnored,
]);
}

View File

@@ -175,6 +175,7 @@
"updater": {
"title": "New version available",
"close": "Ignore",
"update": "Update Now"
"update": "Update Now",
"go": "View on GitHub"
}
}

View File

@@ -158,6 +158,7 @@
"updater": {
"title": "Доступно обновление",
"close": "Закрыть",
"update": "Обновить"
"update": "Обновить",
"go": "Посмотреть на GitHub"
}
}

View File

@@ -177,6 +177,7 @@
"updater": {
"title": "发现新版本",
"close": "忽略",
"update": "立即更新"
"update": "立即更新",
"go": "查看发布页"
}
}

View File

@@ -17,10 +17,11 @@ import { atomIsDrawer } from "@/store";
import { classNames } from "@/utils";
import { useTheme } from "@mui/material";
import { Experimental_CssVarsProvider as CssVarsProvider } from "@mui/material/styles";
import { useBreakpoint } from "@nyanpasu/ui";
import { cn, useBreakpoint } from "@nyanpasu/ui";
import { emit } from "@tauri-apps/api/event";
import "dayjs/locale/ru";
import "dayjs/locale/zh-cn";
import customParseFormat from "dayjs/plugin/customParseFormat";
import relativeTime from "dayjs/plugin/relativeTime";
import { useAtom } from "jotai";
import { useEffect } from "react";
@@ -29,6 +30,7 @@ import { SWRConfig } from "swr";
import styles from "./_app.module.scss";
dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);
export default function App() {
const { theme } = useCustomTheme();
@@ -73,7 +75,7 @@ export default function App() {
<AppContainer isDrawer={isDrawer}>
<PageTransition
className={isDrawer ? "the-content-small" : "the-content"}
className={cn("absolute inset-4 top-10", !isDrawer && "left-0")}
/>
</AppContainer>
</CssVarsProvider>

View File

@@ -136,6 +136,7 @@ export default function ProxyPage() {
className={cn(
"absolute z-10 flex w-full items-center justify-between px-4 py-2 backdrop-blur",
"bg-gray-200/30 dark:bg-gray-900/30",
"!rounded-t-2xl",
)}
>
<div className="flex items-center gap-4">

View File

@@ -56,6 +56,8 @@ export const atomEnableLog = atomWithLocalStorage<boolean>(
export const atomIsDrawer = atom<boolean>();
export const atomIsDrawerOnlyIcon = atom<boolean>();
// save the state of each profile item loading
export const atomLoadingCache = atom<Record<string, boolean>>({});

View File

@@ -174,7 +174,7 @@ export const BaseDialog = ({
<div
className={cn(
"text-xl",
!full ? "m-4" : OS === "macos" ? "ml-20 p-3" : "m-2 ml-6",
!full ? "m-4" : OS === "macos" ? "ml-20 p-3.5" : "m-2 ml-6",
)}
data-windrag={full}
>
@@ -189,7 +189,9 @@ export const BaseDialog = ({
full && "h-full px-6",
)}
style={{
maxHeight: full ? "calc(100vh - 100px)" : "calc(100vh - 200px)",
maxHeight: full
? `calc(100vh - ${OS === "macos" ? 114 : 100}px)`
: "calc(100vh - 200px)",
...contentStyle,
}}
>

View File

@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.18.7",
"mihomo_alpha": "alpha-3e2c9ce",
"mihomo_alpha": "alpha-8483178",
"clash_rs": "v0.3.0",
"clash_premium": "2023-09-05-gdcc8d87"
},
@@ -36,5 +36,5 @@
"darwin-x64": "clash-darwin-amd64-n{}.gz"
}
},
"updated_at": "2024-08-26T22:20:34.296Z"
"updated_at": "2024-08-27T22:20:48.985Z"
}

View File

@@ -61,7 +61,7 @@
"@tauri-apps/cli": "1.6.1",
"@types/fs-extra": "11.0.4",
"@types/lodash-es": "4.17.12",
"@types/node": "22.5.0",
"@types/node": "22.5.1",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.2.0",
"autoprefixer": "10.4.20",

View File

@@ -24,7 +24,7 @@ importers:
devDependencies:
'@commitlint/cli':
specifier: 19.4.0
version: 19.4.0(@types/node@22.5.0)(typescript@5.5.4)
version: 19.4.0(@types/node@22.5.1)(typescript@5.5.4)
'@commitlint/config-conventional':
specifier: 19.2.2
version: 19.2.2
@@ -41,8 +41,8 @@ importers:
specifier: 4.17.12
version: 4.17.12
'@types/node':
specifier: 22.5.0
version: 22.5.0
specifier: 22.5.1
version: 22.5.1
'@typescript-eslint/eslint-plugin':
specifier: 8.2.0
version: 8.2.0(@typescript-eslint/parser@8.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)
@@ -99,7 +99,7 @@ importers:
version: 4.6.2(eslint@8.57.0)
knip:
specifier: 5.27.4
version: 5.27.4(@types/node@22.5.0)(typescript@5.5.4)
version: 5.27.4(@types/node@22.5.1)(typescript@5.5.4)
lint-staged:
specifier: 15.2.9
version: 15.2.9
@@ -199,7 +199,7 @@ importers:
version: 11.13.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1)
'@generouted/react-router':
specifier: 1.19.6
version: 1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
'@juggle/resize-observer':
specifier: 3.4.0
version: 3.4.0
@@ -288,8 +288,8 @@ importers:
specifier: 2.2.5
version: 2.2.5(react@19.0.0-rc-e948a5ac-20240807)
virtua:
specifier: 0.33.7
version: 0.33.7(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)
specifier: 0.34.0
version: 0.34.0(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)
devDependencies:
'@csstools/normalize.css':
specifier: 12.1.1
@@ -301,8 +301,8 @@ importers:
specifier: 11.13.3
version: 11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1)
'@iconify/json':
specifier: 2.2.241
version: 2.2.241
specifier: 2.2.242
version: 2.2.242
'@types/react':
specifier: npm:types-react@rc
version: types-react@19.0.0-rc.1
@@ -311,10 +311,10 @@ importers:
version: types-react-dom@19.0.0-rc.1
'@vitejs/plugin-react':
specifier: 4.3.1
version: 4.3.1(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.3.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
'@vitejs/plugin-react-swc':
specifier: 3.7.0
version: 3.7.0(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 3.7.0(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
clsx:
specifier: 2.1.1
version: 2.1.1
@@ -335,19 +335,19 @@ importers:
version: 0.19.2(@svgr/core@8.1.0(typescript@5.5.4))
vite:
specifier: 5.4.2
version: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
version: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite-plugin-monaco-editor:
specifier: npm:vite-plugin-monaco-editor-new@1.1.3
version: vite-plugin-monaco-editor-new@1.1.3(monaco-editor@0.51.0)
vite-plugin-sass-dts:
specifier: 1.3.25
version: 1.3.25(postcss@8.4.41)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 1.3.25(postcss@8.4.41)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
vite-plugin-svgr:
specifier: 4.2.0
version: 4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
vite-tsconfig-paths:
specifier: 5.0.1
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
frontend/ui:
dependencies:
@@ -380,7 +380,7 @@ importers:
version: types-react@19.0.0-rc.1
'@vitejs/plugin-react':
specifier: 4.3.1
version: 4.3.1(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.3.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
ahooks:
specifier: 3.8.1
version: 3.8.1(react@19.0.0-rc-e948a5ac-20240807)
@@ -404,10 +404,10 @@ importers:
version: 17.5.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)
vite:
specifier: 5.4.2
version: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
version: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite-tsconfig-paths:
specifier: 5.0.1
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
devDependencies:
'@emotion/react':
specifier: 11.13.3
@@ -432,7 +432,7 @@ importers:
version: 5.1.0(typescript@5.5.4)
vite-plugin-dts:
specifier: 4.0.3
version: 4.0.3(@types/node@22.5.0)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
version: 4.0.3(@types/node@22.5.1)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
scripts:
dependencies:
@@ -1389,8 +1389,8 @@ packages:
'@vue/compiler-sfc':
optional: true
'@iconify/json@2.2.241':
resolution: {integrity: sha512-zpeIjmIrTjl0ra6BYTYDfoK/hXn++xT5Hllc87K5SQwnqKs9RJeToSBzjF1gAsrxia+ilkhtGCcqbFF0ppDXiw==}
'@iconify/json@2.2.242':
resolution: {integrity: sha512-cS6eYdx1C1GhqaZm25ztH5yoghCaTXGJBeseUkS259GxxX9obtGLLk0yy+twxpNCD5/F9gjbgxh46BjNWsHtwg==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -2492,6 +2492,9 @@ packages:
'@types/node@22.5.0':
resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==}
'@types/node@22.5.1':
resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -6550,8 +6553,8 @@ packages:
vfile@6.0.1:
resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
virtua@0.33.7:
resolution: {integrity: sha512-IepZaMD/oeEh/ymTqokeQGLrMuRV25+lizPegxVIhOwqX+dEeV9ml1P57Eosok4qiZaeBeQIbIkF9QZrT+EeRQ==}
virtua@0.34.0:
resolution: {integrity: sha512-tcIehi5MXW5TrJyPiwZXyPIquw5lSNMBlFVwZMomnuwn4bsTx3T2FB7knfY1VB53T9poGd9OPX3zpqh7gu1WyQ==}
peerDependencies:
react: npm:react@rc
react-dom: npm:react-dom@rc
@@ -7119,11 +7122,11 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
'@commitlint/cli@19.4.0(@types/node@22.5.0)(typescript@5.5.4)':
'@commitlint/cli@19.4.0(@types/node@22.5.1)(typescript@5.5.4)':
dependencies:
'@commitlint/format': 19.3.0
'@commitlint/lint': 19.2.2
'@commitlint/load': 19.4.0(@types/node@22.5.0)(typescript@5.5.4)
'@commitlint/load': 19.4.0(@types/node@22.5.1)(typescript@5.5.4)
'@commitlint/read': 19.4.0
'@commitlint/types': 19.0.3
execa: 8.0.1
@@ -7170,7 +7173,7 @@ snapshots:
'@commitlint/rules': 19.0.3
'@commitlint/types': 19.0.3
'@commitlint/load@19.4.0(@types/node@22.5.0)(typescript@5.5.4)':
'@commitlint/load@19.4.0(@types/node@22.5.1)(typescript@5.5.4)':
dependencies:
'@commitlint/config-validator': 19.0.3
'@commitlint/execute-rule': 19.0.0
@@ -7178,7 +7181,7 @@ snapshots:
'@commitlint/types': 19.0.3
chalk: 5.3.0
cosmiconfig: 9.0.0(typescript@5.5.4)
cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.0)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -7636,13 +7639,13 @@ snapshots:
postcss: 7.0.32
purgecss: 2.3.0
'@generouted/react-router@1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
'@generouted/react-router@1.19.6(react-router-dom@6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
dependencies:
fast-glob: 3.3.2
generouted: 1.19.6(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
generouted: 1.19.6(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))
react: 19.0.0-rc-e948a5ac-20240807
react-router-dom: 6.26.1(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
'@humanwhocodes/config-array@0.11.14':
dependencies:
@@ -7668,7 +7671,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@iconify/json@2.2.241':
'@iconify/json@2.2.242':
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
@@ -7728,23 +7731,23 @@ snapshots:
'@material/material-color-utilities@0.3.0': {}
'@microsoft/api-extractor-model@7.29.4(@types/node@22.5.0)':
'@microsoft/api-extractor-model@7.29.4(@types/node@22.5.1)':
dependencies:
'@microsoft/tsdoc': 0.15.0
'@microsoft/tsdoc-config': 0.17.0
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.0)
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.1)
transitivePeerDependencies:
- '@types/node'
'@microsoft/api-extractor@7.47.4(@types/node@22.5.0)':
'@microsoft/api-extractor@7.47.4(@types/node@22.5.1)':
dependencies:
'@microsoft/api-extractor-model': 7.29.4(@types/node@22.5.0)
'@microsoft/api-extractor-model': 7.29.4(@types/node@22.5.1)
'@microsoft/tsdoc': 0.15.0
'@microsoft/tsdoc-config': 0.17.0
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.0)
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.1)
'@rushstack/rig-package': 0.5.3
'@rushstack/terminal': 0.13.3(@types/node@22.5.0)
'@rushstack/ts-command-line': 4.22.3(@types/node@22.5.0)
'@rushstack/terminal': 0.13.3(@types/node@22.5.1)
'@rushstack/ts-command-line': 4.22.3(@types/node@22.5.1)
lodash: 4.17.21
minimatch: 3.0.8
resolve: 1.22.8
@@ -8300,7 +8303,7 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.21.0':
optional: true
'@rushstack/node-core-library@5.5.1(@types/node@22.5.0)':
'@rushstack/node-core-library@5.5.1(@types/node@22.5.1)':
dependencies:
ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0)
@@ -8311,23 +8314,23 @@ snapshots:
resolve: 1.22.8
semver: 7.5.4
optionalDependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
'@rushstack/rig-package@0.5.3':
dependencies:
resolve: 1.22.8
strip-json-comments: 3.1.1
'@rushstack/terminal@0.13.3(@types/node@22.5.0)':
'@rushstack/terminal@0.13.3(@types/node@22.5.1)':
dependencies:
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.0)
'@rushstack/node-core-library': 5.5.1(@types/node@22.5.1)
supports-color: 8.1.1
optionalDependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
'@rushstack/ts-command-line@4.22.3(@types/node@22.5.0)':
'@rushstack/ts-command-line@4.22.3(@types/node@22.5.1)':
dependencies:
'@rushstack/terminal': 0.13.3(@types/node@22.5.0)
'@rushstack/terminal': 0.13.3(@types/node@22.5.1)
'@types/argparse': 1.0.38
argparse: 1.0.10
string-argv: 0.3.2
@@ -8589,12 +8592,12 @@ snapshots:
dependencies:
'@types/http-cache-semantics': 4.0.4
'@types/keyv': 3.1.4
'@types/node': 22.5.0
'@types/node': 22.5.1
'@types/responselike': 1.0.3
'@types/conventional-commits-parser@5.0.0':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
'@types/d3-array@3.2.1': {}
@@ -8730,7 +8733,7 @@ snapshots:
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 22.5.0
'@types/node': 22.5.1
'@types/geojson@7946.0.14': {}
@@ -8746,11 +8749,11 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
'@types/keyv@3.1.4':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
'@types/lodash-es@4.17.12':
dependencies:
@@ -8770,6 +8773,10 @@ snapshots:
dependencies:
undici-types: 6.19.6
'@types/node@22.5.1':
dependencies:
undici-types: 6.19.6
'@types/parse-json@4.0.2': {}
'@types/postcss-modules-local-by-default@4.0.2':
@@ -8788,7 +8795,7 @@ snapshots:
'@types/responselike@1.0.3':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
'@types/unist@2.0.10': {}
@@ -8796,7 +8803,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
optional: true
'@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)':
@@ -8882,21 +8889,21 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
'@vitejs/plugin-react-swc@3.7.0(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
'@vitejs/plugin-react-swc@3.7.0(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
dependencies:
'@swc/core': 1.6.1
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- '@swc/helpers'
'@vitejs/plugin-react@4.3.1(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
'@vitejs/plugin-react@4.3.1(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0))':
dependencies:
'@babel/core': 7.24.5
'@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.24.5)
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.5)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- supports-color
@@ -9460,9 +9467,9 @@ snapshots:
dependencies:
toggle-selection: 1.0.6
cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.0)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
dependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
cosmiconfig: 9.0.0(typescript@5.5.4)
jiti: 1.21.6
typescript: 5.5.4
@@ -10545,9 +10552,9 @@ snapshots:
functions-have-names@1.2.3: {}
generouted@1.19.6(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
generouted@1.19.6(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
gensync@1.0.0-beta.2: {}
@@ -11158,11 +11165,11 @@ snapshots:
kind-of@6.0.3: {}
knip@5.27.4(@types/node@22.5.0)(typescript@5.5.4):
knip@5.27.4(@types/node@22.5.1)(typescript@5.5.4):
dependencies:
'@nodelib/fs.walk': 1.2.8
'@snyk/github-codeowners': 1.1.0
'@types/node': 22.5.0
'@types/node': 22.5.1
easy-table: 1.2.0
enhanced-resolve: 5.17.1
fast-glob: 3.3.2
@@ -13358,14 +13365,14 @@ snapshots:
unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2
virtua@0.33.7(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807):
virtua@0.34.0(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807):
optionalDependencies:
react: 19.0.0-rc-e948a5ac-20240807
react-dom: 19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807)
vite-plugin-dts@4.0.3(@types/node@22.5.0)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-plugin-dts@4.0.3(@types/node@22.5.1)(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
'@microsoft/api-extractor': 7.47.4(@types/node@22.5.0)
'@microsoft/api-extractor': 7.47.4(@types/node@22.5.1)
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
'@volar/typescript': 2.4.0
'@vue/language-core': 2.0.29(typescript@5.5.4)
@@ -13377,7 +13384,7 @@ snapshots:
typescript: 5.5.4
vue-tsc: 2.0.29(typescript@5.5.4)
optionalDependencies:
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- '@types/node'
- rollup
@@ -13388,43 +13395,43 @@ snapshots:
esbuild: 0.19.12
monaco-editor: 0.51.0
vite-plugin-sass-dts@1.3.25(postcss@8.4.41)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-plugin-sass-dts@1.3.25(postcss@8.4.41)(prettier@3.3.3)(sass@1.77.8)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
postcss: 8.4.41
postcss-js: 4.0.1(postcss@8.4.41)
prettier: 3.3.3
sass: 1.77.8
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite-plugin-svgr@4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-plugin-svgr@4.2.0(rollup@4.21.0)(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.21.0)
'@svgr/core': 8.1.0(typescript@5.5.4)
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.4))
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- rollup
- supports-color
- typescript
vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
vite-tsconfig-paths@5.0.1(typescript@5.5.4)(vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)):
dependencies:
debug: 4.3.6
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.5.4)
optionalDependencies:
vite: 5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
vite: 5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0)
transitivePeerDependencies:
- supports-color
- typescript
vite@5.4.2(@types/node@22.5.0)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0):
vite@5.4.2(@types/node@22.5.1)(less@4.2.0)(sass@1.77.8)(stylus@0.62.0):
dependencies:
esbuild: 0.21.5
postcss: 8.4.41
rollup: 4.21.0
optionalDependencies:
'@types/node': 22.5.0
'@types/node': 22.5.1
fsevents: 2.3.3
less: 4.2.0
sass: 1.77.8

View File

@@ -4,8 +4,6 @@
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/Ehco1996/ehco)
[![Docker Pulls](https://img.shields.io/docker/pulls/ehco1996/ehco)](https://hub.docker.com/r/ehco1996/ehco)
[see Readme in English here](README_EN.md)
## Ehco Relay - 让流量转发更简单
ehco 现在提供 SaaS软件即服务版本这是一个全托管的解决方案旨在为那些希望在不搭建和管理自己的服务器的情况下享受 ehco 强大流量转发能力的用户提供服务。
@@ -16,5 +14,5 @@ ehco 现在提供 SaaS软件即服务版本这是一个全托管的解
## 主要功能
- tcp/udp relay
- tunnel relay (ws/wss/mwss/mtcp)
- tunnel relay (ws/wss/)
- [更多功能请探索文档](https://docs.ehco-relay.cc/)

View File

@@ -1,23 +0,0 @@
# Ehco
ehco is a network relay tool and a typo :)
[![Go Report Card](https://goreportcard.com/badge/github.com/Ehco1996/ehco)](https://goreportcard.com/report/github.com/Ehco1996/ehco)
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/Ehco1996/ehco)
[![Docker Pulls](https://img.shields.io/docker/pulls/ehco1996/ehco)](https://hub.docker.com/r/ehco1996/ehco)
## Quick Start
let's see some examples
> relay all tcp traffic from `0.0.0.0:1234` to `0.0.0.0:5201`
`ehco -l 0.0.0.0:1234 -r 0.0.0.0:5201`
> also relay udp traffic to `0.0.0.0:5201`
`ehco -l 0.0.0.0:1234 -r 0.0.0.0:5201 -ur 0.0.0.0:5201`
## Advanced Usage
TBD, for now, you can see more examples in [ReadmeCN](README.md)

View File

@@ -9,15 +9,18 @@
"listen_type": "raw",
"transport_type": "raw",
"label": "relay1",
"tcp_remotes": [
"remotes": [
"0.0.0.0:5201"
]
],
"options": {
"enable_udp": true
}
},
{
"listen": "127.0.0.1:1235",
"listen_type": "raw",
"transport_type": "ws",
"tcp_remotes": [
"remotes": [
"ws://0.0.0.0:2443"
]
},
@@ -25,7 +28,7 @@
"listen": "127.0.0.1:1236",
"listen_type": "raw",
"transport_type": "wss",
"tcp_remotes": [
"remotes": [
"wss://0.0.0.0:3443"
]
},
@@ -33,7 +36,7 @@
"listen": "127.0.0.1:2443",
"listen_type": "ws",
"transport_type": "raw",
"tcp_remotes": [
"remotes": [
"0.0.0.0:5201"
]
},
@@ -41,7 +44,7 @@
"listen": "127.0.0.1:3443",
"listen_type": "wss",
"transport_type": "raw",
"tcp_remotes": [
"remotes": [
"0.0.0.0:5201"
]
}

View File

@@ -28,7 +28,6 @@ type RoundRobin interface {
type roundrobin struct {
nodeList []*Node
next *atomic.Int64
len int
}

View File

@@ -114,10 +114,10 @@ func (r *Config) Adjust() error {
if r.Options == nil {
r.Options = newDefaultOptions()
} else {
r.Options.DialTimeout = getDuration(r.Options.DialTimeoutSec, r.Options.DialTimeout)
r.Options.IdleTimeout = getDuration(r.Options.IdleTimeoutSec, r.Options.IdleTimeout)
r.Options.ReadTimeout = getDuration(r.Options.ReadTimeoutSec, r.Options.ReadTimeout)
r.Options.SniffTimeout = getDuration(r.Options.SniffTimeoutSec, r.Options.SniffTimeout)
r.Options.DialTimeout = getDuration(r.Options.DialTimeoutSec, constant.DefaultDialTimeOut)
r.Options.IdleTimeout = getDuration(r.Options.IdleTimeoutSec, constant.DefaultIdleTimeOut)
r.Options.ReadTimeout = getDuration(r.Options.ReadTimeoutSec, constant.DefaultReadTimeOut)
r.Options.SniffTimeout = getDuration(r.Options.SniffTimeoutSec, constant.DefaultSniffTimeOut)
}
return nil
}

View File

@@ -5,17 +5,17 @@ import (
"fmt"
"net"
"github.com/sagernet/sing-box/common/sniff"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"go.uber.org/zap"
"github.com/Ehco1996/ehco/internal/cmgr"
"github.com/Ehco1996/ehco/internal/conn"
"github.com/Ehco1996/ehco/internal/constant"
"github.com/Ehco1996/ehco/internal/lb"
"github.com/Ehco1996/ehco/internal/metrics"
"github.com/Ehco1996/ehco/internal/relay/conf"
"github.com/sagernet/sing-box/common/sniff"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"go.uber.org/zap"
)
var _ RelayServer = &BaseRelayServer{}
@@ -43,8 +43,10 @@ func newBaseRelayServer(cfg *conf.Config, cmgr cmgr.Cmgr) (*BaseRelayServer, err
}, nil
}
func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn) error {
remote := b.remotes.Next().Clone()
func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn, remote *lb.Node) error {
if remote == nil {
remote = b.remotes.Next().Clone()
}
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Inc()
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Dec()
@@ -69,8 +71,10 @@ func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn) error {
return b.handleRelayConn(c, rc, remote, metrics.METRIC_CONN_TYPE_TCP)
}
func (b *BaseRelayServer) RelayUDPConn(ctx context.Context, c net.Conn) error {
remote := b.remotes.Next().Clone()
func (b *BaseRelayServer) RelayUDPConn(ctx context.Context, c net.Conn, remote *lb.Node) error {
if remote == nil {
remote = b.remotes.Next().Clone()
}
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_UDP).Inc()
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_UDP).Dec()

View File

@@ -33,8 +33,8 @@ type RelayServer interface {
ListenAndServe(ctx context.Context) error
Close() error
RelayTCPConn(ctx context.Context, c net.Conn) error
RelayUDPConn(ctx context.Context, c net.Conn) error
RelayTCPConn(ctx context.Context, c net.Conn, remote *lb.Node) error
RelayUDPConn(ctx context.Context, c net.Conn, remote *lb.Node) error
HealthCheck(ctx context.Context) (int64, error) // latency in ms
}

View File

@@ -99,7 +99,7 @@ func (s *RawServer) ListenAndServe(ctx context.Context) error {
}
go func(c net.Conn) {
defer c.Close()
if err := s.RelayTCPConn(ctx, c); err != nil {
if err := s.RelayTCPConn(ctx, c, nil); err != nil {
s.l.Errorf("RelayTCPConn meet error: %s", err.Error())
}
}(c)
@@ -118,7 +118,7 @@ func (s *RawServer) listenUDP(ctx context.Context) error {
return err
}
go func() {
if err := s.RelayUDPConn(ctx, c); err != nil {
if err := s.RelayUDPConn(ctx, c, nil); err != nil {
s.l.Errorf("RelayUDPConn meet error: %s", err.Error())
}
}()

View File

@@ -97,15 +97,21 @@ func (s *WsServer) handleRequest(w http.ResponseWriter, req *http.Request) {
if err != nil {
return
}
var remote *lb.Node
if addr := req.URL.Query().Get(conf.WS_QUERY_REMOTE_ADDR); addr != "" {
remote = &lb.Node{Address: addr, Label: addr}
}
if req.URL.Query().Get("type") == "udp" {
if !s.cfg.Options.EnableUDP {
s.l.Error("udp not support but request with udp type")
wsc.Close()
return
}
err = s.RelayUDPConn(req.Context(), conn.NewWSConn(wsc, true))
err = s.RelayUDPConn(req.Context(), conn.NewWSConn(wsc, true), remote)
} else {
err = s.RelayTCPConn(req.Context(), conn.NewWSConn(wsc, true))
err = s.RelayTCPConn(req.Context(), conn.NewWSConn(wsc, true), remote)
}
if err != nil {
s.l.Errorf("handleRequest meet error:%s", err)

View File

@@ -46,6 +46,14 @@ func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
return set.ToIPSet().Contains(ip.WithZone(""))
}
// MatchIp implements C.IpMatcher
func (set *IpCidrSet) MatchIp(ip netip.Addr) bool {
if set.IsEmpty() {
return false
}
return set.IsContain(ip)
}
func (set *IpCidrSet) Merge() error {
var b netipx.IPSetBuilder
b.AddSet(set.ToIPSet())

View File

@@ -35,7 +35,7 @@ type Pool struct {
offset netip.Addr
cycle bool
mux sync.Mutex
host []C.Rule
host []C.DomainMatcher
ipnet netip.Prefix
store store
}
@@ -66,8 +66,8 @@ func (p *Pool) LookBack(ip netip.Addr) (string, bool) {
// ShouldSkipped return if domain should be skipped
func (p *Pool) ShouldSkipped(domain string) bool {
for _, rule := range p.host {
if match, _ := rule.Match(&C.Metadata{Host: domain}); match {
for _, matcher := range p.host {
if matcher.MatchDomain(domain) {
return true
}
}
@@ -156,7 +156,7 @@ func (p *Pool) restoreState() {
type Options struct {
IPNet netip.Prefix
Host []C.Rule
Host []C.DomainMatcher
// Size sets the maximum number of entries in memory
// and does not work if Persistence is true

View File

@@ -10,7 +10,6 @@ import (
"github.com/metacubex/mihomo/component/profile/cachefile"
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
RP "github.com/metacubex/mihomo/rules/provider"
"github.com/metacubex/bbolt"
"github.com/stretchr/testify/assert"
@@ -157,7 +156,7 @@ func TestPool_Skip(t *testing.T) {
pools, tempfile, err := createPools(Options{
IPNet: ipnet,
Size: 10,
Host: []C.Rule{RP.NewDomainSet(tree.NewDomainSet(), "")},
Host: []C.DomainMatcher{tree.NewDomainSet()},
})
assert.Nil(t, err)
defer os.Remove(tempfile)

View File

@@ -22,23 +22,23 @@ var (
type Dispatcher struct {
enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain []C.Rule
skipSrcAddress []C.Rule
skipDstAddress []C.Rule
skipDomain []C.Rule
forceDomain []C.DomainMatcher
skipSrcAddress []C.IpMatcher
skipDstAddress []C.IpMatcher
skipDomain []C.DomainMatcher
skipList *lru.LruCache[netip.AddrPort, uint8]
forceDnsMapping bool
parsePureIp bool
}
func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
for _, rule := range sd.skipDstAddress {
if ok, _ := rule.Match(&C.Metadata{DstIP: metadata.DstIP}); ok {
for _, matcher := range sd.skipDstAddress {
if matcher.MatchIp(metadata.DstIP) {
return false
}
}
for _, rule := range sd.skipSrcAddress {
if ok, _ := rule.Match(&C.Metadata{DstIP: metadata.SrcIP}); ok {
for _, matcher := range sd.skipSrcAddress {
if matcher.MatchIp(metadata.SrcIP) {
return false
}
}
@@ -48,8 +48,8 @@ func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
return true
}
for _, rule := range sd.forceDomain {
if ok, _ := rule.Match(&C.Metadata{Host: metadata.Host}); ok {
for _, matcher := range sd.forceDomain {
if matcher.MatchDomain(metadata.Host) {
return true
}
}
@@ -112,8 +112,8 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
return false
}
for _, rule := range sd.skipDomain {
if ok, _ := rule.Match(&C.Metadata{Host: host}); ok {
for _, matcher := range sd.skipDomain {
if matcher.MatchDomain(host) {
log.Debugln("[Sniffer] Skip sni[%s]", host)
return false
}
@@ -200,10 +200,10 @@ func (sd *Dispatcher) cacheSniffFailed(metadata *C.Metadata) {
type Config struct {
Enable bool
Sniffers map[sniffer.Type]SnifferConfig
ForceDomain []C.Rule
SkipSrcAddress []C.Rule
SkipDstAddress []C.Rule
SkipDomain []C.Rule
ForceDomain []C.DomainMatcher
SkipSrcAddress []C.IpMatcher
SkipDstAddress []C.IpMatcher
SkipDomain []C.DomainMatcher
ForceDnsMapping bool
ParsePureIp bool
}

View File

@@ -172,6 +172,11 @@ func (ss *DomainSet) Foreach(f func(key string) bool) {
})
}
// MatchDomain implements C.DomainMatcher
func (ss *DomainSet) MatchDomain(domain string) bool {
return ss.Has(domain)
}
func setBit(bm *[]uint64, i int, v int) {
for i>>6 >= len(*bm) {
*bm = append(*bm, 0)

View File

@@ -143,8 +143,8 @@ type DNS struct {
UseSystemHosts bool
NameServer []dns.NameServer
Fallback []dns.NameServer
FallbackIPFilter []C.Rule
FallbackDomainFilter []C.Rule
FallbackIPFilter []C.IpMatcher
FallbackDomainFilter []C.DomainMatcher
Listen string
EnhancedMode C.DNSMode
DefaultNameserver []dns.NameServer
@@ -640,7 +640,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.Hosts = hosts
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders)
dnsCfg, err := parseDNS(rawCfg, hosts, ruleProviders)
if err != nil {
return nil, err
}
@@ -1297,7 +1297,7 @@ func parsePureDNSServer(server string) string {
}
}
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) {
func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], ruleProviders map[string]providerTypes.RuleProvider, respectRules bool, preferH3 bool) ([]dns.Policy, error) {
var policy []dns.Policy
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
@@ -1350,18 +1350,18 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules [
if strings.HasPrefix(domain, "rule-set:") {
domainSetName := domain[9:]
rule, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders)
matcher, err := parseDomainRuleSet(domainSetName, "dns.nameserver-policy", ruleProviders)
if err != nil {
return nil, err
}
policy[idx] = dns.Policy{Rule: rule, NameServers: nameservers}
policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers}
} else if strings.HasPrefix(domain, "geosite:") {
country := domain[8:]
rule, err := RC.NewGEOSITE(country, "dns.nameserver-policy")
matcher, err := RC.NewGEOSITE(country, "dns.nameserver-policy")
if err != nil {
return nil, err
}
policy[idx] = dns.Policy{Rule: rule, NameServers: nameservers}
policy[idx] = dns.Policy{Matcher: matcher, NameServers: nameservers}
} else {
if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
@@ -1372,7 +1372,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rules [
return policy, nil
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) {
cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@@ -1400,7 +1400,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err
}
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, rules, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.RespectRules, cfg.PreferH3); err != nil {
return nil, err
}
@@ -1467,14 +1467,13 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
dnsCfg.FakeIPRange = pool
}
var rule C.Rule
if len(cfg.Fallback) != 0 {
if cfg.FallbackFilter.GeoIP {
rule, err = RC.NewGEOIP(cfg.FallbackFilter.GeoIPCode, "dns.fallback-filter.geoip", false, true)
matcher, err := RC.NewGEOIP(cfg.FallbackFilter.GeoIPCode, "dns.fallback-filter.geoip", false, true)
if err != nil {
return nil, fmt.Errorf("load GeoIP dns fallback filter error, %w", err)
}
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, matcher)
}
if len(cfg.FallbackFilter.IPCIDR) > 0 {
cidrSet := cidr.NewIpCidrSet()
@@ -1488,8 +1487,8 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
if err != nil {
return nil, err
}
rule = RP.NewIpCidrSet(cidrSet, "dns.fallback-filter.ipcidr")
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, rule)
matcher := cidrSet // dns.fallback-filter.ipcidr
dnsCfg.FallbackIPFilter = append(dnsCfg.FallbackIPFilter, matcher)
}
if len(cfg.FallbackFilter.Domain) > 0 {
domainTrie := trie.New[struct{}]()
@@ -1499,17 +1498,17 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, fmt.Errorf("DNS FallbackDomain[%d] format error: %w", idx, err)
}
}
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), "dns.fallback-filter.domain")
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, rule)
matcher := domainTrie.NewDomainSet() // dns.fallback-filter.domain
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, matcher)
}
if len(cfg.FallbackFilter.GeoSite) > 0 {
log.Warnln("replace fallback-filter.geosite with nameserver-policy, it will be removed in the future")
for idx, geoSite := range cfg.FallbackFilter.GeoSite {
rule, err = RC.NewGEOSITE(geoSite, "dns.fallback-filter.geosite")
matcher, err := RC.NewGEOSITE(geoSite, "dns.fallback-filter.geosite")
if err != nil {
return nil, fmt.Errorf("DNS FallbackGeosite[%d] format error: %w", idx, err)
}
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, rule)
dnsCfg.FallbackDomainFilter = append(dnsCfg.FallbackDomainFilter, matcher)
}
}
}
@@ -1701,8 +1700,8 @@ func parseSniffer(snifferRaw RawSniffer, ruleProviders map[string]providerTypes.
return snifferConfig, nil
}
func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (ipRules []C.Rule, err error) {
var rule C.Rule
func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (matchers []C.IpMatcher, err error) {
var matcher C.IpMatcher
for _, ipcidr := range addresses {
ipcidrLower := strings.ToLower(ipcidr)
if strings.Contains(ipcidrLower, "geoip:") {
@@ -1710,22 +1709,22 @@ func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, country := range subkeys {
rule, err = RC.NewGEOIP(country, adapterName, false, false)
matcher, err = RC.NewGEOIP(country, adapterName, false, false)
if err != nil {
return nil, err
}
ipRules = append(ipRules, rule)
matchers = append(matchers, matcher)
}
} else if strings.Contains(ipcidrLower, "rule-set:") {
subkeys := strings.Split(ipcidr, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, domainSetName := range subkeys {
rule, err = parseIPRuleSet(domainSetName, adapterName, ruleProviders)
matcher, err = parseIPRuleSet(domainSetName, adapterName, ruleProviders)
if err != nil {
return nil, err
}
ipRules = append(ipRules, rule)
matchers = append(matchers, matcher)
}
} else {
if cidrSet == nil {
@@ -1742,14 +1741,14 @@ func parseIPCIDR(addresses []string, cidrSet *cidr.IpCidrSet, adapterName string
if err != nil {
return nil, err
}
rule = RP.NewIpCidrSet(cidrSet, adapterName)
ipRules = append(ipRules, rule)
matcher = cidrSet
matchers = append(matchers, matcher)
}
return
}
func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (domainRules []C.Rule, err error) {
var rule C.Rule
func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (matchers []C.DomainMatcher, err error) {
var matcher C.DomainMatcher
for _, domain := range domains {
domainLower := strings.ToLower(domain)
if strings.Contains(domainLower, "geosite:") {
@@ -1757,22 +1756,22 @@ func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapte
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, country := range subkeys {
rule, err = RC.NewGEOSITE(country, adapterName)
matcher, err = RC.NewGEOSITE(country, adapterName)
if err != nil {
return nil, err
}
domainRules = append(domainRules, rule)
matchers = append(matchers, matcher)
}
} else if strings.Contains(domainLower, "rule-set:") {
subkeys := strings.Split(domain, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, domainSetName := range subkeys {
rule, err = parseDomainRuleSet(domainSetName, adapterName, ruleProviders)
matcher, err = parseDomainRuleSet(domainSetName, adapterName, ruleProviders)
if err != nil {
return nil, err
}
domainRules = append(domainRules, rule)
matchers = append(matchers, matcher)
}
} else {
if domainTrie == nil {
@@ -1785,13 +1784,13 @@ func parseDomain(domains []string, domainTrie *trie.DomainTrie[struct{}], adapte
}
}
if !domainTrie.IsEmpty() {
rule = RP.NewDomainSet(domainTrie.NewDomainSet(), adapterName)
domainRules = append(domainRules, rule)
matcher = domainTrie.NewDomainSet()
matchers = append(matchers, matcher)
}
return
}
func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.Rule, error) {
func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.IpMatcher, error) {
if rp, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {
@@ -1806,7 +1805,7 @@ func parseIPRuleSet(domainSetName string, adapterName string, ruleProviders map[
return RP.NewRuleSet(domainSetName, adapterName, true)
}
func parseDomainRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.Rule, error) {
func parseDomainRuleSet(domainSetName string, adapterName string, ruleProviders map[string]providerTypes.RuleProvider) (C.DomainMatcher, error) {
if rp, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {

View File

@@ -0,0 +1,11 @@
package constant
import "net/netip"
type DomainMatcher interface {
MatchDomain(domain string) bool
}
type IpMatcher interface {
MatchIp(ip netip.Addr) bool
}

View File

@@ -133,7 +133,9 @@ type Metadata struct {
Type Type `json:"type"`
SrcIP netip.Addr `json:"sourceIP"`
DstIP netip.Addr `json:"destinationIP"`
SrcGeoIP []string `json:"sourceGeoIP"` // can be nil if never queried, empty slice if got no result
DstGeoIP []string `json:"destinationGeoIP"` // can be nil if never queried, empty slice if got no result
SrcIPASN string `json:"sourceIPASN"`
DstIPASN string `json:"destinationIPASN"`
SrcPort uint16 `json:"sourcePort,string"` // `,string` is used to compatible with old version json output
DstPort uint16 `json:"destinationPort,string"` // `,string` is used to compatible with old version json output

View File

@@ -27,8 +27,6 @@ const (
ProcessNameRegex
ProcessPathRegex
RuleSet
DomainSet
IpCidrSet
Network
Uid
SubRules
@@ -92,10 +90,6 @@ func (rt RuleType) String() string {
return "Match"
case RuleSet:
return "RuleSet"
case DomainSet:
return "DomainSet"
case IpCidrSet:
return "IpCidrSet"
case Network:
return "Network"
case DSCP:

View File

@@ -21,13 +21,13 @@ func (p domainTriePolicy) Match(domain string) []dnsClient {
return nil
}
type domainRulePolicy struct {
rule C.Rule
type domainMatcherPolicy struct {
matcher C.DomainMatcher
dnsClients []dnsClient
}
func (p domainRulePolicy) Match(domain string) []dnsClient {
if ok, _ := p.rule.Match(&C.Metadata{Host: domain}); ok {
func (p domainMatcherPolicy) Match(domain string) []dnsClient {
if p.matcher.MatchDomain(domain) {
return p.dnsClients
}
return nil

View File

@@ -42,8 +42,8 @@ type Resolver struct {
hosts *trie.DomainTrie[resolver.HostValue]
main []dnsClient
fallback []dnsClient
fallbackDomainFilters []C.Rule
fallbackIPFilters []C.Rule
fallbackDomainFilters []C.DomainMatcher
fallbackIPFilters []C.IpMatcher
group singleflight.Group[*D.Msg]
cache dnsCache
policy []dnsPolicy
@@ -119,7 +119,7 @@ func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, e
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
for _, filter := range r.fallbackIPFilters {
if ok, _ := filter.Match(&C.Metadata{DstIP: ip}); ok {
if filter.MatchIp(ip) {
return true
}
}
@@ -275,7 +275,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
}
for _, df := range r.fallbackDomainFilters {
if ok, _ := df.Match(&C.Metadata{Host: domain}); ok {
if df.MatchDomain(domain) {
return true
}
}
@@ -398,7 +398,7 @@ func (ns NameServer) Equal(ns2 NameServer) bool {
type Policy struct {
Domain string
Rule C.Rule
Matcher C.DomainMatcher
NameServers []NameServer
}
@@ -409,8 +409,8 @@ type Config struct {
IPv6 bool
IPv6Timeout uint
EnhancedMode C.DNSMode
FallbackIPFilter []C.Rule
FallbackDomainFilter []C.Rule
FallbackIPFilter []C.IpMatcher
FallbackDomainFilter []C.DomainMatcher
Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue]
Policy []Policy
@@ -495,8 +495,8 @@ func NewResolver(config Config) *Resolver {
}
for _, policy := range config.Policy {
if policy.Rule != nil {
insertPolicy(domainRulePolicy{rule: policy.Rule, dnsClients: cacheTransform(policy.NameServers)})
if policy.Matcher != nil {
insertPolicy(domainMatcherPolicy{matcher: policy.Matcher, dnsClients: cacheTransform(policy.NameServers)})
} else {
if triePolicy == nil {
triePolicy = trie.New[[]dnsClient]()

View File

@@ -3,6 +3,7 @@ package common
import (
"errors"
"fmt"
"net/netip"
"strings"
"github.com/metacubex/mihomo/component/geodata"
@@ -11,6 +12,8 @@ import (
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"golang.org/x/exp/slices"
)
type GEOIP struct {
@@ -41,52 +44,84 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
}
if g.country == "lan" {
return g.isLan(ip), g.adapter
}
if C.GeodataMode {
if g.isSourceIP {
if slices.Contains(metadata.SrcGeoIP, g.country) {
return true, g.adapter
}
} else {
if slices.Contains(metadata.DstGeoIP, g.country) {
return true, g.adapter
}
}
matcher, err := g.getIPMatcher()
if err != nil {
return false, ""
}
match := matcher.Match(ip)
if match {
if g.isSourceIP {
metadata.SrcGeoIP = append(metadata.SrcGeoIP, g.country)
} else {
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
}
}
return match, g.adapter
}
if g.isSourceIP {
if metadata.SrcGeoIP != nil {
return slices.Contains(metadata.SrcGeoIP, g.country), g.adapter
}
} else {
if metadata.DstGeoIP != nil {
return slices.Contains(metadata.DstGeoIP, g.country), g.adapter
}
}
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
if g.isSourceIP {
metadata.SrcGeoIP = codes
} else {
metadata.DstGeoIP = codes
}
if slices.Contains(codes, g.country) {
return true, g.adapter
}
return false, ""
}
// MatchIp implements C.IpMatcher
func (g *GEOIP) MatchIp(ip netip.Addr) bool {
if !ip.IsValid() {
return false
}
if g.country == "lan" {
return g.isLan(ip)
}
if C.GeodataMode {
matcher, err := g.getIPMatcher()
if err != nil {
return false
}
return matcher.Match(ip)
}
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
return slices.Contains(codes, g.country)
}
func (g *GEOIP) isLan(ip netip.Addr) bool {
return ip.IsPrivate() ||
ip.IsUnspecified() ||
ip.IsLoopback() ||
ip.IsMulticast() ||
ip.IsLinkLocalUnicast() ||
resolver.IsFakeBroadcastIP(ip), g.adapter
}
for _, code := range metadata.DstGeoIP {
if g.country == code {
return true, g.adapter
}
}
if !C.GeodataMode {
if g.isSourceIP {
codes := mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range codes {
if g.country == code {
return true, g.adapter
}
}
return false, g.adapter
}
if metadata.DstGeoIP != nil {
return false, g.adapter
}
metadata.DstGeoIP = mmdb.IPInstance().LookupCode(ip.AsSlice())
for _, code := range metadata.DstGeoIP {
if g.country == code {
return true, g.adapter
}
}
return false, g.adapter
}
matcher, err := g.GetIPMatcher()
if err != nil {
return false, ""
}
match := matcher.Match(ip)
if match && !g.isSourceIP {
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
}
return match, g.adapter
resolver.IsFakeBroadcastIP(ip)
}
func (g *GEOIP) Adapter() string {
@@ -106,14 +141,19 @@ func (g *GEOIP) GetCountry() string {
}
func (g *GEOIP) GetIPMatcher() (router.IPMatcher, error) {
if g.geodata {
if C.GeodataMode {
return g.getIPMatcher()
}
return nil, errors.New("not geodata mode")
}
func (g *GEOIP) getIPMatcher() (router.IPMatcher, error) {
geoIPMatcher, err := geodata.LoadGeoIPMatcher(g.country)
if err != nil {
return nil, fmt.Errorf("[GeoIP] %w", err)
}
return geoIPMatcher, nil
}
return nil, errors.New("geoip country not set")
}
func (g *GEOIP) GetRecodeSize() int {
@@ -141,12 +181,13 @@ func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP,
return geoip, nil
}
geoip.geodata = true
geoIPMatcher, err := geoip.GetIPMatcher() // test load
if C.GeodataMode {
geoIPMatcher, err := geoip.getIPMatcher() // test load
if err != nil {
return nil, err
}
log.Infoln("Finished initial GeoIP rule %s => %s, records: %d", country, adapter, geoIPMatcher.Count())
}
return geoip, nil
}

View File

@@ -23,15 +23,19 @@ func (gs *GEOSITE) RuleType() C.RuleType {
}
func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) {
domain := metadata.RuleHost()
return gs.MatchDomain(metadata.RuleHost()), gs.adapter
}
// MatchDomain implements C.DomainMatcher
func (gs *GEOSITE) MatchDomain(domain string) bool {
if len(domain) == 0 {
return false, ""
return false
}
matcher, err := gs.GetDomainMatcher()
if err != nil {
return false, ""
return false
}
return matcher.ApplyDomain(domain), gs.adapter
return matcher.ApplyDomain(domain)
}
func (gs *GEOSITE) Adapter() string {

View File

@@ -28,8 +28,11 @@ func (a *ASN) Match(metadata *C.Metadata) (bool, string) {
result := mmdb.ASNInstance().LookupASN(ip.AsSlice())
asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10)
if !a.isSourceIP {
metadata.DstIPASN = asnNumber + " " + result.AutonomousSystemOrganization
ipASN := asnNumber + " " + result.AutonomousSystemOrganization
if a.isSourceIP {
metadata.SrcIPASN = ipASN
} else {
metadata.DstIPASN = ipASN
}
match := a.asn == asnNumber

View File

@@ -1,40 +0,0 @@
package provider
import (
"github.com/metacubex/mihomo/component/trie"
C "github.com/metacubex/mihomo/constant"
)
type DomainSet struct {
*domainStrategy
adapter string
}
func (d *DomainSet) ProviderNames() []string {
return nil
}
func (d *DomainSet) RuleType() C.RuleType {
return C.DomainSet
}
func (d *DomainSet) Match(metadata *C.Metadata) (bool, string) {
return d.domainStrategy.Match(metadata), d.adapter
}
func (d *DomainSet) Adapter() string {
return d.adapter
}
func (d *DomainSet) Payload() string {
return ""
}
func NewDomainSet(domainSet *trie.DomainSet, adapter string) *DomainSet {
return &DomainSet{
domainStrategy: &domainStrategy{domainSet: domainSet},
adapter: adapter,
}
}
var _ C.Rule = (*DomainSet)(nil)

View File

@@ -1,40 +0,0 @@
package provider
import (
"github.com/metacubex/mihomo/component/cidr"
C "github.com/metacubex/mihomo/constant"
)
type IpCidrSet struct {
*ipcidrStrategy
adapter string
}
func (d *IpCidrSet) ProviderNames() []string {
return nil
}
func (d *IpCidrSet) RuleType() C.RuleType {
return C.IpCidrSet
}
func (d *IpCidrSet) Match(metadata *C.Metadata) (bool, string) {
return d.ipcidrStrategy.Match(metadata), d.adapter
}
func (d *IpCidrSet) Adapter() string {
return d.adapter
}
func (d *IpCidrSet) Payload() string {
return ""
}
func NewIpCidrSet(cidrSet *cidr.IpCidrSet, adapter string) *IpCidrSet {
return &IpCidrSet{
ipcidrStrategy: &ipcidrStrategy{cidrSet: cidrSet},
adapter: adapter,
}
}
var _ C.Rule = (*IpCidrSet)(nil)

View File

@@ -1,6 +1,8 @@
package provider
import (
"net/netip"
C "github.com/metacubex/mihomo/constant"
P "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/rules/common"
@@ -35,6 +37,18 @@ func (rs *RuleSet) Match(metadata *C.Metadata) (bool, string) {
return false, ""
}
// MatchDomain implements C.DomainMatcher
func (rs *RuleSet) MatchDomain(domain string) bool {
ok, _ := rs.Match(&C.Metadata{Host: domain})
return ok
}
// MatchIp implements C.IpMatcher
func (rs *RuleSet) MatchIp(ip netip.Addr) bool {
ok, _ := rs.Match(&C.Metadata{DstIP: ip})
return ok
}
func (rs *RuleSet) Adapter() string {
return rs.adapter
}

View File

@@ -1,12 +1,17 @@
version: 2
version: 2.1
orbs:
android: circleci/android@2.3.0
jobs:
build:
working_directory: ~/code
docker:
- image: cimg/android:2022.12.1-ndk
executor:
name: android/android-docker
tag: 2024.08.1-ndk
resource-class: large
environment:
GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process"
RUST_VERSION: 1.67.0
RUST_VERSION: 1.80.0
steps:
- checkout
- run: git submodule update --init --recursive
@@ -16,16 +21,11 @@ jobs:
command: |
echo 'export PATH="$HOME"/.cargo/bin:"$PATH"' >> "$BASH_ENV"
- run: rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
- restore_cache:
key: jars-{{ checksum "build.gradle.kts" }}
- android/restore-gradle-cache
- run:
name: Run Build and Tests
command: ./gradlew assembleDebug check -PCARGO_PROFILE=debug
- save_cache:
paths:
- ~/.gradle
- ~/.android/build-cache
key: jars-{{ checksum "build.gradle.kts" }}
- android/save-gradle-cache
- store_artifacts:
path: mobile/build/outputs/apk
destination: apk/mobile
@@ -44,3 +44,8 @@ jobs:
- store_artifacts:
path: tv/build/reports
destination: reports/tv
workflows:
test:
jobs:
- build

View File

@@ -1,7 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.github.ben-manes.versions") version "0.45.0"
id("com.github.ben-manes.versions") version "0.51.0"
id("com.google.devtools.ksp") version "2.0.20-1.0.24" apply false
}
buildscript {
@@ -18,11 +19,11 @@ buildscript {
classpath(rootProject.extra["androidPlugin"].toString())
classpath(kotlin("gradle-plugin", kotlinVersion))
classpath("com.google.android.gms:oss-licenses-plugin:0.10.6")
classpath("com.google.firebase:firebase-crashlytics-gradle:2.9.2")
classpath("com.google.gms:google-services:4.3.15")
classpath("com.vanniktech:gradle-maven-publish-plugin:0.24.0")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.7.20")
classpath("org.mozilla.rust-android-gradle:plugin:0.9.3")
classpath("com.google.firebase:firebase-crashlytics-gradle:3.0.2")
classpath("com.google.gms:google-services:4.4.2")
classpath("com.vanniktech:gradle-maven-publish-plugin:0.29.0")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.20")
classpath("org.mozilla.rust-android-gradle:plugin:0.9.4")
}
}
@@ -30,10 +31,6 @@ allprojects {
apply(from = "${rootProject.projectDir}/repositories.gradle.kts")
}
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}
// skip uploading the mapping to Crashlytics
subprojects {
tasks.whenTaskAdded {

View File

@@ -1,39 +1,34 @@
import com.android.build.VariantOutput
import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.AbstractAppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.api.ApkVariantOutputImpl
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByName
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import java.util.*
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import java.util.Locale
const val lifecycleVersion = "2.5.1"
const val lifecycleVersion = "2.8.4"
private val Project.android get() = extensions.getByName<BaseExtension>("android")
private val BaseExtension.lint get() = (this as CommonExtension<*, *, *, *>).lint
private val BaseExtension.lint get() = (this as CommonExtension<*, *, *, *, *, *>).lint
private val flavorRegex = "(assemble|generate)\\w*(Release|Debug)".toRegex()
val Project.currentFlavor get() = gradle.startParameter.taskRequests.toString().let { task ->
flavorRegex.find(task)?.groupValues?.get(2)?.toLowerCase(Locale.ROOT) ?: "debug".also {
flavorRegex.find(task)?.groupValues?.get(2)?.lowercase(Locale.ROOT) ?: "debug".also {
println("Warning: No match found for $task")
}
}
fun Project.setupCommon() {
val javaVersion = JavaVersion.VERSION_11
android.apply {
buildToolsVersion("33.0.1")
compileSdkVersion(33)
compileSdkVersion(34)
defaultConfig {
minSdk = 23
targetSdk = 33
targetSdk = 34
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
val javaVersion = JavaVersion.VERSION_11
compileOptions {
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
@@ -44,14 +39,14 @@ fun Project.setupCommon() {
informational += "MissingQuantity"
informational += "MissingTranslation"
}
(this as ExtensionAware).extensions.getByName<KotlinJvmOptions>("kotlinOptions").jvmTarget =
javaVersion.toString()
}
extensions.getByName<KotlinAndroidProjectExtension>("kotlin").compilerOptions.jvmTarget
.set(JvmTarget.fromTarget(javaVersion.toString()))
dependencies {
add("testImplementation", "junit:junit:4.13.2")
add("androidTestImplementation", "androidx.test:runner:1.5.2")
add("androidTestImplementation", "androidx.test.espresso:espresso-core:3.5.1")
add("androidTestImplementation", "androidx.test:runner:1.6.2")
add("androidTestImplementation", "androidx.test.espresso:espresso-core:3.6.1")
}
}
@@ -69,11 +64,11 @@ fun Project.setupCore() {
disable += "UseAppTint"
}
ndkVersion = "27.0.12077973"
buildFeatures.buildConfig = true
}
dependencies.add("coreLibraryDesugaring", "com.android.tools:desugar_jdk_libs:2.0.2")
dependencies.add("coreLibraryDesugaring", "com.android.tools:desugar_jdk_libs:2.1.0")
}
private val abiCodes = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86" to 3, "x86_64" to 4)
fun Project.setupApp() {
setupCore()
@@ -111,12 +106,4 @@ fun Project.setupApp() {
}
dependencies.add("implementation", project(":core"))
if (currentFlavor == "release") (android as AbstractAppExtension).applicationVariants.all {
for (output in outputs) {
abiCodes[(output as ApkVariantOutputImpl).getFilter(VariantOutput.ABI)]?.let { offset ->
output.versionCodeOverride = versionCode + offset
}
}
}
}

View File

@@ -2,9 +2,9 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn
plugins {
id("com.android.library")
id("com.google.devtools.ksp")
id("org.mozilla.rust-android-gradle.rust-android")
kotlin("android")
kotlin("kapt")
id("kotlin-parcelize")
}
@@ -21,8 +21,8 @@ android {
arguments("-j${Runtime.getRuntime().availableProcessors()}")
}
kapt.arguments {
arg("room.incremental", true)
ksp {
arg("room.incremental", "true")
arg("room.schemaLocation", "$projectDir/schemas")
}
}
@@ -32,6 +32,8 @@ android {
sourceSets.getByName("androidTest") {
assets.setSrcDirs(assets.srcDirs + files("$projectDir/schemas"))
}
buildFeatures.aidl = true
}
cargo {
@@ -51,13 +53,13 @@ cargo {
exec = { spec, toolchain ->
run {
try {
Runtime.getRuntime().exec("python3 -V >/dev/null 2>&1")
Runtime.getRuntime().exec(arrayOf("python3", "-V"))
spec.environment("RUST_ANDROID_GRADLE_PYTHON_COMMAND", "python3")
project.logger.lifecycle("Python 3 detected.")
} catch (e: java.io.IOException) {
project.logger.lifecycle("No python 3 detected.")
try {
Runtime.getRuntime().exec("python -V >/dev/null 2>&1")
Runtime.getRuntime().exec(arrayOf("python", "-V"))
spec.environment("RUST_ANDROID_GRADLE_PYTHON_COMMAND", "python")
project.logger.lifecycle("Python detected.")
} catch (e: java.io.IOException) {
@@ -84,28 +86,28 @@ tasks.register<Exec>("cargoClean") {
tasks.clean.dependsOn("cargoClean")
dependencies {
val coroutinesVersion = "1.6.4"
val roomVersion = "2.5.0"
val workVersion = "2.7.1"
val coroutinesVersion = "1.8.1"
val roomVersion = "2.6.1"
val workVersion = "2.9.1"
api(project(":plugin"))
api("androidx.core:core-ktx:1.9.0")
api("com.google.android.material:material:1.8.0")
api("androidx.core:core-ktx:1.13.1")
api("com.google.android.material:material:1.12.0")
api("androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycleVersion")
api("androidx.preference:preference:1.2.0")
api("androidx.preference:preference:1.2.1")
api("androidx.room:room-runtime:$roomVersion")
api("androidx.work:work-multiprocess:$workVersion")
api("androidx.work:work-runtime-ktx:$workVersion")
api("com.google.android.gms:play-services-oss-licenses:17.0.0")
api("com.google.code.gson:gson:2.10.1")
api("com.google.firebase:firebase-analytics-ktx:21.2.0")
api("com.google.firebase:firebase-crashlytics:18.3.3")
api("com.google.android.gms:play-services-oss-licenses:17.1.0")
api("com.google.code.gson:gson:2.11.0")
api("com.google.firebase:firebase-analytics-ktx:22.1.0")
api("com.google.firebase:firebase-crashlytics:19.0.3")
api("com.jakewharton.timber:timber:5.0.1")
api("dnsjava:dnsjava:3.5.2")
api("dnsjava:dnsjava:3.6.1")
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
api("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion")
kapt("androidx.room:room-compiler:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
androidTestImplementation("androidx.room:room-testing:$roomVersion")
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5")
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
}

View File

@@ -17,6 +17,11 @@
-keepattributes SourceFile,LineNumberTable
-dontobfuscate
-dontwarn lombok.Generated
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.xbill.DNS.spi.DnsjavaInetAddressResolverProvider
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -5,6 +5,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
@@ -27,7 +29,6 @@
android:fullBackupOnly="true"
android:hasFragileUserData="true"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:banner="@mipmap/banner">
@@ -43,7 +44,9 @@
android:directBootAware="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false">
android:foregroundServiceType="systemExempted"
android:exported="false"
tools:ignore="ForegroundServicePermission">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
@@ -53,14 +56,22 @@
android:name="com.github.shadowsocks.bg.TransproxyService"
android:process=":bg"
android:directBootAware="true"
android:foregroundServiceType="specialUse"
android:exported="false">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Proxy service that handles incoming SOCKS5 and Transproxy traffic" />
</service>
<service
android:name="com.github.shadowsocks.bg.ProxyService"
android:process=":bg"
android:directBootAware="true"
android:foregroundServiceType="specialUse"
android:exported="false">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Proxy service that handles incoming SOCKS5 traffic" />
</service>
<service

View File

@@ -20,9 +20,17 @@
package com.github.shadowsocks
import android.app.*
import android.app.ActivityManager
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.content.*
import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.ConnectivityManager
@@ -34,7 +42,6 @@ import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.os.persistableBundleOf
import androidx.work.Configuration
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.core.BuildConfig
@@ -47,19 +54,16 @@ import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DeviceStorageApp
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.google.firebase.FirebaseApp
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.initialize
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import java.io.IOException
import kotlin.reflect.KClass
object Core : Configuration.Provider {
object Core {
lateinit var app: Application
@VisibleForTesting set
lateinit var configureIntent: (Context) -> PendingIntent
@@ -111,7 +115,7 @@ object Core : Configuration.Provider {
// overhead of debug mode is minimal: https://github.com/Kotlin/kotlinx.coroutines/blob/f528898/docs/debugging.md#debug-mode
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
Firebase.initialize(deviceStorage) // multiple processes needs manual set-up
FirebaseApp.initializeApp(deviceStorage) // multiple processes needs manual set-up
Timber.plant(object : Timber.DebugTree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (t == null) {
@@ -142,13 +146,6 @@ object Core : Configuration.Provider {
updateNotificationChannels()
}
override fun getWorkManagerConfiguration() = Configuration.Builder().apply {
setDefaultProcessName(app.packageName + ":bg")
setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO)
setExecutor { GlobalScope.launch { it.run() } }
setTaskExecutor { GlobalScope.launch { it.run() } }
}.build()
fun updateNotificationChannels() {
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
notification.createNotificationChannels(listOf(

View File

@@ -51,6 +51,7 @@ class UrlImportActivity : AppCompatActivity() {
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
requireActivity().finish()
}
}

View File

@@ -22,10 +22,22 @@ package com.github.shadowsocks.acl
import android.content.Context
import android.os.Build
import androidx.work.*
import android.util.Log
import androidx.work.Configuration
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.core.BuildConfig
import com.github.shadowsocks.utils.useCancellable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.IOException
import java.net.HttpURLConnection
@@ -38,16 +50,21 @@ class AclSyncer(context: Context, workerParams: WorkerParameters) : CoroutineWor
fun schedule(route: String) {
if (Build.VERSION.SDK_INT >= 24 && !Core.user.isUserUnlocked) return // work does not support this
if (!WorkManager.isInitialized()) WorkManager.initialize(app, Configuration.Builder().apply {
setDefaultProcessName(app.packageName + ":bg")
setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO)
setExecutor { GlobalScope.launch { it.run() } }
setTaskExecutor { GlobalScope.launch { it.run() } }
}.build())
WorkManager.getInstance(app).enqueueUniqueWork(
route, ExistingWorkPolicy.REPLACE, OneTimeWorkRequestBuilder<AclSyncer>().run {
route, ExistingWorkPolicy.REPLACE, OneTimeWorkRequestBuilder<AclSyncer>().apply {
setInputData(Data.Builder().putString(KEY_ROUTE, route).build())
setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build())
setConstraints(Constraints.Builder().apply {
setRequiredNetworkType(NetworkType.UNMETERED)
setRequiresCharging(true)
}.build())
setInitialDelay(10, TimeUnit.SECONDS)
build()
})
}.build())
}
}

View File

@@ -70,7 +70,7 @@ class ShadowsocksConnection(private var listenForDeath: Boolean = false) : Servi
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
val callback = callback ?: return
GlobalScope.launch(Dispatchers.Main.immediate) {
callback.stateChanged(BaseService.State.values()[state], profileName, msg)
callback.stateChanged(BaseService.State.entries[state], profileName, msg)
}
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats) {

View File

@@ -44,7 +44,7 @@ import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.readableMessage
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.analytics.ktx.logEvent
import com.google.firebase.analytics.logEvent
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.*
import timber.log.Timber

View File

@@ -85,6 +85,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
config.put("plugin", path).put("plugin_opts", opts.toString())
}
config.put("dns", "system")
config.put("mode", mode)
config.put("locals", JSONArray().apply {
// local SOCKS5 proxy
put(JSONObject().apply {

View File

@@ -20,6 +20,7 @@
package com.github.shadowsocks.bg
import android.annotation.SuppressLint
import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
@@ -79,6 +80,7 @@ class VpnService : BaseVpnService(), BaseService.Interface {
network.bindSocket(fd)
return@let true
} catch (e: IOException) {
@SuppressLint("NewApi")
when ((e.cause as? ErrnoException)?.errno) {
OsConstants.EPERM, OsConstants.EACCES, OsConstants.ENONET -> Timber.d(e)
else -> Timber.w(e)
@@ -159,7 +161,7 @@ class VpnService : BaseVpnService(), BaseService.Interface {
override val isVpnService get() = true
private suspend fun startVpn(): FileDescriptor {
private fun startVpn(): FileDescriptor {
val profile = data.proxy!!.profile
val builder = Builder()
.setConfigureIntent(Core.configureIntent(this))

View File

@@ -92,7 +92,7 @@ data class Profile(
companion object {
@JvmStatic
@TypeConverter
fun of(value: Int) = values().single { it.persistedValue == value }
fun of(value: Int) = entries.single { it.persistedValue == value }
@JvmStatic
@TypeConverter
fun toInt(status: SubscriptionStatus) = status.persistedValue
@@ -199,10 +199,10 @@ data class Profile(
remoteDns = json["remote_dns"].optString ?: remoteDns
ipv6 = json["ipv6"].optBoolean ?: ipv6
metered = json["metered"].optBoolean ?: metered
(json["proxy_apps"] as? JsonObject)?.also {
proxyApps = it["enabled"].optBoolean ?: proxyApps
bypass = it["bypass"].optBoolean ?: bypass
individual = (it["android_list"] as? JsonArray)?.asIterable()?.mapNotNull { it.optString }
(json["proxy_apps"] as? JsonObject)?.also { obj ->
proxyApps = obj["enabled"].optBoolean ?: proxyApps
bypass = obj["bypass"].optBoolean ?: bypass
individual = (obj["android_list"] as? JsonArray)?.asIterable()?.mapNotNull { it.optString }
?.joinToString("\n") ?: individual
}
udpdns = json["udpdns"].optBoolean ?: udpdns

View File

@@ -83,7 +83,7 @@ object DefaultNetworkListener {
}
suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener))
suspend fun get() = if (fallback) @TargetApi(23) {
suspend fun get() = if (fallback) {
Core.connectivity.activeNetwork ?: throw UnknownHostException() // failed to listen, return current if available
} else NetworkMessage.Get().run {
networkActor.send(this)

View File

@@ -26,14 +26,26 @@ import android.net.Network
import android.os.Build
import android.os.CancellationSignal
import com.github.shadowsocks.Core
import kotlinx.coroutines.*
import org.xbill.DNS.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import org.xbill.DNS.AAAARecord
import org.xbill.DNS.ARecord
import org.xbill.DNS.DClass
import org.xbill.DNS.Flags
import org.xbill.DNS.Message
import org.xbill.DNS.Name
import org.xbill.DNS.Opcode
import org.xbill.DNS.PTRRecord
import org.xbill.DNS.ReverseMap
import org.xbill.DNS.Section
import org.xbill.DNS.Type
import java.io.IOException
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -73,15 +85,14 @@ sealed class DnsResolverCompat {
abstract suspend fun resolveRaw(network: Network, query: ByteArray): ByteArray
abstract suspend fun resolveRawOnActiveNetwork(query: ByteArray): ByteArray
private object DnsResolverCompat23 : DnsResolverCompat() {
private data object DnsResolverCompat23 : DnsResolverCompat() {
/**
* This dispatcher is used for noncancellable possibly-forever-blocking operations in network IO.
*
* See also: https://issuetracker.google.com/issues/133874590
*/
private val unboundedIO by lazy {
if (Core.activity.isLowRamDevice) Dispatchers.IO
else Executors.newCachedThreadPool().asCoroutineDispatcher()
if (Core.activity.isLowRamDevice) Dispatchers.IO else Dispatchers.IO.limitedParallelism(Int.MAX_VALUE)
}
override suspend fun resolve(network: Network, host: String) =

View File

@@ -47,10 +47,10 @@ class HttpsTest : ViewModel() {
protected abstract val status: CharSequence
open fun retrieve(setStatus: (CharSequence) -> Unit, errorCallback: (String) -> Unit) = setStatus(status)
object Idle : Status() {
data object Idle : Status() {
override val status get() = app.getText(R.string.vpn_connected)
}
object Testing : Status() {
data object Testing : Status() {
override val status get() = app.getText(R.string.connection_test_testing)
}
class Success(private val elapsed: Long) : Status() {

Some files were not shown because too many files have changed in this diff Show More