Code: refresh code & rebase branch 'with-tun'

This commit is contained in:
yaling888
2022-03-21 09:03:47 +08:00
parent 2c0890854e
commit a45354fa08
21 changed files with 1832 additions and 115 deletions

View File

@@ -7,6 +7,7 @@ import (
"net/netip"
"net/url"
"os"
"regexp"
"runtime"
"strings"
@@ -19,6 +20,7 @@ import (
"github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router"
S "github.com/Dreamacro/clash/component/script"
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider"
@@ -100,21 +102,28 @@ type Tun struct {
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
}
// Script config
type Script struct {
MainCode string `yaml:"code" json:"code"`
ShortcutsCode map[string]string `yaml:"shortcuts" json:"shortcuts"`
}
// Experimental config
type Experimental struct{}
// Config is clash config manager
type Config struct {
General *General
Tun *Tun
DNS *DNS
Experimental *Experimental
Hosts *trie.DomainTrie
Profile *Profile
Rules []C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
Providers map[string]providerTypes.ProxyProvider
General *General
Tun *Tun
DNS *DNS
Experimental *Experimental
Hosts *trie.DomainTrie
Profile *Profile
Rules []C.Rule
RuleProviders map[string]C.Rule
Users []auth.AuthUser
Proxies map[string]C.Proxy
Providers map[string]providerTypes.ProxyProvider
}
type RawDNS struct {
@@ -175,6 +184,7 @@ type RawConfig struct {
Proxy []map[string]any `yaml:"proxies"`
ProxyGroup []map[string]any `yaml:"proxy-groups"`
Rule []string `yaml:"rules"`
Script Script `yaml:"script"`
}
// Parse config
@@ -265,11 +275,17 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Proxies = proxies
config.Providers = providers
rules, err := parseRules(rawCfg, proxies)
err = parseScript(rawCfg)
if err != nil {
return nil, err
}
rules, ruleProviders, err := parseRules(rawCfg, proxies)
if err != nil {
return nil, err
}
config.Rules = rules
config.RuleProviders = ruleProviders
hosts, err := parseHosts(rawCfg)
if err != nil {
@@ -430,10 +446,16 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return proxies, providersMap, nil
}
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
rulesConfig := cfg.Rule
func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[string]C.Rule, error) {
var (
rules []C.Rule
providerNames []string
var rules []C.Rule
ruleProviders = map[string]C.Rule{}
rulesConfig = cfg.Rule
mode = cfg.Mode
isPyInit = S.Py_IsInitialized()
)
// parse rules
for idx, line := range rulesConfig {
@@ -445,10 +467,14 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
ruleName = strings.ToUpper(rule[0])
)
if mode == T.Script && ruleName != "GEOSITE" {
continue
}
l := len(rule)
if l < 2 {
return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
return nil, nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line)
}
if l < 4 {
@@ -467,23 +493,42 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
target = rule[l-1]
params = rule[l:]
if _, ok := proxies[target]; !ok {
return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
if _, ok := proxies[target]; mode != T.Script && !ok {
return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target)
}
params = trimArr(params)
parsed, parseErr := R.ParseRule(ruleName, payload, target, params)
if parseErr != nil {
return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error())
}
rules = append(rules, parsed)
if isPyInit {
if ruleName == "GEOSITE" {
pvName := "geosite:" + strings.ToLower(payload)
providerNames = append(providerNames, pvName)
ruleProviders[pvName] = parsed
}
}
if mode != T.Script {
rules = append(rules, parsed)
}
}
runtime.GC()
return rules, nil
if isPyInit {
err := S.NewClashPyContext(providerNames)
if err != nil {
return nil, nil, err
} else {
log.Infoln("Start initial script context successful, provider records: %v", len(providerNames))
}
}
return rules, ruleProviders, nil
}
func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) {
@@ -784,3 +829,85 @@ func parseTun(rawTun RawTun, general *General) (*Tun, error) {
AutoRoute: rawTun.AutoRoute,
}, nil
}
func parseScript(cfg *RawConfig) error {
mode := cfg.Mode
script := cfg.Script
mainCode := cleanPyKeywords(script.MainCode)
shortcutsCode := script.ShortcutsCode
if mode != T.Script && len(shortcutsCode) == 0 {
return nil
} else if mode == T.Script && len(mainCode) == 0 {
return fmt.Errorf("initialized script module failure, can't find script code in the config file")
}
content := `# -*- coding: UTF-8 -*-
from datetime import datetime as whatever
class ClashTime:
def now(self):
return whatever.now()
def unix(self):
return int(whatever.now().timestamp())
def unix_nano(self):
return int(round(whatever.now().timestamp() * 1000))
time = ClashTime()
`
var shouldInitPy bool
if mode == T.Script {
content += mainCode + "\n\n"
shouldInitPy = true
}
for k, v := range shortcutsCode {
v = cleanPyKeywords(v)
v = strings.TrimSpace(v)
if len(v) == 0 {
return fmt.Errorf("initialized rule SCRIPT failure, shortcut [%s] code invalid syntax", k)
}
content += "def " + strings.ToLower(k) + "(ctx, network, process_name, host, src_ip, src_port, dst_ip, dst_port):\n return " + v + "\n\n"
shouldInitPy = true
}
if !shouldInitPy {
return nil
}
err := os.WriteFile(C.Path.Script(), []byte(content), 0o644)
if err != nil {
return fmt.Errorf("initialized script module failure, %s", err.Error())
}
if err = S.Py_Initialize(C.Path.GetExecutableFullPath(), C.Path.ScriptDir()); err != nil {
return fmt.Errorf("initialized script module failure, %s", err.Error())
} else if mode == T.Script {
if err = S.LoadMainFunction(); err != nil {
return fmt.Errorf("initialized script module failure, %s", err.Error())
}
}
log.Infoln("Start initial script module successful, version: %s", S.Py_GetVersion())
return nil
}
func cleanPyKeywords(code string) string {
if len(code) == 0 {
return code
}
keywords := []string{"import", "print"}
for _, kw := range keywords {
reg := regexp.MustCompile("(?m)[\r\n]+^.*" + kw + ".*$")
code = reg.ReplaceAllString(code, "")
}
return code
}