This commit is contained in:
parentalclash 2024-11-14 23:48:42 +08:00 committed by GitHub
commit 871b725b8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 259 additions and 2 deletions

View File

@ -34,6 +34,8 @@ const (
AND
OR
NOT
SrcMAC
Schedule
)
type RuleType int
@ -104,6 +106,10 @@ func (rt RuleType) String() string {
return "OR"
case NOT:
return "NOT"
case SrcMAC:
return "SrcMAC"
case Schedule:
return "Schedule"
default:
return "Unknown"
}

1
go.mod
View File

@ -90,6 +90,7 @@ require (
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect

2
go.sum
View File

@ -144,6 +144,8 @@ github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs
github.com/openacid/testkeys v0.1.6/go.mod h1:MfA7cACzBpbiwekivj8StqX0WIRmqlMsci1c37CA3Do=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

127
rules/common/mac.go Normal file
View File

@ -0,0 +1,127 @@
package common
import (
"bytes"
"os/exec"
"regexp"
"runtime"
"strings"
"time"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/patrickmn/go-cache"
"golang.org/x/net/idna"
)
var lc = cache.New(5*time.Minute, 10*time.Minute)
var arpCommand = "arp"
var arpVar = "-a"
func init() {
switch os := runtime.GOOS; os {
case "linux":
arpCommand = "cat"
arpVar = "/proc/net/arp"
case "windows":
default:
}
}
type SrcMAC struct {
*Base
mac string
adapter string
}
func (d *SrcMAC) RuleType() C.RuleType {
return C.SrcMAC
}
func (d *SrcMAC) Match(metadata *C.Metadata) (bool, string) {
arpTable, err := getARPTable(false)
if err != nil {
log.Errorln("can't initial arp table: %s", err)
return false, ""
}
mac, exists := arpTable[metadata.SrcIP.String()]
if exists {
if mac == d.mac {
return true, d.adapter
}
} else {
arpTable, err := getARPTable(true)
if err != nil {
log.Errorln("can't initial arp table: %s", err)
return false, ""
}
mac, exists := arpTable[metadata.SrcIP.String()]
if exists {
if mac == d.mac {
return true, d.adapter
}
} else {
log.Errorln("can't find the IP address in arp table: %s", metadata.SrcIP.String())
}
}
return false, d.adapter
}
func (d *SrcMAC) Adapter() string {
return d.adapter
}
func (d *SrcMAC) Payload() string {
return d.mac
}
func NewMAC(mac string, adapter string) *SrcMAC {
punycode, _ := idna.ToASCII(strings.ToLower(mac))
return &SrcMAC{
Base: &Base{},
mac: punycode,
adapter: adapter,
}
}
func getARPTable(forceReload bool) (map[string]string, error) {
item, found := lc.Get("arpTable")
if found && !forceReload {
arpTable := item.(map[string]string)
//log.Infoln("get arpTable from cache")
return arpTable, nil
}
// 执行arp命令
cmd := exec.Command(arpCommand, arpVar)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
ipRegex := regexp.MustCompile(`(([0-9]{1,3}\.){3}[0-9]{1,3})`)
macRegex := regexp.MustCompile(`(?i)(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}`)
// 解析arp命令的输出
arpTable := make(map[string]string)
lines := strings.Split(out.String(), "\n")
for _, line := range lines {
ip := ipRegex.FindString(line)
mac := macRegex.FindString(line)
if len(ip) > 0 && len(mac) > 0 {
punycode, _ := idna.ToASCII(strings.ToLower(mac))
arpTable[ip] = punycode
}
}
lc.Set("arpTable", arpTable, cache.DefaultExpiration)
return arpTable, nil
}
//var _ C.Rule = (*Mac)(nil)

117
rules/common/schedule.go Normal file
View File

@ -0,0 +1,117 @@
package common
import (
"fmt"
"strconv"
"strings"
"time"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"golang.org/x/net/idna"
)
// make sure the system has install related zoneinfo for local time zone!
func init() {
log.Infoln("current system time is %s", time.Now().Format("2006-01-02 15:04:05"))
}
type Schedule struct {
*Base
weekDayArr [7]bool
startHour int
startMinute int
endHour int
endMinute int
schedule string
adapter string
}
func (d *Schedule) RuleType() C.RuleType {
return C.Schedule
}
func (d *Schedule) Match(metadata *C.Metadata) (bool, string) {
now := time.Now()
//log.Infoln("system time is %", now.Format("2006-01-02 15:04:05.000 Mon Jan"))
if d.weekDayArr[now.Weekday()] {
startTime := time.Date(now.Year(), now.Month(), now.Day(), d.startHour, d.startMinute, 0, 0, now.Location())
endTime := time.Date(now.Year(), now.Month(), now.Day(), d.endHour, d.endMinute, 59, 999999999, now.Location())
if now.After(startTime) && now.Before(endTime) {
//log.Infoln("src ip %s in the time %d:%d~%d:%d. adapter is %s.", metadata.SrcIP.String(), d.startHour, d.startMinute, d.endHour, d.endMinute, d.adapter)
return true, d.adapter
}
}
return false, d.adapter
}
func (d *Schedule) Adapter() string {
return d.adapter
}
func (d *Schedule) Payload() string {
return d.schedule
}
func NewSchedule(schedule string, adapter string) (*Schedule, error) {
punycode, _ := idna.ToASCII(strings.ToUpper(schedule))
weekDayArr := [7]bool{false, false, false, false, false, false, false}
if len(punycode) != 19 {
return nil, fmt.Errorf("could you initial Schedule rule %s, the rule format is not correct!", punycode)
}
if punycode[0] == 'S' {
weekDayArr[0] = true
}
if punycode[1] == 'M' {
weekDayArr[1] = true
}
if punycode[2] == 'T' {
weekDayArr[2] = true
}
if punycode[3] == 'W' {
weekDayArr[3] = true
}
if punycode[4] == 'T' {
weekDayArr[4] = true
}
if punycode[5] == 'F' {
weekDayArr[5] = true
}
if punycode[6] == 'S' {
weekDayArr[6] = true
}
startHour, err := strconv.Atoi(punycode[8:10])
if err != nil {
return nil, fmt.Errorf("could you initial Schedule rule %s, the time format is not correct!", punycode)
}
startMinute, err := strconv.Atoi(punycode[11:13])
if err != nil {
return nil, fmt.Errorf("could you initial Schedule rule %s, the time format is not correct!", punycode)
}
endHour, err := strconv.Atoi(punycode[14:16])
if err != nil {
return nil, fmt.Errorf("could you initial Schedule rule %s, the time format is not correct!", punycode)
}
endMinute, err := strconv.Atoi(punycode[17:19])
if err != nil {
return nil, fmt.Errorf("could you initial Schedule rule %s, the time format is not correct!", punycode)
}
if startHour > endHour {
return nil, fmt.Errorf("could you initial Schedule rule %s, the end time should not be earlier than start time!", punycode)
}
if startHour == endHour && startMinute > endMinute {
return nil, fmt.Errorf("could you initial Schedule rule %s, the end time should not be earlier than start time!", punycode)
}
return &Schedule{
Base: &Base{},
weekDayArr: weekDayArr,
startHour: startHour,
startMinute: startMinute,
endHour: endHour,
endMinute: endMinute,
schedule: punycode,
adapter: adapter,
}, nil
}
//var _ C.Rule = (*Schedule)(nil)

View File

@ -6,10 +6,10 @@ import (
"strings"
"sync"
list "github.com/bahlo/generic-list-go"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/rules/common"
list "github.com/bahlo/generic-list-go"
)
type Logic struct {

View File

@ -75,6 +75,10 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
parsed, parseErr = logic.NewOR(payload, target, ParseRule)
case "NOT":
parsed, parseErr = logic.NewNOT(payload, target, ParseRule)
case "SRC-MAC":
parsed = RC.NewMAC(payload, target)
case "SCHEDULE":
parsed, parseErr = RC.NewSchedule(payload, target)
case "RULE-SET":
isSrc, noResolve := RC.ParseParams(params)
parsed, parseErr = RP.NewRuleSet(payload, target, isSrc, noResolve)