arping.go 1.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. package neighbors
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "errors"
  7. "fmt"
  8. "os/exec"
  9. "regexp"
  10. )
  11. type (
  12. ARPing interface {
  13. Ping(ctx context.Context, ifi, hw, ip string) (bool, error)
  14. }
  15. arping struct {
  16. arpingCmd, sudoCmd, count string
  17. }
  18. )
  19. func NewARPing(count uint) (ARPing, error) {
  20. arpingCmd, err := exec.LookPath("arping")
  21. if err != nil {
  22. return nil, err
  23. }
  24. cmd := exec.Command(arpingCmd, "--help")
  25. b, err := cmd.Output()
  26. if err != nil {
  27. return nil, fmt.Errorf(`incompatible "arping" command (%w)`, err)
  28. }
  29. ok, err := regexp.Match("^ARPing ", b)
  30. if err != nil {
  31. return nil, err
  32. }
  33. if !ok {
  34. r := bufio.NewReaderSize(bytes.NewReader(b), 32)
  35. l, p, err := r.ReadLine()
  36. if err != nil {
  37. return nil, fmt.Errorf(`incompatible "arping" command (%w)`, err)
  38. }
  39. var e string
  40. if p {
  41. e = "\u2026"
  42. }
  43. return nil, fmt.Errorf(`incompatible "arping" command (%s%v)`, l, e)
  44. }
  45. sudoCmd, err := exec.LookPath("sudo")
  46. if err != nil {
  47. return nil, err
  48. }
  49. return &arping{arpingCmd: arpingCmd, sudoCmd: sudoCmd, count: fmt.Sprint(count)}, nil
  50. }
  51. func (a *arping) Ping(ctx context.Context, ifi, hw, ip string) (ok bool, err error) {
  52. cmd := exec.CommandContext(ctx, a.sudoCmd, a.arpingCmd, "-c", a.count, "-i", ifi, "-t", hw, "-q", ip)
  53. err = cmd.Run()
  54. if err == nil {
  55. ok = true
  56. } else {
  57. var exitError *exec.ExitError
  58. if errors.As(err, &exitError) && len(exitError.Stderr) == 0 {
  59. err = nil
  60. }
  61. }
  62. return
  63. }