mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 11:42:43 +08:00
feat: add mrs
format domain ruleset
Some checks are pending
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run
Some checks are pending
Trigger CMFA Update / trigger-CMFA-update (push) Waiting to run
This commit is contained in:
parent
0d90a93645
commit
303f6e4567
127
component/trie/domain_set_bin.go
Normal file
127
component/trie/domain_set_bin.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package trie
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
func (ss *DomainSet) WriteBin(w io.Writer, count int64) (err error) {
|
||||
// version
|
||||
_, err = w.Write([]byte{1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// count
|
||||
err = binary.Write(w, binary.BigEndian, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// leaves
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.leaves)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range ss.leaves {
|
||||
err = binary.Write(w, binary.BigEndian, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// labelBitmap
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.labelBitmap)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range ss.labelBitmap {
|
||||
err = binary.Write(w, binary.BigEndian, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// labels
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.labels)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(ss.labels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadDomainSetBin(r io.Reader) (ds *DomainSet, count int64, err error) {
|
||||
// version
|
||||
version := make([]byte, 1)
|
||||
_, err = io.ReadFull(r, version)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if version[0] != 1 {
|
||||
return nil, 0, errors.New("version is invalid")
|
||||
}
|
||||
|
||||
// count
|
||||
err = binary.Read(r, binary.BigEndian, &count)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
ds = &DomainSet{}
|
||||
var length int64
|
||||
|
||||
// leaves
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, 0, errors.New("length is invalid")
|
||||
}
|
||||
ds.leaves = make([]uint64, length)
|
||||
for i := int64(0); i < length; i++ {
|
||||
err = binary.Read(r, binary.BigEndian, &ds.leaves[i])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// labelBitmap
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, 0, errors.New("length is invalid")
|
||||
}
|
||||
ds.labelBitmap = make([]uint64, length)
|
||||
for i := int64(0); i < length; i++ {
|
||||
err = binary.Read(r, binary.BigEndian, &ds.labelBitmap[i])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// labels
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, 0, errors.New("length is invalid")
|
||||
}
|
||||
ds.labels = make([]byte, length)
|
||||
_, err = io.ReadFull(r, ds.labels)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
ds.init()
|
||||
return ds, count, nil
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
@ -110,9 +112,24 @@ func (rt RuleBehavior) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
func ParseBehavior(s string) (behavior RuleBehavior, err error) {
|
||||
switch s {
|
||||
case "domain":
|
||||
behavior = Domain
|
||||
case "ipcidr":
|
||||
behavior = IPCIDR
|
||||
case "classical":
|
||||
behavior = Classical
|
||||
default:
|
||||
err = fmt.Errorf("unsupported behavior type: %s", s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
YamlRule RuleFormat = iota
|
||||
TextRule
|
||||
MrsRule
|
||||
)
|
||||
|
||||
type RuleFormat int
|
||||
|
@ -123,11 +140,27 @@ func (rf RuleFormat) String() string {
|
|||
return "YamlRule"
|
||||
case TextRule:
|
||||
return "TextRule"
|
||||
case MrsRule:
|
||||
return "MrsRule"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func ParseRuleFormat(s string) (format RuleFormat, err error) {
|
||||
switch s {
|
||||
case "", "yaml":
|
||||
format = YamlRule
|
||||
case "text":
|
||||
format = TextRule
|
||||
case "mrs":
|
||||
format = MrsRule
|
||||
default:
|
||||
err = fmt.Errorf("unsupported format type: %s", s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Tunnel interface {
|
||||
Providers() map[string]ProxyProvider
|
||||
RuleProviders() map[string]RuleProvider
|
||||
|
|
|
@ -942,6 +942,12 @@ rule-providers:
|
|||
interval: 259200
|
||||
path: /path/to/save/file.yaml
|
||||
type: file
|
||||
rule3: # mrs类型ruleset,目前仅支持domain,可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到
|
||||
type: http
|
||||
url: "url"
|
||||
format: mrs
|
||||
behavior: domain
|
||||
path: /path/to/save/file.mrs
|
||||
rules:
|
||||
- RULE-SET,rule1,REJECT
|
||||
- IP-ASN,1,PROXY
|
||||
|
|
2
go.mod
2
go.mod
|
@ -14,6 +14,7 @@ require (
|
|||
github.com/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.2.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240529192340-51bc6136a0a6
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
|
@ -82,7 +83,6 @@ require (
|
|||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -81,8 +81,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
|||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
|
7
main.go
7
main.go
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/metacubex/mihomo/hub"
|
||||
"github.com/metacubex/mihomo/hub/executor"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/rules/provider"
|
||||
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
)
|
||||
|
@ -48,6 +49,12 @@ func init() {
|
|||
|
||||
func main() {
|
||||
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "convert-ruleset" {
|
||||
provider.ConvertMain(os.Args[2:])
|
||||
return
|
||||
}
|
||||
|
||||
if version {
|
||||
fmt.Printf("Mihomo Meta %s %s %s with %s %s\n",
|
||||
C.Version, runtime.GOOS, runtime.GOARCH, runtime.Version(), C.BuildTime)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/metacubex/mihomo/component/trie"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
@ -48,6 +51,25 @@ func (d *domainStrategy) FinishInsert() {
|
|||
d.domainTrie = nil
|
||||
}
|
||||
|
||||
func (d *domainStrategy) FromMrs(r io.Reader) error {
|
||||
domainSet, count, err := trie.ReadDomainSetBin(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.count = int(count)
|
||||
d.domainSet = domainSet
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *domainStrategy) WriteMrs(w io.Writer) error {
|
||||
if d.domainSet == nil {
|
||||
return errors.New("nil domainSet")
|
||||
}
|
||||
return d.domainSet.WriteBin(w, int64(d.count))
|
||||
}
|
||||
|
||||
var _ mrsRuleStrategy = (*domainStrategy)(nil)
|
||||
|
||||
func NewDomainStrategy() *domainStrategy {
|
||||
return &domainStrategy{}
|
||||
}
|
||||
|
|
71
rules/provider/mrs_converter.go
Normal file
71
rules/provider/mrs_converter.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io.Writer) (err error) {
|
||||
strategy := newStrategy(behavior, nil)
|
||||
strategy, err = rulesParse(buf, strategy, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _strategy, ok := strategy.(mrsRuleStrategy); ok {
|
||||
var encoder *zstd.Encoder
|
||||
encoder, err = zstd.NewWriter(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
zstdErr := encoder.Close()
|
||||
if err == nil {
|
||||
err = zstdErr
|
||||
}
|
||||
}()
|
||||
return _strategy.WriteMrs(encoder)
|
||||
} else {
|
||||
return ErrInvalidFormat
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertMain(args []string) {
|
||||
if len(args) > 3 {
|
||||
behavior, err := P.ParseBehavior(args[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
format, err := P.ParseRuleFormat(args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
source := args[2]
|
||||
target := args[3]
|
||||
|
||||
sourceFile, err := os.ReadFile(source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
targetFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ConvertToMrs(sourceFile, behavior, format, targetFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = targetFile.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
panic("Usage: convert-ruleset <behavior> <format> <source file> <target file>")
|
||||
}
|
||||
}
|
|
@ -32,28 +32,13 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
|
|||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var behavior P.RuleBehavior
|
||||
|
||||
switch schema.Behavior {
|
||||
case "domain":
|
||||
behavior = P.Domain
|
||||
case "ipcidr":
|
||||
behavior = P.IPCIDR
|
||||
case "classical":
|
||||
behavior = P.Classical
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported behavior type: %s", schema.Behavior)
|
||||
behavior, err := P.ParseBehavior(schema.Behavior)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var format P.RuleFormat
|
||||
|
||||
switch schema.Format {
|
||||
case "", "yaml":
|
||||
format = P.YamlRule
|
||||
case "text":
|
||||
format = P.TextRule
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format type: %s", schema.Format)
|
||||
format, err := P.ParseRuleFormat(schema.Format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vehicle P.Vehicle
|
||||
|
|
|
@ -4,16 +4,18 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var tunnel P.Tunnel
|
||||
|
@ -52,6 +54,12 @@ type ruleStrategy interface {
|
|||
FinishInsert()
|
||||
}
|
||||
|
||||
type mrsRuleStrategy interface {
|
||||
ruleStrategy
|
||||
FromMrs(r io.Reader) error
|
||||
WriteMrs(w io.Writer) error
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Type() P.ProviderType {
|
||||
return P.Rule
|
||||
}
|
||||
|
@ -152,9 +160,23 @@ func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string,
|
|||
}
|
||||
|
||||
var ErrNoPayload = errors.New("file must have a `payload` field")
|
||||
var ErrInvalidFormat = errors.New("invalid format")
|
||||
|
||||
func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) {
|
||||
strategy.Reset()
|
||||
if format == P.MrsRule {
|
||||
if _strategy, ok := strategy.(mrsRuleStrategy); ok {
|
||||
reader, err := zstd.NewReader(bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
err = _strategy.FromMrs(reader)
|
||||
return strategy, err
|
||||
} else {
|
||||
return nil, ErrInvalidFormat
|
||||
}
|
||||
}
|
||||
|
||||
schema := &RulePayload{}
|
||||
|
||||
|
@ -228,6 +250,8 @@ func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStr
|
|||
if len(schema.Payload) > 0 {
|
||||
str = schema.Payload[0]
|
||||
}
|
||||
default:
|
||||
return nil, ErrInvalidFormat
|
||||
}
|
||||
|
||||
if str == "" {
|
||||
|
|
Loading…
Reference in New Issue
Block a user