mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 16:30:07 +08:00
194 lines
4.1 KiB
Go
194 lines
4.1 KiB
Go
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 MacAddr struct {
|
|
*Base
|
|
mac string
|
|
adapter string
|
|
isSourceIP bool
|
|
}
|
|
|
|
func (d *MacAddr) RuleType() C.RuleType {
|
|
if d.isSourceIP {
|
|
return C.SrcMAC
|
|
} else {
|
|
return C.DstMAC
|
|
}
|
|
}
|
|
|
|
func getLoadArpTableFunc() func() (string, error) {
|
|
const ipv6Error = "can't load ipv6 arp table, SRC-MAC/DST-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 *MacAddr) Match(metadata *C.Metadata) (bool, string) {
|
|
table := getArpTable()
|
|
var ip string
|
|
if d.isSourceIP {
|
|
ip = metadata.SrcIP.String()
|
|
} else {
|
|
ip = metadata.DstIP.String()
|
|
}
|
|
mac, exists := table[ip]
|
|
if exists {
|
|
if mac == d.mac {
|
|
return true, d.adapter
|
|
}
|
|
} else {
|
|
log.Infoln("can't find the IP address in arp table: %s", ip)
|
|
}
|
|
return false, d.adapter
|
|
}
|
|
|
|
func (d *MacAddr) Adapter() string {
|
|
return d.adapter
|
|
}
|
|
|
|
func (d *MacAddr) Payload() string {
|
|
return d.mac
|
|
}
|
|
|
|
var macRegex = regexp.MustCompile(`^([0-9a-f]{2}:){5}[0-9a-f]{2}$`)
|
|
|
|
func NewMAC(mac string, adapter string, isSrc bool) (*MacAddr, error) {
|
|
macAddr := strings.ReplaceAll(strings.ToLower(mac), "-", ":")
|
|
if !macRegex.MatchString(macAddr) {
|
|
return nil, errors.New("mac address format error: " + mac)
|
|
}
|
|
return &MacAddr{
|
|
Base: &Base{},
|
|
mac: macAddr,
|
|
adapter: adapter,
|
|
isSourceIP: isSrc,
|
|
}, 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
|
|
}
|