Skip to content

Commit 4f8a5a5

Browse files
committed
feat: add mrs format ipcidr ruleset
1 parent 303f6e4 commit 4f8a5a5

10 files changed

+255
-44
lines changed

component/cidr/ipcidr_set_bin.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package cidr
2+
3+
import (
4+
"encoding/binary"
5+
"errors"
6+
"io"
7+
"net/netip"
8+
9+
"go4.org/netipx"
10+
)
11+
12+
func (ss *IpCidrSet) WriteBin(w io.Writer) (err error) {
13+
// version
14+
_, err = w.Write([]byte{1})
15+
if err != nil {
16+
return err
17+
}
18+
19+
// rr
20+
err = binary.Write(w, binary.BigEndian, int64(len(ss.rr)))
21+
if err != nil {
22+
return err
23+
}
24+
for _, r := range ss.rr {
25+
err = binary.Write(w, binary.BigEndian, r.From().As16())
26+
if err != nil {
27+
return err
28+
}
29+
err = binary.Write(w, binary.BigEndian, r.To().As16())
30+
if err != nil {
31+
return err
32+
}
33+
}
34+
35+
return nil
36+
}
37+
38+
func ReadIpCidrSet(r io.Reader) (ss *IpCidrSet, err error) {
39+
// version
40+
version := make([]byte, 1)
41+
_, err = io.ReadFull(r, version)
42+
if err != nil {
43+
return nil, err
44+
}
45+
if version[0] != 1 {
46+
return nil, errors.New("version is invalid")
47+
}
48+
49+
ss = NewIpCidrSet()
50+
var length int64
51+
52+
// rr
53+
err = binary.Read(r, binary.BigEndian, &length)
54+
if err != nil {
55+
return nil, err
56+
}
57+
if length < 1 {
58+
return nil, errors.New("length is invalid")
59+
}
60+
ss.rr = make([]netipx.IPRange, length)
61+
for i := int64(0); i < length; i++ {
62+
var a16 [16]byte
63+
err = binary.Read(r, binary.BigEndian, &a16)
64+
if err != nil {
65+
return nil, err
66+
}
67+
from := netip.AddrFrom16(a16).Unmap()
68+
err = binary.Read(r, binary.BigEndian, &a16)
69+
if err != nil {
70+
return nil, err
71+
}
72+
to := netip.AddrFrom16(a16).Unmap()
73+
ss.rr[i] = netipx.IPRangeFrom(from, to)
74+
}
75+
76+
return ss, nil
77+
}

component/trie/domain_set_bin.go

+14-26
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,13 @@ import (
66
"io"
77
)
88

9-
func (ss *DomainSet) WriteBin(w io.Writer, count int64) (err error) {
9+
func (ss *DomainSet) WriteBin(w io.Writer) (err error) {
1010
// version
1111
_, err = w.Write([]byte{1})
1212
if err != nil {
1313
return err
1414
}
1515

16-
// count
17-
err = binary.Write(w, binary.BigEndian, count)
18-
if err != nil {
19-
return err
20-
}
21-
2216
// leaves
2317
err = binary.Write(w, binary.BigEndian, int64(len(ss.leaves)))
2418
if err != nil {
@@ -56,21 +50,15 @@ func (ss *DomainSet) WriteBin(w io.Writer, count int64) (err error) {
5650
return nil
5751
}
5852

59-
func ReadDomainSetBin(r io.Reader) (ds *DomainSet, count int64, err error) {
53+
func ReadDomainSetBin(r io.Reader) (ds *DomainSet, err error) {
6054
// version
6155
version := make([]byte, 1)
6256
_, err = io.ReadFull(r, version)
6357
if err != nil {
64-
return nil, 0, err
58+
return nil, err
6559
}
6660
if version[0] != 1 {
67-
return nil, 0, errors.New("version is invalid")
68-
}
69-
70-
// count
71-
err = binary.Read(r, binary.BigEndian, &count)
72-
if err != nil {
73-
return nil, 0, err
61+
return nil, errors.New("version is invalid")
7462
}
7563

7664
ds = &DomainSet{}
@@ -79,49 +67,49 @@ func ReadDomainSetBin(r io.Reader) (ds *DomainSet, count int64, err error) {
7967
// leaves
8068
err = binary.Read(r, binary.BigEndian, &length)
8169
if err != nil {
82-
return nil, 0, err
70+
return nil, err
8371
}
8472
if length < 1 {
85-
return nil, 0, errors.New("length is invalid")
73+
return nil, errors.New("length is invalid")
8674
}
8775
ds.leaves = make([]uint64, length)
8876
for i := int64(0); i < length; i++ {
8977
err = binary.Read(r, binary.BigEndian, &ds.leaves[i])
9078
if err != nil {
91-
return nil, 0, err
79+
return nil, err
9280
}
9381
}
9482

9583
// labelBitmap
9684
err = binary.Read(r, binary.BigEndian, &length)
9785
if err != nil {
98-
return nil, 0, err
86+
return nil, err
9987
}
10088
if length < 1 {
101-
return nil, 0, errors.New("length is invalid")
89+
return nil, errors.New("length is invalid")
10290
}
10391
ds.labelBitmap = make([]uint64, length)
10492
for i := int64(0); i < length; i++ {
10593
err = binary.Read(r, binary.BigEndian, &ds.labelBitmap[i])
10694
if err != nil {
107-
return nil, 0, err
95+
return nil, err
10896
}
10997
}
11098

11199
// labels
112100
err = binary.Read(r, binary.BigEndian, &length)
113101
if err != nil {
114-
return nil, 0, err
102+
return nil, err
115103
}
116104
if length < 1 {
117-
return nil, 0, errors.New("length is invalid")
105+
return nil, errors.New("length is invalid")
118106
}
119107
ds.labels = make([]byte, length)
120108
_, err = io.ReadFull(r, ds.labels)
121109
if err != nil {
122-
return nil, 0, err
110+
return nil, err
123111
}
124112

125113
ds.init()
126-
return ds, count, nil
114+
return ds, nil
127115
}

constant/provider/interface.go

+13
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,19 @@ func (rt RuleBehavior) String() string {
112112
}
113113
}
114114

115+
func (rt RuleBehavior) Byte() byte {
116+
switch rt {
117+
case Domain:
118+
return 0
119+
case IPCIDR:
120+
return 1
121+
case Classical:
122+
return 2
123+
default:
124+
return 255
125+
}
126+
}
127+
115128
func ParseBehavior(s string) (behavior RuleBehavior, err error) {
116129
switch s {
117130
case "domain":

docs/config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ rule-providers:
942942
interval: 259200
943943
path: /path/to/save/file.yaml
944944
type: file
945-
rule3: # mrs类型ruleset,目前仅支持domain,可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到
945+
rule3: # mrs类型ruleset,目前仅支持domain和ipcidr,可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到
946946
type: http
947947
url: "url"
948948
format: mrs

rules/provider/classical_strategy.go

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
C "github.com/metacubex/mihomo/constant"
8+
P "github.com/metacubex/mihomo/constant/provider"
89
"github.com/metacubex/mihomo/log"
910
)
1011

@@ -16,6 +17,10 @@ type classicalStrategy struct {
1617
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
1718
}
1819

20+
func (c *classicalStrategy) Behavior() P.RuleBehavior {
21+
return P.Classical
22+
}
23+
1924
func (c *classicalStrategy) Match(metadata *C.Metadata) bool {
2025
for _, rule := range c.rules {
2126
if m, _ := rule.Match(metadata); m {

rules/provider/domain_strategy.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/metacubex/mihomo/component/trie"
88
C "github.com/metacubex/mihomo/constant"
9+
P "github.com/metacubex/mihomo/constant/provider"
910
"github.com/metacubex/mihomo/log"
1011
)
1112

@@ -15,6 +16,10 @@ type domainStrategy struct {
1516
domainSet *trie.DomainSet
1617
}
1718

19+
func (d *domainStrategy) Behavior() P.RuleBehavior {
20+
return P.Domain
21+
}
22+
1823
func (d *domainStrategy) ShouldFindProcess() bool {
1924
return false
2025
}
@@ -51,12 +56,12 @@ func (d *domainStrategy) FinishInsert() {
5156
d.domainTrie = nil
5257
}
5358

54-
func (d *domainStrategy) FromMrs(r io.Reader) error {
55-
domainSet, count, err := trie.ReadDomainSetBin(r)
59+
func (d *domainStrategy) FromMrs(r io.Reader, count int) error {
60+
domainSet, err := trie.ReadDomainSetBin(r)
5661
if err != nil {
5762
return err
5863
}
59-
d.count = int(count)
64+
d.count = count
6065
d.domainSet = domainSet
6166
return nil
6267
}
@@ -65,7 +70,7 @@ func (d *domainStrategy) WriteMrs(w io.Writer) error {
6570
if d.domainSet == nil {
6671
return errors.New("nil domainSet")
6772
}
68-
return d.domainSet.WriteBin(w, int64(d.count))
73+
return d.domainSet.WriteBin(w)
6974
}
7075

7176
var _ mrsRuleStrategy = (*domainStrategy)(nil)

rules/provider/ipcidr_strategy.go

+28
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package provider
22

33
import (
4+
"errors"
5+
"io"
6+
47
"github.com/metacubex/mihomo/component/cidr"
58
C "github.com/metacubex/mihomo/constant"
9+
P "github.com/metacubex/mihomo/constant/provider"
610
"github.com/metacubex/mihomo/log"
711

812
"go4.org/netipx"
@@ -15,6 +19,10 @@ type ipcidrStrategy struct {
1519
//trie *trie.IpCidrTrie
1620
}
1721

22+
func (i *ipcidrStrategy) Behavior() P.RuleBehavior {
23+
return P.IPCIDR
24+
}
25+
1826
func (i *ipcidrStrategy) ShouldFindProcess() bool {
1927
return false
2028
}
@@ -54,6 +62,26 @@ func (i *ipcidrStrategy) FinishInsert() {
5462
i.cidrSet.Merge()
5563
}
5664

65+
func (i *ipcidrStrategy) FromMrs(r io.Reader, count int) error {
66+
cidrSet, err := cidr.ReadIpCidrSet(r)
67+
if err != nil {
68+
return err
69+
}
70+
i.count = count
71+
i.cidrSet = cidrSet
72+
if i.count > 0 {
73+
i.shouldResolveIP = true
74+
}
75+
return nil
76+
}
77+
78+
func (i *ipcidrStrategy) WriteMrs(w io.Writer) error {
79+
if i.cidrSet == nil {
80+
return errors.New("nil cidrSet")
81+
}
82+
return i.cidrSet.WriteBin(w)
83+
}
84+
5785
func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet {
5886
return i.cidrSet.ToIPSet()
5987
}

rules/provider/mrs_converter.go

+33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package provider
22

33
import (
4+
"encoding/binary"
45
"io"
56
"os"
67

@@ -27,6 +28,38 @@ func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io
2728
err = zstdErr
2829
}
2930
}()
31+
32+
// header
33+
_, err = encoder.Write(MrsMagicBytes[:])
34+
if err != nil {
35+
return err
36+
}
37+
38+
// behavior
39+
_behavior := []byte{behavior.Byte()}
40+
_, err = encoder.Write(_behavior[:])
41+
if err != nil {
42+
return err
43+
}
44+
45+
// count
46+
count := int64(_strategy.Count())
47+
err = binary.Write(encoder, binary.BigEndian, count)
48+
if err != nil {
49+
return err
50+
}
51+
52+
// extra (reserved for future using)
53+
var extra []byte
54+
err = binary.Write(encoder, binary.BigEndian, int64(len(extra)))
55+
if err != nil {
56+
return err
57+
}
58+
_, err = encoder.Write(extra)
59+
if err != nil {
60+
return err
61+
}
62+
3063
return _strategy.WriteMrs(encoder)
3164
} else {
3265
return ErrInvalidFormat

0 commit comments

Comments
 (0)