diff --git a/.golangci.yaml b/.golangci.yaml index 045e036..95633e8 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -11,6 +11,7 @@ linters: - unconvert - unused - usestdlibvars + - exhaustive linters-settings: gci: @@ -21,3 +22,5 @@ linters-settings: - default staticcheck: go: '1.21' + exhaustive: + default-signifies-exhaustive: true diff --git a/constant/metadata.go b/constant/metadata.go index 00e149c..2570312 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -52,6 +52,8 @@ func (t Type) String() string { return "Redir" case TPROXY: return "TProxy" + case TUNNEL: + return "Tunnel" default: return "Unknown" } diff --git a/constant/rule.go b/constant/rule.go index a7ab7e2..2dfb51a 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -1,5 +1,27 @@ package constant +const ( + RuleConfigDomain RuleConfig = "DOMAIN" + RuleConfigDomainSuffix RuleConfig = "DOMAIN-SUFFIX" + RuleConfigDomainKeyword RuleConfig = "DOMAIN-KEYWORD" + RuleConfigGeoIP RuleConfig = "GEOIP" + RuleConfigIPCIDR RuleConfig = "IP-CIDR" + RuleConfigIPCIDR6 RuleConfig = "IP-CIDR6" + RuleConfigSrcIPCIDR RuleConfig = "SRC-IP-CIDR" + RuleConfigSrcPort RuleConfig = "SRC-PORT" + RuleConfigDstPort RuleConfig = "DST-PORT" + RuleConfigInboundPort RuleConfig = "INBOUND-PORT" + RuleConfigProcessName RuleConfig = "PROCESS-NAME" + RuleConfigProcessPath RuleConfig = "PROCESS-PATH" + RuleConfigIPSet RuleConfig = "IPSET" + RuleConfigRuleSet RuleConfig = "RULE-SET" + RuleConfigScript RuleConfig = "SCRIPT" + RuleConfigMatch RuleConfig = "MATCH" +) + +// Rule Config Type String represents a rule type in configuration files. +type RuleConfig string + // Rule Type const ( Domain RuleType = iota diff --git a/log/log.go b/log/log.go index ba706e5..64c69a2 100644 --- a/log/log.go +++ b/log/log.go @@ -88,6 +88,8 @@ func print(data Event) { log.Errorln(data.Payload) case DEBUG: log.Debugln(data.Payload) + case SILENT: + return } } diff --git a/rule/domain.go b/rule/domain.go index 94978e6..1a07875 100644 --- a/rule/domain.go +++ b/rule/domain.go @@ -6,6 +6,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// Implements C.Rule +var _ C.Rule = (*Domain)(nil) + type Domain struct { domain string adapter string diff --git a/rule/domain_keyword.go b/rule/domain_keyword.go index 9e66112..0f2c29a 100644 --- a/rule/domain_keyword.go +++ b/rule/domain_keyword.go @@ -6,6 +6,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// Implements C.Rule +var _ C.Rule = (*DomainKeyword)(nil) + type DomainKeyword struct { keyword string adapter string diff --git a/rule/domain_suffix.go b/rule/domain_suffix.go index 70b8cfe..9b1dc45 100644 --- a/rule/domain_suffix.go +++ b/rule/domain_suffix.go @@ -6,6 +6,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// Implements C.Rule +var _ C.Rule = (*DomainSuffix)(nil) + type DomainSuffix struct { suffix string adapter string diff --git a/rule/final.go b/rule/final.go index 80a38f0..c4d800c 100644 --- a/rule/final.go +++ b/rule/final.go @@ -4,6 +4,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// Implements C.Rule +var _ C.Rule = (*Match)(nil) + type Match struct { adapter string } diff --git a/rule/geoip.go b/rule/geoip.go index 152d4e6..1451373 100644 --- a/rule/geoip.go +++ b/rule/geoip.go @@ -7,6 +7,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// Implements C.Rule +var _ C.Rule = (*GEOIP)(nil) + type GEOIP struct { country string adapter string diff --git a/rule/ipcidr.go b/rule/ipcidr.go index 42c0c9f..c26c264 100644 --- a/rule/ipcidr.go +++ b/rule/ipcidr.go @@ -20,6 +20,9 @@ func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { } } +// Implements C.Rule +var _ C.Rule = (*IPCIDR)(nil) + type IPCIDR struct { ipnet *net.IPNet adapter string diff --git a/rule/ipset.go b/rule/ipset.go index 07999ec..7c596da 100644 --- a/rule/ipset.go +++ b/rule/ipset.go @@ -6,6 +6,9 @@ import ( "github.com/Dreamacro/clash/log" ) +// Implements C.Rule +var _ C.Rule = (*IPSet)(nil) + type IPSet struct { name string adapter string diff --git a/rule/parser.go b/rule/parser.go index 90c54d0..9743007 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -12,36 +12,40 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed C.Rule ) - switch tp { - case "DOMAIN": + ruleConfigType := C.RuleConfig(tp) + + switch ruleConfigType { + case C.RuleConfigDomain: parsed = NewDomain(payload, target) - case "DOMAIN-SUFFIX": + case C.RuleConfigDomainSuffix: parsed = NewDomainSuffix(payload, target) - case "DOMAIN-KEYWORD": + case C.RuleConfigDomainKeyword: parsed = NewDomainKeyword(payload, target) - case "GEOIP": + case C.RuleConfigGeoIP: noResolve := HasNoResolve(params) parsed = NewGEOIP(payload, target, noResolve) - case "IP-CIDR", "IP-CIDR6": + case C.RuleConfigIPCIDR, C.RuleConfigIPCIDR6: noResolve := HasNoResolve(params) parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) - case "SRC-IP-CIDR": + case C.RuleConfigSrcIPCIDR: parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) - case "SRC-PORT": + case C.RuleConfigSrcPort: parsed, parseErr = NewPort(payload, target, PortTypeSrc) - case "DST-PORT": + case C.RuleConfigDstPort: parsed, parseErr = NewPort(payload, target, PortTypeDest) - case "INBOUND-PORT": + case C.RuleConfigInboundPort: parsed, parseErr = NewPort(payload, target, PortTypeInbound) - case "PROCESS-NAME": + case C.RuleConfigProcessName: parsed, parseErr = NewProcess(payload, target, true) - case "PROCESS-PATH": + case C.RuleConfigProcessPath: parsed, parseErr = NewProcess(payload, target, false) - case "IPSET": + case C.RuleConfigIPSet: noResolve := HasNoResolve(params) parsed, parseErr = NewIPSet(payload, target, noResolve) - case "MATCH": + case C.RuleConfigMatch: parsed = NewMatch(target) + case C.RuleConfigRuleSet, C.RuleConfigScript: + parseErr = fmt.Errorf("unsupported rule type %s", tp) default: parseErr = fmt.Errorf("unsupported rule type %s", tp) } diff --git a/rule/parser_test.go b/rule/parser_test.go new file mode 100644 index 0000000..f14ead0 --- /dev/null +++ b/rule/parser_test.go @@ -0,0 +1,171 @@ +package rules + +import ( + "errors" + "fmt" + "testing" + + C "github.com/Dreamacro/clash/constant" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseRule(t *testing.T) { + type testCase struct { + tp C.RuleConfig + payload string + target string + params []string + expectedRule C.Rule + expectedError error + } + + policy := "DIRECT" + + testCases := []testCase{ + { + tp: C.RuleConfigDomain, + payload: "example.com", + target: policy, + expectedRule: NewDomain("example.com", policy), + }, + { + tp: C.RuleConfigDomainSuffix, + payload: "example.com", + target: policy, + expectedRule: NewDomainSuffix("example.com", policy), + }, + { + tp: C.RuleConfigDomainKeyword, + payload: "example.com", + target: policy, + expectedRule: NewDomainKeyword("example.com", policy), + }, + { + tp: C.RuleConfigGeoIP, + payload: "CN", + target: policy, params: []string{noResolve}, + expectedRule: NewGEOIP("CN", policy, true), + }, + { + tp: C.RuleConfigIPCIDR, + payload: "127.0.0.0/8", + target: policy, + expectedRule: lo.Must(NewIPCIDR("127.0.0.0/8", policy, WithIPCIDRNoResolve(false))), + }, + { + tp: C.RuleConfigIPCIDR, + payload: "127.0.0.0/8", + target: policy, params: []string{noResolve}, + expectedRule: lo.Must(NewIPCIDR("127.0.0.0/8", policy, WithIPCIDRNoResolve(true))), + }, + { + tp: C.RuleConfigIPCIDR6, + payload: "2001:db8::/32", + target: policy, + expectedRule: lo.Must(NewIPCIDR("2001:db8::/32", policy, WithIPCIDRNoResolve(false))), + }, + { + tp: C.RuleConfigIPCIDR6, + payload: "2001:db8::/32", + target: policy, params: []string{noResolve}, + expectedRule: lo.Must(NewIPCIDR("2001:db8::/32", policy, WithIPCIDRNoResolve(true))), + }, + { + tp: C.RuleConfigSrcIPCIDR, + payload: "192.168.1.201/32", + target: policy, + expectedRule: lo.Must(NewIPCIDR("192.168.1.201/32", policy, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true))), + }, + { + tp: C.RuleConfigSrcPort, + payload: "80", + target: policy, + expectedRule: lo.Must(NewPort("80", policy, PortTypeSrc)), + }, + { + tp: C.RuleConfigDstPort, + payload: "80", + target: policy, + expectedRule: lo.Must(NewPort("80", policy, PortTypeDest)), + }, + { + tp: C.RuleConfigInboundPort, + payload: "80", + target: policy, + expectedRule: lo.Must(NewPort("80", policy, PortTypeInbound)), + }, + { + tp: C.RuleConfigProcessName, + payload: "example.exe", + target: policy, + expectedRule: lo.Must(NewProcess("example.exe", policy, true)), + }, + { + tp: C.RuleConfigProcessPath, + payload: "C:\\Program Files\\example.exe", + target: policy, + expectedRule: lo.Must(NewProcess("C:\\Program Files\\example.exe", policy, false)), + }, + { + tp: C.RuleConfigProcessPath, + payload: "/opt/example/example", + target: policy, + expectedRule: lo.Must(NewProcess("/opt/example/example", policy, false)), + }, + { + tp: C.RuleConfigIPSet, + payload: "example", + target: policy, + expectedRule: lo.Must(NewIPSet("example", policy, true)), + }, + { + tp: C.RuleConfigIPSet, + payload: "example", + target: policy, params: []string{noResolve}, + expectedRule: lo.Must(NewIPSet("example", policy, false)), + }, + { + tp: C.RuleConfigMatch, + payload: "example", + target: policy, + expectedRule: NewMatch(policy), + }, + { + tp: C.RuleConfigRuleSet, + payload: "example", + target: policy, + expectedError: fmt.Errorf("unsupported rule type %s", C.RuleConfigRuleSet), + }, + { + tp: C.RuleConfigScript, + payload: "example", + target: policy, + expectedError: fmt.Errorf("unsupported rule type %s", C.RuleConfigScript), + }, + { + tp: "UNKNOWN", + payload: "example", + target: policy, + expectedError: errors.New("unsupported rule type UNKNOWN"), + }, + { + tp: "ABCD", + payload: "example", + target: policy, + expectedError: errors.New("unsupported rule type ABCD"), + }, + } + + for _, tc := range testCases { + _, err := ParseRule(string(tc.tp), tc.payload, tc.target, tc.params) + if tc.expectedError != nil { + require.Error(t, err) + assert.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + } + } +} diff --git a/rule/port.go b/rule/port.go index f6392a2..6076136 100644 --- a/rule/port.go +++ b/rule/port.go @@ -15,6 +15,9 @@ const ( PortTypeInbound ) +// Implements C.Rule +var _ C.Rule = (*Port)(nil) + type Port struct { adapter string port C.Port diff --git a/rule/process.go b/rule/process.go index 1c9f43b..bf3af86 100644 --- a/rule/process.go +++ b/rule/process.go @@ -7,6 +7,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// Implements C.Rule +var _ C.Rule = (*Process)(nil) + type Process struct { adapter string process string diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 693068c..c6ed863 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -165,10 +165,12 @@ func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, r proxy = proxies["DIRECT"] case Global: proxy = proxies["GLOBAL"] - // Rule - default: + case Rule: proxy, rule, err = match(metadata) + default: + panic(fmt.Sprintf("unknown mode: %s", mode)) } + return }