defaultroute_ios.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build ios
  4. package interfaces
  5. import (
  6. "log"
  7. "tailscale.com/syncs"
  8. )
  9. var (
  10. lastKnownDefaultRouteIfName syncs.AtomicValue[string]
  11. )
  12. // UpdateLastKnownDefaultRouteInterface is called by ipn-go-bridge in the iOS app when
  13. // our NWPathMonitor instance detects a network path transition.
  14. func UpdateLastKnownDefaultRouteInterface(ifName string) {
  15. if ifName == "" {
  16. return
  17. }
  18. lastKnownDefaultRouteIfName.Store(ifName)
  19. log.Printf("defaultroute_ios: update from Swift, ifName = %s", ifName)
  20. }
  21. func defaultRoute() (d DefaultRouteDetails, err error) {
  22. // We cannot rely on the delegated interface data on iOS. The NetworkExtension framework
  23. // seems to set the delegate interface only once, upon the *creation* of the VPN tunnel.
  24. // If a network transition (e.g. from Wi-Fi to Cellular) happens while the tunnel is
  25. // connected, it will be ignored and we will still try to set Wi-Fi as the default route
  26. // because the delegated interface is not updated by the NetworkExtension framework.
  27. //
  28. // We work around this on the Swift side with a NWPathMonitor instance that observes
  29. // the interface name of the first currently satisfied network path. Our Swift code will
  30. // call into `UpdateLastKnownDefaultRouteInterface`, so we can rely on that when it is set.
  31. //
  32. // If for any reason the Swift machinery didn't work and we don't get any updates, here
  33. // we also have some fallback logic: we try finding a hardcoded Wi-Fi interface called en0.
  34. // If en0 is down, we fall back to cellular (pdp_ip0) as a last resort. This doesn't handle
  35. // all edge cases like USB-Ethernet adapters or multiple Ethernet interfaces, but is good
  36. // enough to ensure connectivity isn't broken.
  37. // Start by getting all available interfaces.
  38. interfaces, err := netInterfaces()
  39. if err != nil {
  40. log.Printf("defaultroute_ios: could not get interfaces: %v", err)
  41. return d, ErrNoGatewayIndexFound
  42. }
  43. getInterfaceByName := func(name string) *Interface {
  44. for _, ifc := range interfaces {
  45. if ifc.Name != name {
  46. continue
  47. }
  48. if !ifc.IsUp() {
  49. log.Println("defaultroute_ios: %s is down", name)
  50. return nil
  51. }
  52. addrs, _ := ifc.Addrs()
  53. if len(addrs) == 0 {
  54. log.Println("defaultroute_ios: %s has no addresses", name)
  55. return nil
  56. }
  57. return &ifc
  58. }
  59. return nil
  60. }
  61. // Did Swift set lastKnownDefaultRouteInterface? If so, we should use it and don't bother
  62. // with anything else. However, for sanity, do check whether Swift gave us with an interface
  63. // that exists, is up, and has an address.
  64. if swiftIfName := lastKnownDefaultRouteIfName.Load(); swiftIfName != "" {
  65. ifc := getInterfaceByName(swiftIfName)
  66. if ifc != nil {
  67. log.Printf("defaultroute_ios: using %s (provided by Swift)", ifc.Name)
  68. d.InterfaceName = ifc.Name
  69. d.InterfaceIndex = ifc.Index
  70. return d, nil
  71. }
  72. }
  73. // Start of our fallback logic if Swift didn't give us an interface name, or gave us an invalid
  74. // one.
  75. // We start by attempting to use the Wi-Fi interface, which on iPhone is always called en0.
  76. enZeroIf := getInterfaceByName("en0")
  77. if enZeroIf != nil {
  78. log.Println("defaultroute_ios: using en0 (fallback)")
  79. d.InterfaceName = enZeroIf.Name
  80. d.InterfaceIndex = enZeroIf.Index
  81. return d, nil
  82. }
  83. // Did it not work? Let's try with Cellular (pdp_ip0).
  84. cellIf := getInterfaceByName("pdp_ip0")
  85. if cellIf != nil {
  86. log.Println("defaultroute_ios: using pdp_ip0 (fallback)")
  87. d.InterfaceName = cellIf.Name
  88. d.InterfaceIndex = cellIf.Index
  89. return d, nil
  90. }
  91. log.Println("defaultroute_ios: no running interfaces available")
  92. return d, ErrNoGatewayIndexFound
  93. }