-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge in DNS/adguard-home from 3597-wrt-netlink to master Updates #3597. Squashed commit of the following: commit 1709582 Merge: 0507b6e e7b3c99 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Mar 15 20:25:18 2022 +0300 Merge branch 'master' into 3597-wrt-netlink commit 0507b6e Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Mar 15 20:21:29 2022 +0300 all: imp code commit 71f9758 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Mar 9 18:03:48 2022 +0500 aghnet: imp naming commit c949e76 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Mar 9 17:26:30 2022 +0500 all: imp code, docs commit cf605dd Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Mar 8 15:33:52 2022 +0500 all: imp code, docs commit 2960c65 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Sun Mar 6 21:34:58 2022 +0500 all: imp code & docs, fix tests commit 29c049f Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Mar 2 20:45:34 2022 +0300 all: add arpdb
- Loading branch information
1 parent
e7b3c99
commit 573cbaf
Showing
19 changed files
with
1,058 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
package aghnet | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"net" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/AdguardTeam/AdGuardHome/internal/aghos" | ||
"github.com/AdguardTeam/golibs/errors" | ||
"github.com/AdguardTeam/golibs/netutil" | ||
) | ||
|
||
// ARPDB: The Network Neighborhood Database | ||
|
||
// ARPDB stores and refreshes the network neighborhood reported by ARP (Address | ||
// Resolution Protocol). | ||
type ARPDB interface { | ||
// Refresh updates the stored data. It must be safe for concurrent use. | ||
Refresh() (err error) | ||
|
||
// Neighbors returnes the last set of data reported by ARP. Both the method | ||
// and it's result must be safe for concurrent use. | ||
Neighbors() (ns []Neighbor) | ||
} | ||
|
||
// NewARPDB returns the ARPDB properly initialized for the OS. | ||
func NewARPDB() (arp ARPDB, err error) { | ||
arp = newARPDB() | ||
|
||
err = arp.Refresh() | ||
if err != nil { | ||
return nil, fmt.Errorf("arpdb initial refresh: %w", err) | ||
} | ||
|
||
return arp, nil | ||
} | ||
|
||
// Empty ARPDB implementation | ||
|
||
// EmptyARPDB is the ARPDB implementation that does nothing. | ||
type EmptyARPDB struct{} | ||
|
||
// type check | ||
var _ ARPDB = EmptyARPDB{} | ||
|
||
// Refresh implements the ARPDB interface for EmptyARPContainer. It does | ||
// nothing and always returns nil error. | ||
func (EmptyARPDB) Refresh() (err error) { return nil } | ||
|
||
// Neighbors implements the ARPDB interface for EmptyARPContainer. It always | ||
// returns nil. | ||
func (EmptyARPDB) Neighbors() (ns []Neighbor) { return nil } | ||
|
||
// ARPDB Helper Types | ||
|
||
// Neighbor is the pair of IP address and MAC address reported by ARP. | ||
type Neighbor struct { | ||
// Name is the hostname of the neighbor. Empty name is valid since not each | ||
// implementation of ARP is able to retrieve that. | ||
Name string | ||
|
||
// IP contains either IPv4 or IPv6. | ||
IP net.IP | ||
|
||
// MAC contains the hardware address. | ||
MAC net.HardwareAddr | ||
} | ||
|
||
// Clone returns the deep copy of n. | ||
func (n Neighbor) Clone() (clone Neighbor) { | ||
return Neighbor{ | ||
Name: n.Name, | ||
IP: netutil.CloneIP(n.IP), | ||
MAC: netutil.CloneMAC(n.MAC), | ||
} | ||
} | ||
|
||
// neighs is the helper type that stores neighbors to avoid copying its methods | ||
// among all the ARPDB implementations. | ||
type neighs struct { | ||
mu *sync.RWMutex | ||
ns []Neighbor | ||
} | ||
|
||
// len returns the length of the neighbors slice. It's safe for concurrent use. | ||
func (ns *neighs) len() (l int) { | ||
ns.mu.RLock() | ||
defer ns.mu.RUnlock() | ||
|
||
return len(ns.ns) | ||
} | ||
|
||
// clone returns a deep copy of the underlying neighbors slice. It's safe for | ||
// concurrent use. | ||
func (ns *neighs) clone() (cloned []Neighbor) { | ||
ns.mu.RLock() | ||
defer ns.mu.RUnlock() | ||
|
||
cloned = make([]Neighbor, len(ns.ns)) | ||
for i, n := range ns.ns { | ||
cloned[i] = n.Clone() | ||
} | ||
|
||
return cloned | ||
} | ||
|
||
// reset replaces the underlying slice with the new one. It's safe for | ||
// concurrent use. | ||
func (ns *neighs) reset(with []Neighbor) { | ||
ns.mu.Lock() | ||
defer ns.mu.Unlock() | ||
|
||
ns.ns = with | ||
} | ||
|
||
// Command ARPDB | ||
|
||
// parseNeighsFunc parses the text from sc as if it'd be an output of some | ||
// ARP-related command. lenHint is a hint for the size of the allocated slice | ||
// of Neighbors. | ||
type parseNeighsFunc func(sc *bufio.Scanner, lenHint int) (ns []Neighbor) | ||
|
||
// runCmdFunc is the function that runs some command and returns its output | ||
// wrapped to be a io.Reader. | ||
type runCmdFunc func() (r io.Reader, err error) | ||
|
||
// cmdARPDB is the implementation of the ARPDB that uses command line to | ||
// retrieve data. | ||
type cmdARPDB struct { | ||
parse parseNeighsFunc | ||
runcmd runCmdFunc | ||
ns *neighs | ||
} | ||
|
||
// type check | ||
var _ ARPDB = (*cmdARPDB)(nil) | ||
|
||
// runCmd runs the cmd with it's args and returns the result wrapped to be an | ||
// io.Reader. The error is returned either if the exit code retured by command | ||
// not equals 0 or the execution itself failed. | ||
func runCmd(cmd string, args ...string) (r io.Reader, err error) { | ||
var code int | ||
var out string | ||
code, out, err = aghos.RunCommand(cmd, args...) | ||
if err != nil { | ||
return nil, err | ||
} else if code != 0 { | ||
return nil, fmt.Errorf("unexpected exit code %d", code) | ||
} | ||
|
||
return strings.NewReader(out), nil | ||
} | ||
|
||
// Refresh implements the ARPDB interface for *cmdARPDB. | ||
func (arp *cmdARPDB) Refresh() (err error) { | ||
defer func() { err = errors.Annotate(err, "cmd arpdb: %w") }() | ||
|
||
var r io.Reader | ||
r, err = arp.runcmd() | ||
if err != nil { | ||
return fmt.Errorf("running command: %w", err) | ||
} | ||
|
||
sc := bufio.NewScanner(r) | ||
ns := arp.parse(sc, arp.ns.len()) | ||
if err = sc.Err(); err != nil { | ||
return fmt.Errorf("scanning the output: %w", err) | ||
} | ||
|
||
arp.ns.reset(ns) | ||
|
||
return nil | ||
} | ||
|
||
// Neighbors implements the ARPDB interface for *cmdARPDB. | ||
func (arp *cmdARPDB) Neighbors() (ns []Neighbor) { | ||
return arp.ns.clone() | ||
} | ||
|
||
// Composite ARPDB | ||
|
||
// arpdbs is the ARPDB that combines several ARPDB implementations and | ||
// consequently switches between those. | ||
type arpdbs struct { | ||
// arps is the set of ARPDB implementations to range through. | ||
arps []ARPDB | ||
// last is the last succeeded ARPDB index. | ||
last int | ||
} | ||
|
||
// newARPDBs returns a properly initialized *arpdbs. It begins refreshing from | ||
// the first of arps. | ||
func newARPDBs(arps ...ARPDB) (arp *arpdbs) { | ||
return &arpdbs{ | ||
arps: arps, | ||
last: 0, | ||
} | ||
} | ||
|
||
// type check | ||
var _ ARPDB = (*arpdbs)(nil) | ||
|
||
// Refresh implements the ARPDB interface for *arpdbs. | ||
func (arp *arpdbs) Refresh() (err error) { | ||
var errs []error | ||
l := len(arp.arps) | ||
// Start from the last succeeded implementation. | ||
for i := 0; i < l; i++ { | ||
cur := (arp.last + i) % l | ||
err = arp.arps[cur].Refresh() | ||
if err == nil { | ||
// The succeeded implementation found so update the last succeeded | ||
// index. | ||
arp.last = cur | ||
|
||
return nil | ||
} | ||
|
||
errs = append(errs, err) | ||
} | ||
|
||
if len(errs) > 0 { | ||
err = errors.List("each arpdb failed", errs...) | ||
} | ||
|
||
return err | ||
} | ||
|
||
// Neighbors implements the ARPDB interface for *arpdbs. | ||
func (arp *arpdbs) Neighbors() (ns []Neighbor) { | ||
if l := len(arp.arps); l > 0 && arp.last < l { | ||
return arp.arps[arp.last].Neighbors() | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
//go:build darwin || freebsd | ||
// +build darwin freebsd | ||
|
||
package aghnet | ||
|
||
import ( | ||
"bufio" | ||
"net" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/AdguardTeam/golibs/log" | ||
"github.com/AdguardTeam/golibs/netutil" | ||
) | ||
|
||
func newARPDB() *cmdARPDB { | ||
return &cmdARPDB{ | ||
parse: parseArpA, | ||
runcmd: rcArpA, | ||
ns: &neighs{ | ||
mu: &sync.RWMutex{}, | ||
ns: make([]Neighbor, 0), | ||
}, | ||
} | ||
} | ||
|
||
// parseArpA parses the output of the "arp -a" command on macOS and FreeBSD. | ||
// The expected input format: | ||
// | ||
// host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet] | ||
// | ||
func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) { | ||
ns = make([]Neighbor, 0, lenHint) | ||
for sc.Scan() { | ||
ln := sc.Text() | ||
|
||
fields := strings.Fields(ln) | ||
if len(fields) < 4 { | ||
continue | ||
} | ||
|
||
n := Neighbor{} | ||
|
||
if ipStr := fields[1]; len(ipStr) < 2 { | ||
continue | ||
} else if ip := net.ParseIP(ipStr[1 : len(ipStr)-1]); ip == nil { | ||
continue | ||
} else { | ||
n.IP = ip | ||
} | ||
|
||
hwStr := fields[3] | ||
if mac, err := net.ParseMAC(hwStr); err != nil { | ||
continue | ||
} else { | ||
n.MAC = mac | ||
} | ||
|
||
host := fields[0] | ||
if err := netutil.ValidateDomainName(host); err != nil { | ||
log.Debug("parsing arp output: %s", err) | ||
} else { | ||
n.Name = host | ||
} | ||
|
||
ns = append(ns, n) | ||
} | ||
|
||
return ns | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
//go:build darwin || freebsd | ||
// +build darwin freebsd | ||
|
||
package aghnet | ||
|
||
import ( | ||
"net" | ||
) | ||
|
||
const arpAOutput = ` | ||
hostname.one (192.168.1.2) at ab:cd:ef:ab:cd:ef on en0 ifscope [ethernet] | ||
hostname.two (::ffff:ffff) at ef:cd:ab:ef:cd:ab on em0 expires in 1198 seconds [ethernet] | ||
` | ||
|
||
var wantNeighs = []Neighbor{{ | ||
Name: "hostname.one", | ||
IP: net.IPv4(192, 168, 1, 2), | ||
MAC: net.HardwareAddr{0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF}, | ||
}, { | ||
Name: "hostname.two", | ||
IP: net.ParseIP("::ffff:ffff"), | ||
MAC: net.HardwareAddr{0xEF, 0xCD, 0xAB, 0xEF, 0xCD, 0xAB}, | ||
}} |
Oops, something went wrong.