Skip to content

Commit

Permalink
Detect presence with arp and arping on FreeBSD
Browse files Browse the repository at this point in the history
  • Loading branch information
douglaswth committed Jun 13, 2022
1 parent 3b5e304 commit c30f2ac
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
29 changes: 29 additions & 0 deletions cmd/presence/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"context"
"log"
"os"

"douglasthrift.net/presence/neighbors"
)

func main() {
ifs := map[string]bool{os.Args[1]: true}
hws := make(map[string]bool, len(os.Args[2:]))
for _, hw := range os.Args[2:] {
hws[hw] = true
}

ctx := context.Background()
a, err := neighbors.NewARP(1)
if err != nil {
log.Fatal(err)
}

ok, err := a.Present(ctx, ifs, hws)
if err != nil {
log.Fatal(err)
}
log.Println(ok)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module douglasthrift.net/presence

go 1.17
11 changes: 11 additions & 0 deletions neighbors/arp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package neighbors

import (
"context"
)

type (
ARP interface {
Present(ctx context.Context, ifs map[string]bool, addrs map[string]bool) (bool, error)
}
)
86 changes: 86 additions & 0 deletions neighbors/arp_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package neighbors

import (
"context"
"encoding/json"
"fmt"
"net"
"os/exec"
)

const (
arpOutputVersion = "1"
)

type (
arp struct {
cmd string
arping ARPing
}

arpOutput struct {
Version string `json:"__version"`
ARP struct {
Cache []arpEntry `json:"arp-cache"`
} `json:"arp"`
}

arpEntry struct {
IPAddress string `json:"ip-address"`
MACAddress string `json:"mac-address"`
Interface string `json:"interface"`
}
)

func NewARP(count uint) (ARP, error) {
cmd, err := exec.LookPath("arp")
if err != nil {
return nil, err
}

arping, err := NewARPing(count)
if err != nil {
return nil, err
}

return &arp{cmd: cmd, arping: arping}, nil
}

func (a *arp) Present(ctx context.Context, ifs map[string]bool, hws map[string]bool) (ok bool, err error) {
cmd := exec.CommandContext(ctx, a.cmd, "--libxo=json", "-an")
b, err := cmd.Output()
if err != nil {
return
}

o := &arpOutput{}
err = json.Unmarshal(b, o)
if err != nil {
return
}

if o.Version != arpOutputVersion {
err = fmt.Errorf("arp output version mismatch (got %v, expected %v)", o.Version, arpOutputVersion)
return
}

for _, e := range o.ARP.Cache {
if ifs[e.Interface] {
var hwa net.HardwareAddr
hwa, err = net.ParseMAC(e.MACAddress)
if err != nil {
return
}
hw := hwa.String()

if hws[hw] {
ok, err = a.arping.Ping(ctx, e.Interface, hw, e.IPAddress)
if ok || err != nil {
return
}
}
}
}

return
}
47 changes: 47 additions & 0 deletions neighbors/arping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package neighbors

import (
"context"
"errors"
"fmt"
"os/exec"
)

type (
ARPing interface {
Ping(ctx context.Context, ifi, hw, ip string) (bool, error)
}

arping struct {
cmd, sudoCmd, count string
}
)

func NewARPing(count uint) (ARPing, error) {
cmd, err := exec.LookPath("arping")
if err != nil {
return nil, err
}

sudoCmd, err := exec.LookPath("sudo")
if err != nil {
return nil, err
}

return &arping{cmd: cmd, sudoCmd: sudoCmd, count: fmt.Sprint(count)}, nil
}

func (a *arping) Ping(ctx context.Context, ifi, hw, ip string) (ok bool, err error) {
cmd := exec.CommandContext(ctx, a.sudoCmd, a.cmd, "-c", a.count, "-i", ifi, "-t", hw, "-q", ip)
err = cmd.Run()
if err == nil {
ok = true
} else {
var exitError *exec.ExitError
if errors.As(err, &exitError) && len(exitError.Stderr) == 0 {
err = nil
}
}

return
}

0 comments on commit c30f2ac

Please sign in to comment.