132 lines
3.1 KiB
Go
132 lines
3.1 KiB
Go
package device
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/nemunaire/repeater/internal/models"
|
|
)
|
|
|
|
// GetConnectedDevices returns a list of connected devices
|
|
func GetConnectedDevices() ([]models.ConnectedDevice, error) {
|
|
var devices []models.ConnectedDevice
|
|
|
|
// Read DHCP leases
|
|
leases, err := parseDHCPLeases()
|
|
if err != nil {
|
|
return devices, err
|
|
}
|
|
|
|
// Get ARP information
|
|
arpInfo, err := getARPInfo()
|
|
if err != nil {
|
|
return devices, err
|
|
}
|
|
|
|
for _, lease := range leases {
|
|
device := models.ConnectedDevice{
|
|
Name: lease.Hostname,
|
|
MAC: lease.MAC,
|
|
IP: lease.IP,
|
|
Type: guessDeviceType(lease.Hostname, lease.MAC),
|
|
}
|
|
|
|
// Check if the device is still connected via ARP
|
|
if _, exists := arpInfo[lease.IP]; exists {
|
|
devices = append(devices, device)
|
|
}
|
|
}
|
|
|
|
return devices, nil
|
|
}
|
|
|
|
// parseDHCPLeases reads and parses DHCP lease file
|
|
func parseDHCPLeases() ([]models.DHCPLease, error) {
|
|
var leases []models.DHCPLease
|
|
|
|
file, err := os.Open("/var/lib/dhcp/dhcpd.leases")
|
|
if err != nil {
|
|
return leases, err
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
var currentLease models.DHCPLease
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
|
|
if strings.HasPrefix(line, "lease ") {
|
|
ip := strings.Fields(line)[1]
|
|
currentLease = models.DHCPLease{IP: ip}
|
|
} else if strings.Contains(line, "hardware ethernet") {
|
|
mac := strings.Fields(line)[2]
|
|
mac = strings.TrimSuffix(mac, ";")
|
|
currentLease.MAC = mac
|
|
} else if strings.Contains(line, "client-hostname") {
|
|
hostname := strings.Fields(line)[1]
|
|
hostname = strings.Trim(hostname, `";`)
|
|
currentLease.Hostname = hostname
|
|
} else if line == "}" {
|
|
if currentLease.IP != "" && currentLease.MAC != "" {
|
|
leases = append(leases, currentLease)
|
|
}
|
|
currentLease = models.DHCPLease{}
|
|
}
|
|
}
|
|
|
|
return leases, nil
|
|
}
|
|
|
|
// getARPInfo retrieves ARP table information
|
|
func getARPInfo() (map[string]string, error) {
|
|
arpInfo := make(map[string]string)
|
|
|
|
cmd := exec.Command("arp", "-a")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return arpInfo, err
|
|
}
|
|
|
|
lines := strings.Split(string(output), "\n")
|
|
for _, line := range lines {
|
|
if matches := regexp.MustCompile(`\(([^)]+)\) at ([0-9a-fA-F:]{17})`).FindStringSubmatch(line); len(matches) > 2 {
|
|
ip := matches[1]
|
|
mac := matches[2]
|
|
arpInfo[ip] = mac
|
|
}
|
|
}
|
|
|
|
return arpInfo, nil
|
|
}
|
|
|
|
// guessDeviceType attempts to guess device type from hostname and MAC address
|
|
func guessDeviceType(hostname, mac string) string {
|
|
hostname = strings.ToLower(hostname)
|
|
|
|
if strings.Contains(hostname, "iphone") || strings.Contains(hostname, "android") {
|
|
return "mobile"
|
|
} else if strings.Contains(hostname, "ipad") || strings.Contains(hostname, "tablet") {
|
|
return "tablet"
|
|
} else if strings.Contains(hostname, "macbook") || strings.Contains(hostname, "laptop") {
|
|
return "laptop"
|
|
}
|
|
|
|
// Guess by MAC prefix (OUI)
|
|
if len(mac) >= 8 {
|
|
macPrefix := strings.ToUpper(mac[:8])
|
|
switch macPrefix {
|
|
case "00:50:56", "00:0C:29", "00:05:69": // VMware
|
|
return "laptop"
|
|
case "08:00:27": // VirtualBox
|
|
return "laptop"
|
|
default:
|
|
return "mobile"
|
|
}
|
|
}
|
|
|
|
return "unknown"
|
|
}
|