Merge: + clients: parse 'arp -a' output; periodically update info

Close #826

* commit 'db7efc24d381f6c8d88e14f485475e812ff5fb7b':
  + clients: parse 'arp -a' output;  periodically update info
This commit is contained in:
Simon Zolin 2019-06-27 11:59:19 +03:00
commit c631a6832f
2 changed files with 90 additions and 9 deletions

View File

@ -7,11 +7,18 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"os/exec"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/utils"
)
const (
clientsUpdatePeriod = 1 * time.Hour
) )
// Client information // Client information
@ -40,8 +47,10 @@ type clientJSON struct {
type clientSource uint type clientSource uint
const ( const (
ClientSourceHostsFile clientSource = 0 // from /etc/hosts // Priority: etc/hosts > ARP > rDNS
ClientSourceRDNS clientSource = 1 // from rDNS ClientSourceRDNS clientSource = 0 // from rDNS
ClientSourceARP clientSource = 1 // from 'arp -a'
ClientSourceHostsFile clientSource = 2 // from /etc/hosts
) )
// ClientHost information // ClientHost information
@ -68,7 +77,15 @@ func clientsInit() {
clients.ipIndex = make(map[string]*Client) clients.ipIndex = make(map[string]*Client)
clients.ipHost = make(map[string]ClientHost) clients.ipHost = make(map[string]ClientHost)
go periodicClientsUpdate()
}
func periodicClientsUpdate() {
for {
clientsAddFromHostsFile() clientsAddFromHostsFile()
clientsAddFromSystemARP()
time.Sleep(clientsUpdatePeriod)
}
} }
func clientsGetList() map[string]*Client { func clientsGetList() map[string]*Client {
@ -240,13 +257,16 @@ func clientUpdate(name string, c Client) error {
return nil return nil
} }
// Add new IP -> Host pair
// Use priority of the source (etc/hosts > ARP > rDNS)
// so we overwrite existing entries with an equal or higher priority
func clientAddHost(ip, host string, source clientSource) (bool, error) { func clientAddHost(ip, host string, source clientSource) (bool, error) {
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
// check index // check index
_, ok := clients.ipHost[ip] c, ok := clients.ipHost[ip]
if ok { if ok && c.Source > source {
return false, nil return false, nil
} }
@ -254,7 +274,7 @@ func clientAddHost(ip, host string, source clientSource) (bool, error) {
Host: host, Host: host,
Source: source, Source: source,
} }
log.Tracef("'%s': '%s' -> [%d]", host, ip, len(clients.ipHost)) log.Tracef("'%s' -> '%s' [%d]", ip, host, len(clients.ipHost))
return true, nil return true, nil
} }
@ -296,6 +316,52 @@ func clientsAddFromHostsFile() {
log.Info("Added %d client aliases from %s", n, hostsFn) log.Info("Added %d client aliases from %s", n, hostsFn)
} }
// Add IP -> Host pairs from the system's `arp -a` command output
// The command's output is:
// HOST (IP) at MAC on IFACE
func clientsAddFromSystemARP() {
if runtime.GOOS == "windows" {
return
}
cmd := exec.Command("arp", "-a")
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
data, err := cmd.Output()
if err != nil || cmd.ProcessState.ExitCode() != 0 {
log.Debug("command %s has failed: %v code:%d",
cmd.Path, err, cmd.ProcessState.ExitCode())
return
}
n := 0
lines := strings.Split(string(data), "\n")
for _, ln := range lines {
open := strings.Index(ln, " (")
close := strings.Index(ln, ") ")
if open == -1 || close == -1 || open >= close {
continue
}
host := ln[:open]
ip := ln[open+2 : close]
if utils.IsValidHostname(host) != nil || net.ParseIP(ip) == nil {
continue
}
ok, e := clientAddHost(ip, host, ClientSourceARP)
if e != nil {
log.Tracef("%s", e)
}
if ok {
n++
}
}
log.Info("Added %d client aliases from 'arp -a' command output", n)
}
type clientHostJSON struct { type clientHostJSON struct {
IP string `json:"ip"` IP string `json:"ip"`
Name string `json:"name"` Name string `json:"name"`
@ -342,8 +408,11 @@ func handleGetClients(w http.ResponseWriter, r *http.Request) {
Name: ch.Host, Name: ch.Host,
} }
cj.Source = "etc/hosts" cj.Source = "etc/hosts"
if ch.Source == ClientSourceRDNS { switch ch.Source {
case ClientSourceRDNS:
cj.Source = "rDNS" cj.Source = "rDNS"
case ClientSourceARP:
cj.Source = "ARP"
} }
data.AutoClients = append(data.AutoClients, cj) data.AutoClients = append(data.AutoClients, cj)
} }

View File

@ -104,17 +104,29 @@ func TestClients(t *testing.T) {
} }
// add host client // add host client
b, e = clientAddHost("1.1.1.1", "host", ClientSourceHostsFile) b, e = clientAddHost("1.1.1.1", "host", ClientSourceARP)
if !b || e != nil { if !b || e != nil {
t.Fatalf("clientAddHost") t.Fatalf("clientAddHost")
} }
// failed add - ip exists // failed add - ip exists
b, e = clientAddHost("1.1.1.1", "host", ClientSourceHostsFile) b, e = clientAddHost("1.1.1.1", "host1", ClientSourceRDNS)
if b || e != nil { if b || e != nil {
t.Fatalf("clientAddHost - ip exists") t.Fatalf("clientAddHost - ip exists")
} }
// overwrite with new data
b, e = clientAddHost("1.1.1.1", "host2", ClientSourceARP)
if !b || e != nil {
t.Fatalf("clientAddHost - overwrite with new data")
}
// overwrite with new data (higher priority)
b, e = clientAddHost("1.1.1.1", "host3", ClientSourceHostsFile)
if !b || e != nil {
t.Fatalf("clientAddHost - overwrite with new data (higher priority)")
}
// get // get
if !clientExists("1.1.1.1") { if !clientExists("1.1.1.1") {
t.Fatalf("clientAddHost") t.Fatalf("clientAddHost")