Skip to content

Commit

Permalink
Draft rule set support
Browse files Browse the repository at this point in the history
  • Loading branch information
nekohasekai committed Nov 29, 2023
1 parent 927865e commit 85fabbd
Show file tree
Hide file tree
Showing 42 changed files with 2,050 additions and 72 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/.idea/
/vendor/
/*.json
/*.srs
/*.db
/site/
/bin/
Expand Down
28 changes: 27 additions & 1 deletion adapter/experimental.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package adapter

import (
"bytes"
"context"
"encoding/binary"
"net"
"time"

"github.com/sagernet/sing-box/common/urltest"
N "github.com/sagernet/sing/common/network"
Expand All @@ -23,14 +26,37 @@ type CacheFile interface {
PreStarter

StoreFakeIP() bool
FakeIPStorage

LoadMode() string
StoreMode(mode string) error
LoadSelected(group string) string
StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error
FakeIPStorage
LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedRuleSet) error
}

type SavedRuleSet struct {
Content []byte
LastUpdated time.Time
}

func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
if err != nil {
return nil, err
}
buffer.Write(s.Content)
return buffer.Bytes(), nil
}

func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
s.LastUpdated = time.Unix(int64(binary.BigEndian.Uint64(data)), 0)
s.Content = data[8:]
return nil
}

type Tracker interface {
Expand Down
15 changes: 13 additions & 2 deletions adapter/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Router interface {

Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound
DefaultOutbound(network string) (Outbound, error)

FakeIPStore() FakeIPStore

Expand All @@ -27,6 +27,8 @@ type Router interface {
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)

RuleSet(tag string) (RuleSet, bool)

Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
Expand Down Expand Up @@ -61,11 +63,15 @@ func RouterFromContext(ctx context.Context) Router {
return service.FromContext[Router](ctx)
}

type HeadlessRule interface {
Match(metadata *InboundContext) bool
}

type Rule interface {
HeadlessRule
Service
Type() string
UpdateGeosite() error
Match(metadata *InboundContext) bool
Outbound() string
String() string
}
Expand All @@ -76,6 +82,11 @@ type DNSRule interface {
RewriteTTL() *uint32
}

type RuleSet interface {
HeadlessRule
Service
}

type InterfaceUpdateListener interface {
InterfaceUpdated()
}
Expand Down
39 changes: 0 additions & 39 deletions cmd/sing-box/cmd_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -69,41 +68,3 @@ func format() error {
}
return nil
}

func formatOne(configPath string) error {
configContent, err := os.ReadFile(configPath)
if err != nil {
return E.Cause(err, "read config")
}
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return E.Cause(err, "decode config")
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
return E.Cause(err, "encode config")
}
if !commandFormatFlagWrite {
os.Stdout.WriteString(buffer.String() + "\n")
return nil
}
if bytes.Equal(configContent, buffer.Bytes()) {
return nil
}
output, err := os.Create(configPath)
if err != nil {
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
return E.Cause(err, "write output")
}
outputPath, _ := filepath.Abs(configPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}
41 changes: 41 additions & 0 deletions cmd/sing-box/cmd_geosite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"

"github.com/spf13/cobra"
)

var (
commandGeoSiteFlagFile string
geositeReader *geosite.Reader
geositeCodeList []string
)

var commandGeoSite = &cobra.Command{
Use: "geosite",
Short: "Geosite tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geositePreRun()
if err != nil {
log.Fatal(err)
}
},
}

func init() {
commandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, "file", "f", "geosite.db", "geosite file")
mainCommand.AddCommand(commandGeoSite)
}

func geositePreRun() error {
reader, codeList, err := geosite.Open(commandGeoSiteFlagFile)
if err != nil {
return E.Cause(err, "open geosite file")
}
geositeReader = reader
geositeCodeList = codeList
return nil
}
97 changes: 97 additions & 0 deletions cmd/sing-box/cmd_geosite_contains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package main

import (
"os"
"sort"

"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"

"github.com/spf13/cobra"
)

var commandGeositeContains = &cobra.Command{
Use: "contains [category] <domain>",
Short: "Check if a domain is in the geosite",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
var (
source string
target string
)
switch len(args) {
case 1:
target = args[0]
case 2:
source = args[0]
target = args[1]
}
err := geositeContains(source, target)
if err != nil {
log.Fatal(err)
}
},
}

func init() {
commandGeoSite.AddCommand(commandGeositeContains)
}

func geositeContains(source string, target string) error {
var sourceMatcherList []struct {
code string
matcher *searchGeositeMatcher
}
if source != "" {
sourceSet, err := geositeReader.Read(source)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+source)
}
sourceMatcherList = []struct {
code string
matcher *searchGeositeMatcher
}{
{
code: source,
matcher: sourceMatcher,
},
}

} else {
for _, code := range geositeCodeList {
sourceSet, err := geositeReader.Read(code)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+code)
}
sourceMatcherList = append(sourceMatcherList, struct {
code string
matcher *searchGeositeMatcher
}{
code: code,
matcher: sourceMatcher,
})
}
}
sort.SliceStable(sourceMatcherList, func(i, j int) bool {
return sourceMatcherList[i].code < sourceMatcherList[j].code
})

for _, matcherItem := range sourceMatcherList {
if matchRule := matcherItem.matcher.Match(target); matchRule != "" {
os.Stdout.WriteString("Match code (")
os.Stdout.WriteString(matcherItem.code)
os.Stdout.WriteString(") ")
os.Stdout.WriteString(matchRule)
os.Stdout.WriteString("\n")
}
}
return nil
}
80 changes: 80 additions & 0 deletions cmd/sing-box/cmd_geosite_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"encoding/json"
"io"
"os"

"github.com/sagernet/sing-box/common/geosite"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"

"github.com/spf13/cobra"
)

var commandGeositeExportOutput string

const commandGeositeExportDefaultOutput = "geosite-<category>.json"

var commandGeositeExport = &cobra.Command{
Use: "export <category>",
Short: "Export geosite category as rule-set",
Run: func(cmd *cobra.Command, args []string) {
err := geositeExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}

func init() {
commandGeositeExport.Flags().StringVarP(&commandGeositeExportOutput, "output", "o", commandGeositeExportDefaultOutput, "Output path")
commandGeoSite.AddCommand(commandGeositeExport)
}

func geositeExport(category string) error {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
var (
outputFile *os.File
outputWriter io.Writer
)
if commandGeositeExportOutput == "stdout" {
outputWriter = os.Stdout
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}

encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
defaultRule := geosite.Compile(sourceSet)
headlessRule.Domain = defaultRule.Domain
headlessRule.DomainSuffix = defaultRule.DomainSuffix
headlessRule.DomainKeyword = defaultRule.DomainKeyword
headlessRule.DomainRegex = defaultRule.DomainRegex
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion1
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}
Loading

0 comments on commit 85fabbd

Please sign in to comment.