netstack_userping.go 2.1 KB

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