netstack_userping.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !darwin && !ios
  4. package netstack
  5. import (
  6. "errors"
  7. "net/netip"
  8. "os"
  9. "os/exec"
  10. "runtime"
  11. "time"
  12. "tailscale.com/version/distro"
  13. )
  14. // setAmbientCapsRaw is non-nil on Linux for Synology, to run ping with
  15. // CAP_NET_RAW from tailscaled's binary.
  16. var setAmbientCapsRaw func(*exec.Cmd)
  17. var isSynology = runtime.GOOS == "linux" && distro.Get() == distro.Synology
  18. // sendOutboundUserPing sends a non-privileged ICMP (or ICMPv6) ping to dstIP with the given timeout.
  19. func (ns *Impl) sendOutboundUserPing(dstIP netip.Addr, timeout time.Duration) error {
  20. var err error
  21. switch runtime.GOOS {
  22. case "windows":
  23. var out []byte
  24. out, err = exec.Command("ping", "-n", "1", "-w", "3000", dstIP.String()).CombinedOutput()
  25. if err == nil && !windowsPingOutputIsSuccess(dstIP, out) {
  26. // TODO(bradfitz,nickkhyl): return the actual ICMP error we heard back to the caller?
  27. // For now we just drop it.
  28. err = errors.New("unsuccessful ICMP reply received")
  29. }
  30. case "freebsd":
  31. // Note: 2000 ms is actually 1 second + 2,000
  32. // milliseconds extra for 3 seconds total.
  33. // See https://github.com/tailscale/tailscale/pull/3753 for details.
  34. ping := "ping"
  35. if dstIP.Is6() {
  36. ping = "ping6"
  37. }
  38. err = exec.Command(ping, "-c", "1", "-W", "2000", dstIP.String()).Run()
  39. case "openbsd":
  40. ping := "ping"
  41. if dstIP.Is6() {
  42. ping = "ping6"
  43. }
  44. err = exec.Command(ping, "-c", "1", "-w", "3", dstIP.String()).Run()
  45. case "android":
  46. ping := "/system/bin/ping"
  47. if dstIP.Is6() {
  48. ping = "/system/bin/ping6"
  49. }
  50. err = exec.Command(ping, "-c", "1", "-w", "3", dstIP.String()).Run()
  51. default:
  52. ping := "ping"
  53. if isSynology {
  54. ping = "/bin/ping"
  55. }
  56. cmd := exec.Command(ping, "-c", "1", "-W", "3", dstIP.String())
  57. if isSynology && os.Getuid() != 0 {
  58. // On DSM7 we run as non-root and need to pass
  59. // CAP_NET_RAW if our binary has it.
  60. setAmbientCapsRaw(cmd)
  61. }
  62. err = cmd.Run()
  63. }
  64. return err
  65. }