repeater/internal/device/device.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"
}