arping.go 1.5 KB

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