wol.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package hostinfo
  4. import (
  5. "log"
  6. "net"
  7. "runtime"
  8. "strings"
  9. "unicode"
  10. "tailscale.com/envknob"
  11. )
  12. // TODO(bradfitz): this is all too simplistic and static. It needs to run
  13. // continuously in response to netmon events (USB ethernet adapaters might get
  14. // plugged in) and look for the media type/status/etc. Right now on macOS it
  15. // still detects a half dozen "up" en0, en1, en2, en3 etc interfaces that don't
  16. // have any media. We should only report the one that's actually connected.
  17. // But it works for now (2023-10-05) for fleshing out the rest.
  18. var wakeMAC = envknob.RegisterString("TS_WAKE_MAC") // mac address, "false" or "auto". for https://github.com/tailscale/tailscale/issues/306
  19. // getWoLMACs returns up to 10 MAC address of the local machine to send
  20. // wake-on-LAN packets to in order to wake it up. The returned MACs are in
  21. // lowercase hex colon-separated form ("xx:xx:xx:xx:xx:xx").
  22. //
  23. // If TS_WAKE_MAC=auto, it tries to automatically find the MACs based on the OS
  24. // type and interface properties. (TODO(bradfitz): incomplete) If TS_WAKE_MAC is
  25. // set to a MAC address, that sole MAC address is returned.
  26. func getWoLMACs() (macs []string) {
  27. switch runtime.GOOS {
  28. case "ios", "android":
  29. return nil
  30. }
  31. if s := wakeMAC(); s != "" {
  32. switch s {
  33. case "auto":
  34. ifs, _ := net.Interfaces()
  35. for _, iface := range ifs {
  36. if iface.Flags&net.FlagLoopback != 0 {
  37. continue
  38. }
  39. if iface.Flags&net.FlagBroadcast == 0 ||
  40. iface.Flags&net.FlagRunning == 0 ||
  41. iface.Flags&net.FlagUp == 0 {
  42. continue
  43. }
  44. if keepMAC(iface.Name, iface.HardwareAddr) {
  45. macs = append(macs, iface.HardwareAddr.String())
  46. }
  47. if len(macs) == 10 {
  48. break
  49. }
  50. }
  51. return macs
  52. case "false", "off": // fast path before ParseMAC error
  53. return nil
  54. }
  55. mac, err := net.ParseMAC(s)
  56. if err != nil {
  57. log.Printf("invalid MAC %q", s)
  58. return nil
  59. }
  60. return []string{mac.String()}
  61. }
  62. return nil
  63. }
  64. var ignoreWakeOUI = map[[3]byte]bool{
  65. {0x00, 0x15, 0x5d}: true, // Hyper-V
  66. {0x00, 0x50, 0x56}: true, // VMware
  67. {0x00, 0x1c, 0x14}: true, // VMware
  68. {0x00, 0x05, 0x69}: true, // VMware
  69. {0x00, 0x0c, 0x29}: true, // VMware
  70. {0x00, 0x1c, 0x42}: true, // Parallels
  71. {0x08, 0x00, 0x27}: true, // VirtualBox
  72. {0x00, 0x21, 0xf6}: true, // VirtualBox
  73. {0x00, 0x14, 0x4f}: true, // VirtualBox
  74. {0x00, 0x0f, 0x4b}: true, // VirtualBox
  75. {0x52, 0x54, 0x00}: true, // VirtualBox/Vagrant
  76. }
  77. func keepMAC(ifName string, mac []byte) bool {
  78. if len(mac) != 6 {
  79. return false
  80. }
  81. base := strings.TrimRightFunc(ifName, unicode.IsNumber)
  82. switch runtime.GOOS {
  83. case "darwin":
  84. switch base {
  85. case "llw", "awdl", "utun", "bridge", "lo", "gif", "stf", "anpi", "ap":
  86. return false
  87. }
  88. }
  89. if mac[0] == 0x02 && mac[1] == 0x42 {
  90. // Docker container.
  91. return false
  92. }
  93. oui := [3]byte{mac[0], mac[1], mac[2]}
  94. if ignoreWakeOUI[oui] {
  95. return false
  96. }
  97. return true
  98. }