mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-20 00:50:06 +08:00
feat: add SRC-MAC rule
This commit is contained in:
parent
cc7823dad8
commit
c3b77652e6
@ -10,6 +10,7 @@ const (
|
|||||||
GEOIP
|
GEOIP
|
||||||
SrcGEOIP
|
SrcGEOIP
|
||||||
IPASN
|
IPASN
|
||||||
|
SrcMAC
|
||||||
SrcIPASN
|
SrcIPASN
|
||||||
IPCIDR
|
IPCIDR
|
||||||
SrcIPCIDR
|
SrcIPCIDR
|
||||||
@ -56,6 +57,8 @@ func (rt RuleType) String() string {
|
|||||||
return "SrcGeoIP"
|
return "SrcGeoIP"
|
||||||
case IPASN:
|
case IPASN:
|
||||||
return "IPASN"
|
return "IPASN"
|
||||||
|
case SrcMAC:
|
||||||
|
return "SrcMAC"
|
||||||
case SrcIPASN:
|
case SrcIPASN:
|
||||||
return "SrcIPASN"
|
return "SrcIPASN"
|
||||||
case IPCIDR:
|
case IPCIDR:
|
||||||
|
|||||||
181
rules/common/mac.go
Normal file
181
rules/common/mac.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/metacubex/mihomo/common/cmd"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var arpTable = make(map[string]string)
|
||||||
|
|
||||||
|
const reloadInterval = 5 * time.Minute
|
||||||
|
|
||||||
|
var startOnce sync.Once
|
||||||
|
func init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type SrcMAC struct {
|
||||||
|
*Base
|
||||||
|
mac string
|
||||||
|
adapter string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SrcMAC) RuleType() C.RuleType {
|
||||||
|
return C.SrcMAC
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLoadArpTableFunc() func() (string, error) {
|
||||||
|
const ipv6Error = "can't load ipv6 arp table, SRC-MAC rule can't match src ipv6 address"
|
||||||
|
|
||||||
|
getIpv4Only := func() (string, error) {
|
||||||
|
return cmd.ExecCmd("arp -a")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
result, err := cmd.ExecCmd("ip --help")
|
||||||
|
if err != nil {
|
||||||
|
result += err.Error()
|
||||||
|
}
|
||||||
|
if strings.Contains(result, "neigh") && strings.Contains(result, "inet6") {
|
||||||
|
return func() (string, error) {
|
||||||
|
return cmd.ExecCmd("ip -s neigh show")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnln(ipv6Error)
|
||||||
|
const arpPath = "/proc/net/arp"
|
||||||
|
if file, err := os.Open(arpPath); err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
return func() (string, error) {
|
||||||
|
data, err := os.ReadFile(arpPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return func() (string, error) {
|
||||||
|
return cmd.ExecCmd("arp -a -n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "windows":
|
||||||
|
getIpv6ArpWindows := func() (string, error) {
|
||||||
|
return cmd.ExecCmd("netsh interface ipv6 show neighbors")
|
||||||
|
}
|
||||||
|
result, err := getIpv6ArpWindows()
|
||||||
|
if err != nil || !strings.Contains(result, "----") {
|
||||||
|
log.Warnln(ipv6Error)
|
||||||
|
return getIpv4Only
|
||||||
|
}
|
||||||
|
return func() (string, error) {
|
||||||
|
result, err := cmd.ExecCmd("netsh interface ipv4 show neighbors")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ipv6Result, err := getIpv6ArpWindows()
|
||||||
|
if err == nil {
|
||||||
|
result += ipv6Result
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Warnln(ipv6Error)
|
||||||
|
return getIpv4Only
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SrcMAC) Match(metadata *C.Metadata) (bool, string) {
|
||||||
|
table := getArpTable()
|
||||||
|
srcIP := metadata.SrcIP.String()
|
||||||
|
mac, exists := table[srcIP]
|
||||||
|
if exists {
|
||||||
|
if mac == d.mac {
|
||||||
|
return true, d.adapter
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnln("can't find the IP address in arp table: %s", srcIP)
|
||||||
|
}
|
||||||
|
return false, d.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SrcMAC) Adapter() string {
|
||||||
|
return d.adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SrcMAC) Payload() string {
|
||||||
|
return d.mac
|
||||||
|
}
|
||||||
|
|
||||||
|
var macRegex = regexp.MustCompile(`^([0-9a-f]{2}:){5}[0-9a-f]{2}$`)
|
||||||
|
|
||||||
|
func NewMAC(mac string, adapter string) (*SrcMAC, error) {
|
||||||
|
macAddr := strings.ReplaceAll(strings.ToLower(mac), "-", ":")
|
||||||
|
if !macRegex.MatchString(macAddr) {
|
||||||
|
return nil, errors.New("mac address format error: " + mac)
|
||||||
|
}
|
||||||
|
return &SrcMAC{
|
||||||
|
Base: &Base{},
|
||||||
|
mac: macAddr,
|
||||||
|
adapter: adapter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var arpMapRegex = regexp.MustCompile(`((([0-9]{1,3}\.){3}[0-9]{1,3})|(\b[0-9a-fA-F:].*?:.*?))\s.*?\b(([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2})\b`)
|
||||||
|
|
||||||
|
func getArpTable() map[string]string {
|
||||||
|
startOnce.Do(func() {
|
||||||
|
loadArpTable := getLoadArpTableFunc()
|
||||||
|
table, err := reloadArpTable(loadArpTable)
|
||||||
|
if err == nil {
|
||||||
|
arpTable = table
|
||||||
|
} else {
|
||||||
|
log.Errorln("init arp table failed: %s", err)
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(reloadInterval)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-timer.C
|
||||||
|
table, err := reloadArpTable(loadArpTable)
|
||||||
|
if err == nil {
|
||||||
|
arpTable = table
|
||||||
|
} else {
|
||||||
|
log.Errorln("reload arp table failed: %s", err)
|
||||||
|
}
|
||||||
|
timer.Reset(reloadInterval)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
return arpTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadArpTable(loadArpFunc func() (string, error)) (map[string]string, error) {
|
||||||
|
result, err := loadArpFunc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newArpTable := make(map[string]string)
|
||||||
|
for _, line := range strings.Split(result, "\n") {
|
||||||
|
matches := arpMapRegex.FindStringSubmatch(line)
|
||||||
|
if matches == nil || len(matches) <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip := matches[1]
|
||||||
|
mac := strings.ToLower(matches[5])
|
||||||
|
if strings.Contains(mac, "-") {
|
||||||
|
mac = strings.ReplaceAll(mac, "-", ":")
|
||||||
|
}
|
||||||
|
newArpTable[ip] = mac
|
||||||
|
}
|
||||||
|
return newArpTable, nil
|
||||||
|
}
|
||||||
@ -29,6 +29,8 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
|
|||||||
case "IP-ASN":
|
case "IP-ASN":
|
||||||
noResolve := RC.HasNoResolve(params)
|
noResolve := RC.HasNoResolve(params)
|
||||||
parsed, parseErr = RC.NewIPASN(payload, target, false, noResolve)
|
parsed, parseErr = RC.NewIPASN(payload, target, false, noResolve)
|
||||||
|
case "SRC-MAC":
|
||||||
|
parsed, parseErr = RC.NewMAC(payload, target)
|
||||||
case "SRC-IP-ASN":
|
case "SRC-IP-ASN":
|
||||||
parsed, parseErr = RC.NewIPASN(payload, target, true, true)
|
parsed, parseErr = RC.NewIPASN(payload, target, true, true)
|
||||||
case "IP-CIDR", "IP-CIDR6":
|
case "IP-CIDR", "IP-CIDR6":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user