mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
387 lines
9.6 KiB
Go
387 lines
9.6 KiB
Go
package route
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func parseLeaseFile(path string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer file.Close()
|
|
if strings.HasSuffix(path, "dhcpd_leases") {
|
|
parseBootpdLeases(file, ipToMAC, ipToHostname, macToHostname)
|
|
return
|
|
}
|
|
if strings.HasSuffix(path, "kea-leases4.csv") {
|
|
parseKeaCSV4(file, ipToMAC, ipToHostname, macToHostname)
|
|
return
|
|
}
|
|
if strings.HasSuffix(path, "kea-leases6.csv") {
|
|
parseKeaCSV6(file, ipToMAC, ipToHostname, macToHostname)
|
|
return
|
|
}
|
|
if strings.HasSuffix(path, "dhcpd.leases") {
|
|
parseISCDhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
|
return
|
|
}
|
|
parseDnsmasqOdhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
|
}
|
|
|
|
func ReloadLeaseFiles(leaseFiles []string) (leaseIPToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
leaseIPToMAC = make(map[netip.Addr]net.HardwareAddr)
|
|
ipToHostname = make(map[netip.Addr]string)
|
|
macToHostname = make(map[string]string)
|
|
for _, path := range leaseFiles {
|
|
parseLeaseFile(path, leaseIPToMAC, ipToHostname, macToHostname)
|
|
}
|
|
return
|
|
}
|
|
|
|
func parseDnsmasqOdhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
now := time.Now().Unix()
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.HasPrefix(line, "duid ") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "# ") {
|
|
parseOdhcpdLine(line[2:], ipToMAC, ipToHostname, macToHostname)
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 4 {
|
|
continue
|
|
}
|
|
expiry, err := strconv.ParseInt(fields[0], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if expiry != 0 && expiry < now {
|
|
continue
|
|
}
|
|
if strings.Contains(fields[1], ":") {
|
|
mac, macErr := net.ParseMAC(fields[1])
|
|
if macErr != nil {
|
|
continue
|
|
}
|
|
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))
|
|
if !addrOK {
|
|
continue
|
|
}
|
|
address = address.Unmap()
|
|
ipToMAC[address] = mac
|
|
hostname := fields[3]
|
|
if hostname != "*" {
|
|
ipToHostname[address] = hostname
|
|
macToHostname[mac.String()] = hostname
|
|
}
|
|
} else {
|
|
var mac net.HardwareAddr
|
|
if len(fields) >= 5 {
|
|
duid, duidErr := parseDUID(fields[4])
|
|
if duidErr == nil {
|
|
mac, _ = extractMACFromDUID(duid)
|
|
}
|
|
}
|
|
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))
|
|
if !addrOK {
|
|
continue
|
|
}
|
|
address = address.Unmap()
|
|
if mac != nil {
|
|
ipToMAC[address] = mac
|
|
}
|
|
hostname := fields[3]
|
|
if hostname != "*" {
|
|
ipToHostname[address] = hostname
|
|
if mac != nil {
|
|
macToHostname[mac.String()] = hostname
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseOdhcpdLine(line string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 5 {
|
|
return
|
|
}
|
|
validTime, err := strconv.ParseInt(fields[4], 10, 64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if validTime == 0 {
|
|
return
|
|
}
|
|
if validTime > 0 && validTime < time.Now().Unix() {
|
|
return
|
|
}
|
|
hostname := fields[3]
|
|
if hostname == "-" || strings.HasPrefix(hostname, `broken\x20`) {
|
|
hostname = ""
|
|
}
|
|
if len(fields) >= 8 && fields[2] == "ipv4" {
|
|
mac, macErr := net.ParseMAC(fields[1])
|
|
if macErr != nil {
|
|
return
|
|
}
|
|
addressField := fields[7]
|
|
slashIndex := strings.IndexByte(addressField, '/')
|
|
if slashIndex >= 0 {
|
|
addressField = addressField[:slashIndex]
|
|
}
|
|
address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))
|
|
if !addrOK {
|
|
return
|
|
}
|
|
address = address.Unmap()
|
|
ipToMAC[address] = mac
|
|
if hostname != "" {
|
|
ipToHostname[address] = hostname
|
|
macToHostname[mac.String()] = hostname
|
|
}
|
|
return
|
|
}
|
|
var mac net.HardwareAddr
|
|
duidHex := fields[1]
|
|
duidBytes, hexErr := hex.DecodeString(duidHex)
|
|
if hexErr == nil {
|
|
mac, _ = extractMACFromDUID(duidBytes)
|
|
}
|
|
for i := 7; i < len(fields); i++ {
|
|
addressField := fields[i]
|
|
slashIndex := strings.IndexByte(addressField, '/')
|
|
if slashIndex >= 0 {
|
|
addressField = addressField[:slashIndex]
|
|
}
|
|
address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))
|
|
if !addrOK {
|
|
continue
|
|
}
|
|
address = address.Unmap()
|
|
if mac != nil {
|
|
ipToMAC[address] = mac
|
|
}
|
|
if hostname != "" {
|
|
ipToHostname[address] = hostname
|
|
if mac != nil {
|
|
macToHostname[mac.String()] = hostname
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseISCDhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
scanner := bufio.NewScanner(file)
|
|
var currentIP netip.Addr
|
|
var currentMAC net.HardwareAddr
|
|
var currentHostname string
|
|
var currentActive bool
|
|
var inLease bool
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if strings.HasPrefix(line, "lease ") && strings.HasSuffix(line, "{") {
|
|
ipString := strings.TrimSuffix(strings.TrimPrefix(line, "lease "), " {")
|
|
parsed, addrOK := netip.AddrFromSlice(net.ParseIP(ipString))
|
|
if addrOK {
|
|
currentIP = parsed.Unmap()
|
|
inLease = true
|
|
currentMAC = nil
|
|
currentHostname = ""
|
|
currentActive = false
|
|
}
|
|
continue
|
|
}
|
|
if line == "}" && inLease {
|
|
if currentActive && currentMAC != nil {
|
|
ipToMAC[currentIP] = currentMAC
|
|
if currentHostname != "" {
|
|
ipToHostname[currentIP] = currentHostname
|
|
macToHostname[currentMAC.String()] = currentHostname
|
|
}
|
|
} else {
|
|
delete(ipToMAC, currentIP)
|
|
delete(ipToHostname, currentIP)
|
|
}
|
|
inLease = false
|
|
continue
|
|
}
|
|
if !inLease {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "hardware ethernet ") {
|
|
macString := strings.TrimSuffix(strings.TrimPrefix(line, "hardware ethernet "), ";")
|
|
parsed, macErr := net.ParseMAC(macString)
|
|
if macErr == nil {
|
|
currentMAC = parsed
|
|
}
|
|
} else if strings.HasPrefix(line, "client-hostname ") {
|
|
hostname := strings.TrimSuffix(strings.TrimPrefix(line, "client-hostname "), ";")
|
|
hostname = strings.Trim(hostname, "\"")
|
|
if hostname != "" {
|
|
currentHostname = hostname
|
|
}
|
|
} else if strings.HasPrefix(line, "binding state ") {
|
|
state := strings.TrimSuffix(strings.TrimPrefix(line, "binding state "), ";")
|
|
currentActive = state == "active"
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseKeaCSV4(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
scanner := bufio.NewScanner(file)
|
|
firstLine := true
|
|
for scanner.Scan() {
|
|
if firstLine {
|
|
firstLine = false
|
|
continue
|
|
}
|
|
fields := strings.Split(scanner.Text(), ",")
|
|
if len(fields) < 10 {
|
|
continue
|
|
}
|
|
if fields[9] != "0" {
|
|
continue
|
|
}
|
|
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))
|
|
if !addrOK {
|
|
continue
|
|
}
|
|
address = address.Unmap()
|
|
mac, macErr := net.ParseMAC(fields[1])
|
|
if macErr != nil {
|
|
continue
|
|
}
|
|
ipToMAC[address] = mac
|
|
hostname := ""
|
|
if len(fields) > 8 {
|
|
hostname = fields[8]
|
|
}
|
|
if hostname != "" {
|
|
ipToHostname[address] = hostname
|
|
macToHostname[mac.String()] = hostname
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseKeaCSV6(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
scanner := bufio.NewScanner(file)
|
|
firstLine := true
|
|
for scanner.Scan() {
|
|
if firstLine {
|
|
firstLine = false
|
|
continue
|
|
}
|
|
fields := strings.Split(scanner.Text(), ",")
|
|
if len(fields) < 14 {
|
|
continue
|
|
}
|
|
if fields[13] != "0" {
|
|
continue
|
|
}
|
|
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))
|
|
if !addrOK {
|
|
continue
|
|
}
|
|
address = address.Unmap()
|
|
var mac net.HardwareAddr
|
|
if fields[12] != "" {
|
|
mac, _ = net.ParseMAC(fields[12])
|
|
}
|
|
if mac == nil {
|
|
duid, duidErr := hex.DecodeString(strings.ReplaceAll(fields[1], ":", ""))
|
|
if duidErr == nil {
|
|
mac, _ = extractMACFromDUID(duid)
|
|
}
|
|
}
|
|
hostname := ""
|
|
if len(fields) > 11 {
|
|
hostname = fields[11]
|
|
}
|
|
if mac != nil {
|
|
ipToMAC[address] = mac
|
|
}
|
|
if hostname != "" {
|
|
ipToHostname[address] = hostname
|
|
if mac != nil {
|
|
macToHostname[mac.String()] = hostname
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseBootpdLeases(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
now := time.Now().Unix()
|
|
scanner := bufio.NewScanner(file)
|
|
var currentName string
|
|
var currentIP netip.Addr
|
|
var currentMAC net.HardwareAddr
|
|
var currentLease int64
|
|
var inBlock bool
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "{" {
|
|
inBlock = true
|
|
currentName = ""
|
|
currentIP = netip.Addr{}
|
|
currentMAC = nil
|
|
currentLease = 0
|
|
continue
|
|
}
|
|
if line == "}" && inBlock {
|
|
if currentMAC != nil && currentIP.IsValid() {
|
|
if currentLease == 0 || currentLease >= now {
|
|
ipToMAC[currentIP] = currentMAC
|
|
if currentName != "" {
|
|
ipToHostname[currentIP] = currentName
|
|
macToHostname[currentMAC.String()] = currentName
|
|
}
|
|
}
|
|
}
|
|
inBlock = false
|
|
continue
|
|
}
|
|
if !inBlock {
|
|
continue
|
|
}
|
|
key, value, found := strings.Cut(line, "=")
|
|
if !found {
|
|
continue
|
|
}
|
|
switch key {
|
|
case "name":
|
|
currentName = value
|
|
case "ip_address":
|
|
parsed, addrOK := netip.AddrFromSlice(net.ParseIP(value))
|
|
if addrOK {
|
|
currentIP = parsed.Unmap()
|
|
}
|
|
case "hw_address":
|
|
typeAndMAC, hasSep := strings.CutPrefix(value, "1,")
|
|
if hasSep {
|
|
mac, macErr := net.ParseMAC(typeAndMAC)
|
|
if macErr == nil {
|
|
currentMAC = mac
|
|
}
|
|
}
|
|
case "lease":
|
|
leaseHex := strings.TrimPrefix(value, "0x")
|
|
parsed, parseErr := strconv.ParseInt(leaseHex, 16, 64)
|
|
if parseErr == nil {
|
|
currentLease = parsed
|
|
}
|
|
}
|
|
}
|
|
}
|