interfaces.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Package interfaces contains helpers for looking up system network interfaces.
  5. package interfaces
  6. import (
  7. "bytes"
  8. "fmt"
  9. "net"
  10. "net/http"
  11. "runtime"
  12. "sort"
  13. "strings"
  14. "inet.af/netaddr"
  15. "tailscale.com/hostinfo"
  16. "tailscale.com/net/tsaddr"
  17. "tailscale.com/net/tshttpproxy"
  18. )
  19. // LoginEndpointForProxyDetermination is the URL used for testing
  20. // which HTTP proxy the system should use.
  21. var LoginEndpointForProxyDetermination = "https://controlplane.tailscale.com/"
  22. // Tailscale returns the current machine's Tailscale interface, if any.
  23. // If none is found, all zero values are returned.
  24. // A non-nil error is only returned on a problem listing the system interfaces.
  25. func Tailscale() ([]netaddr.IP, *net.Interface, error) {
  26. ifs, err := netInterfaces()
  27. if err != nil {
  28. return nil, nil, err
  29. }
  30. for _, iface := range ifs {
  31. if !maybeTailscaleInterfaceName(iface.Name) {
  32. continue
  33. }
  34. addrs, err := iface.Addrs()
  35. if err != nil {
  36. continue
  37. }
  38. var tsIPs []netaddr.IP
  39. for _, a := range addrs {
  40. if ipnet, ok := a.(*net.IPNet); ok {
  41. nip, ok := netaddr.FromStdIP(ipnet.IP)
  42. if ok && tsaddr.IsTailscaleIP(nip) {
  43. tsIPs = append(tsIPs, nip)
  44. }
  45. }
  46. }
  47. if len(tsIPs) > 0 {
  48. return tsIPs, iface.Interface, nil
  49. }
  50. }
  51. return nil, nil, nil
  52. }
  53. // maybeTailscaleInterfaceName reports whether s is an interface
  54. // name that might be used by Tailscale.
  55. func maybeTailscaleInterfaceName(s string) bool {
  56. return s == "Tailscale" ||
  57. strings.HasPrefix(s, "wg") ||
  58. strings.HasPrefix(s, "ts") ||
  59. strings.HasPrefix(s, "tailscale") ||
  60. strings.HasPrefix(s, "utun")
  61. }
  62. func isUp(nif *net.Interface) bool { return nif.Flags&net.FlagUp != 0 }
  63. func isLoopback(nif *net.Interface) bool { return nif.Flags&net.FlagLoopback != 0 }
  64. func isProblematicInterface(nif *net.Interface) bool {
  65. name := nif.Name
  66. // Don't try to send disco/etc packets over zerotier; they effectively
  67. // DoS each other by doing traffic amplification, both of them
  68. // preferring/trying to use each other for transport. See:
  69. // https://github.com/tailscale/tailscale/issues/1208
  70. if strings.HasPrefix(name, "zt") || (runtime.GOOS == "windows" && strings.Contains(name, "ZeroTier")) {
  71. return true
  72. }
  73. return false
  74. }
  75. // LocalAddresses returns the machine's IP addresses, separated by
  76. // whether they're loopback addresses. If there are no regular addresses
  77. // it will return any IPv4 linklocal or IPv6 unique local addresses because we
  78. // know of environments where these are used with NAT to provide connectivity.
  79. func LocalAddresses() (regular, loopback []netaddr.IP, err error) {
  80. // TODO(crawshaw): don't serve interface addresses that we are routing
  81. ifaces, err := netInterfaces()
  82. if err != nil {
  83. return nil, nil, err
  84. }
  85. var regular4, regular6, linklocal4, ula6 []netaddr.IP
  86. for _, iface := range ifaces {
  87. stdIf := iface.Interface
  88. if !isUp(stdIf) || isProblematicInterface(stdIf) {
  89. // Skip down interfaces and ones that are
  90. // problematic that we don't want to try to
  91. // send Tailscale traffic over.
  92. continue
  93. }
  94. ifcIsLoopback := isLoopback(stdIf)
  95. addrs, err := iface.Addrs()
  96. if err != nil {
  97. return nil, nil, err
  98. }
  99. for _, a := range addrs {
  100. switch v := a.(type) {
  101. case *net.IPNet:
  102. ip, ok := netaddr.FromStdIP(v.IP)
  103. if !ok {
  104. continue
  105. }
  106. // TODO(apenwarr): don't special case cgNAT.
  107. // In the general wireguard case, it might
  108. // very well be something we can route to
  109. // directly, because both nodes are
  110. // behind the same CGNAT router.
  111. if tsaddr.IsTailscaleIP(ip) {
  112. continue
  113. }
  114. if ip.IsLoopback() || ifcIsLoopback {
  115. loopback = append(loopback, ip)
  116. } else if ip.IsLinkLocalUnicast() {
  117. if ip.Is4() {
  118. linklocal4 = append(linklocal4, ip)
  119. }
  120. // We know of no cases where the IPv6 fe80:: addresses
  121. // are used to provide WAN connectivity. It is also very
  122. // common for users to have no IPv6 WAN connectivity,
  123. // but their OS supports IPv6 so they have an fe80::
  124. // address. We don't want to report all of those
  125. // IPv6 LL to Control.
  126. } else if ip.Is6() && ip.IsPrivate() {
  127. // Google Cloud Run uses NAT with IPv6 Unique
  128. // Local Addresses to provide IPv6 connectivity.
  129. ula6 = append(ula6, ip)
  130. } else {
  131. if ip.Is4() {
  132. regular4 = append(regular4, ip)
  133. } else {
  134. regular6 = append(regular6, ip)
  135. }
  136. }
  137. }
  138. }
  139. }
  140. if len(regular4) == 0 && len(regular6) == 0 {
  141. // if we have no usable IP addresses then be willing to accept
  142. // addresses we otherwise wouldn't, like:
  143. // + 169.254.x.x (AWS Lambda uses NAT with these)
  144. // + IPv6 ULA (Google Cloud Run uses these with address translation)
  145. if hostinfo.GetEnvType() == hostinfo.AWSLambda {
  146. regular4 = linklocal4
  147. }
  148. regular6 = ula6
  149. }
  150. regular = append(regular4, regular6...)
  151. sortIPs(regular)
  152. sortIPs(loopback)
  153. return regular, loopback, nil
  154. }
  155. func sortIPs(s []netaddr.IP) {
  156. sort.Slice(s, func(i, j int) bool { return s[i].Less(s[j]) })
  157. }
  158. // Interface is a wrapper around Go's net.Interface with some extra methods.
  159. type Interface struct {
  160. *net.Interface
  161. AltAddrs []net.Addr // if non-nil, returned by Addrs
  162. }
  163. func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) }
  164. func (i Interface) IsUp() bool { return isUp(i.Interface) }
  165. func (i Interface) Addrs() ([]net.Addr, error) {
  166. if i.AltAddrs != nil {
  167. return i.AltAddrs, nil
  168. }
  169. return i.Interface.Addrs()
  170. }
  171. // ForeachInterfaceAddress is a wrapper for GetList, then
  172. // List.ForeachInterfaceAddress.
  173. func ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
  174. ifaces, err := GetList()
  175. if err != nil {
  176. return err
  177. }
  178. return ifaces.ForeachInterfaceAddress(fn)
  179. }
  180. // ForeachInterfaceAddress calls fn for each interface in ifaces, with
  181. // all its addresses. The IPPrefix's IP is the IP address assigned to
  182. // the interface, and Bits are the subnet mask.
  183. func (ifaces List) ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
  184. for _, iface := range ifaces {
  185. addrs, err := iface.Addrs()
  186. if err != nil {
  187. return err
  188. }
  189. for _, a := range addrs {
  190. switch v := a.(type) {
  191. case *net.IPNet:
  192. if pfx, ok := netaddr.FromStdIPNet(v); ok {
  193. fn(iface, pfx)
  194. }
  195. }
  196. }
  197. }
  198. return nil
  199. }
  200. // ForeachInterface is a wrapper for GetList, then
  201. // List.ForeachInterface.
  202. func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
  203. ifaces, err := GetList()
  204. if err != nil {
  205. return err
  206. }
  207. return ifaces.ForeachInterface(fn)
  208. }
  209. // ForeachInterface calls fn for each interface in ifaces, with
  210. // all its addresses. The IPPrefix's IP is the IP address assigned to
  211. // the interface, and Bits are the subnet mask.
  212. func (ifaces List) ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
  213. ifaces, err := GetList()
  214. if err != nil {
  215. return err
  216. }
  217. for _, iface := range ifaces {
  218. addrs, err := iface.Addrs()
  219. if err != nil {
  220. return err
  221. }
  222. var pfxs []netaddr.IPPrefix
  223. for _, a := range addrs {
  224. switch v := a.(type) {
  225. case *net.IPNet:
  226. if pfx, ok := netaddr.FromStdIPNet(v); ok {
  227. pfxs = append(pfxs, pfx)
  228. }
  229. }
  230. }
  231. sort.Slice(pfxs, func(i, j int) bool {
  232. return pfxs[i].IP().Less(pfxs[j].IP())
  233. })
  234. fn(iface, pfxs)
  235. }
  236. return nil
  237. }
  238. // State is intended to store the state of the machine's network interfaces,
  239. // routing table, and other network configuration.
  240. // For now it's pretty basic.
  241. type State struct {
  242. // InterfaceIPs maps from an interface name to the IP addresses
  243. // configured on that interface. Each address is represented as an
  244. // IPPrefix, where the IP is the interface IP address and Bits is
  245. // the subnet mask.
  246. InterfaceIPs map[string][]netaddr.IPPrefix
  247. Interface map[string]Interface
  248. // HaveV6 is whether this machine has an IPv6 Global or Unique Local Address
  249. // which might provide connectivity on a non-Tailscale interface that's up.
  250. HaveV6 bool
  251. // HaveV4 is whether the machine has some non-localhost,
  252. // non-link-local IPv4 address on a non-Tailscale interface that's up.
  253. HaveV4 bool
  254. // IsExpensive is whether the current network interface is
  255. // considered "expensive", which currently means LTE/etc
  256. // instead of Wifi. This field is not populated by GetState.
  257. IsExpensive bool
  258. // DefaultRouteInterface is the interface name for the machine's default route.
  259. // It is not yet populated on all OSes.
  260. // Its exact value should not be assumed to be a map key for
  261. // the Interface maps above; it's only used for debugging.
  262. DefaultRouteInterface string
  263. // HTTPProxy is the HTTP proxy to use.
  264. HTTPProxy string
  265. // PAC is the URL to the Proxy Autoconfig URL, if applicable.
  266. PAC string
  267. }
  268. func (s *State) String() string {
  269. var sb strings.Builder
  270. fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ifs={", s.DefaultRouteInterface)
  271. ifs := make([]string, 0, len(s.Interface))
  272. for k := range s.Interface {
  273. if anyInterestingIP(s.InterfaceIPs[k]) {
  274. ifs = append(ifs, k)
  275. }
  276. }
  277. sort.Slice(ifs, func(i, j int) bool {
  278. upi, upj := s.Interface[ifs[i]].IsUp(), s.Interface[ifs[j]].IsUp()
  279. if upi != upj {
  280. // Up sorts before down.
  281. return upi
  282. }
  283. return ifs[i] < ifs[j]
  284. })
  285. for i, ifName := range ifs {
  286. if i > 0 {
  287. sb.WriteString(" ")
  288. }
  289. if s.Interface[ifName].IsUp() {
  290. fmt.Fprintf(&sb, "%s:[", ifName)
  291. needSpace := false
  292. for _, pfx := range s.InterfaceIPs[ifName] {
  293. if !isInterestingIP(pfx.IP()) {
  294. continue
  295. }
  296. if needSpace {
  297. sb.WriteString(" ")
  298. }
  299. fmt.Fprintf(&sb, "%s", pfx)
  300. needSpace = true
  301. }
  302. sb.WriteString("]")
  303. } else {
  304. fmt.Fprintf(&sb, "%s:down", ifName)
  305. }
  306. }
  307. sb.WriteString("}")
  308. if s.IsExpensive {
  309. sb.WriteString(" expensive")
  310. }
  311. if s.HTTPProxy != "" {
  312. fmt.Fprintf(&sb, " httpproxy=%s", s.HTTPProxy)
  313. }
  314. if s.PAC != "" {
  315. fmt.Fprintf(&sb, " pac=%s", s.PAC)
  316. }
  317. fmt.Fprintf(&sb, " v4=%v v6=%v}", s.HaveV4, s.HaveV6)
  318. return sb.String()
  319. }
  320. // EqualFiltered reports whether s and s2 are equal,
  321. // considering only interfaces in s for which filter returns true.
  322. func (s *State) EqualFiltered(s2 *State, filter func(i Interface, ips []netaddr.IPPrefix) bool) bool {
  323. if s == nil && s2 == nil {
  324. return true
  325. }
  326. if s == nil || s2 == nil {
  327. return false
  328. }
  329. if s.HaveV6 != s2.HaveV6 ||
  330. s.HaveV4 != s2.HaveV4 ||
  331. s.IsExpensive != s2.IsExpensive ||
  332. s.DefaultRouteInterface != s2.DefaultRouteInterface ||
  333. s.HTTPProxy != s2.HTTPProxy ||
  334. s.PAC != s2.PAC {
  335. return false
  336. }
  337. for iname, i := range s.Interface {
  338. ips := s.InterfaceIPs[iname]
  339. if !filter(i, ips) {
  340. continue
  341. }
  342. i2, ok := s2.Interface[iname]
  343. if !ok {
  344. return false
  345. }
  346. ips2, ok := s2.InterfaceIPs[iname]
  347. if !ok {
  348. return false
  349. }
  350. if !interfacesEqual(i, i2) || !prefixesEqual(ips, ips2) {
  351. return false
  352. }
  353. }
  354. return true
  355. }
  356. func interfacesEqual(a, b Interface) bool {
  357. return a.Index == b.Index &&
  358. a.MTU == b.MTU &&
  359. a.Name == b.Name &&
  360. a.Flags == b.Flags &&
  361. bytes.Equal([]byte(a.HardwareAddr), []byte(b.HardwareAddr))
  362. }
  363. func prefixesEqual(a, b []netaddr.IPPrefix) bool {
  364. if len(a) != len(b) {
  365. return false
  366. }
  367. for i, v := range a {
  368. if b[i] != v {
  369. return false
  370. }
  371. }
  372. return true
  373. }
  374. // FilterInteresting reports whether i is an interesting non-Tailscale interface.
  375. func FilterInteresting(i Interface, ips []netaddr.IPPrefix) bool {
  376. return !isTailscaleInterface(i.Name, ips) && anyInterestingIP(ips)
  377. }
  378. // FilterAll always returns true, to use EqualFiltered against all interfaces.
  379. func FilterAll(i Interface, ips []netaddr.IPPrefix) bool { return true }
  380. func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
  381. // AnyInterfaceUp reports whether any interface seems like it has Internet access.
  382. func (s *State) AnyInterfaceUp() bool {
  383. if runtime.GOOS == "js" {
  384. return true
  385. }
  386. return s != nil && (s.HaveV4 || s.HaveV6)
  387. }
  388. func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
  389. for _, pfx := range pfxs {
  390. if tsaddr.IsTailscaleIP(pfx.IP()) {
  391. return true
  392. }
  393. }
  394. return false
  395. }
  396. func isTailscaleInterface(name string, ips []netaddr.IPPrefix) bool {
  397. if runtime.GOOS == "darwin" && strings.HasPrefix(name, "utun") && hasTailscaleIP(ips) {
  398. // On macOS in the sandboxed app (at least as of
  399. // 2021-02-25), we often see two utun devices
  400. // (e.g. utun4 and utun7) with the same IPv4 and IPv6
  401. // addresses. Just remove all utun devices with
  402. // Tailscale IPs until we know what's happening with
  403. // macOS NetworkExtensions and utun devices.
  404. return true
  405. }
  406. return name == "Tailscale" || // as it is on Windows
  407. strings.HasPrefix(name, "tailscale") // TODO: use --tun flag value, etc; see TODO in method doc
  408. }
  409. // getPAC, if non-nil, returns the current PAC file URL.
  410. var getPAC func() string
  411. // GetState returns the state of all the current machine's network interfaces.
  412. //
  413. // It does not set the returned State.IsExpensive. The caller can populate that.
  414. func GetState() (*State, error) {
  415. s := &State{
  416. InterfaceIPs: make(map[string][]netaddr.IPPrefix),
  417. Interface: make(map[string]Interface),
  418. }
  419. if err := ForeachInterface(func(ni Interface, pfxs []netaddr.IPPrefix) {
  420. ifUp := ni.IsUp()
  421. s.Interface[ni.Name] = ni
  422. s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
  423. if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
  424. return
  425. }
  426. for _, pfx := range pfxs {
  427. if pfx.IP().IsLoopback() {
  428. continue
  429. }
  430. s.HaveV6 = s.HaveV6 || isUsableV6(pfx.IP())
  431. s.HaveV4 = s.HaveV4 || isUsableV4(pfx.IP())
  432. }
  433. }); err != nil {
  434. return nil, err
  435. }
  436. s.DefaultRouteInterface, _ = DefaultRouteInterface()
  437. if s.AnyInterfaceUp() {
  438. req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
  439. if err != nil {
  440. return nil, err
  441. }
  442. if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
  443. s.HTTPProxy = u.String()
  444. }
  445. if getPAC != nil {
  446. s.PAC = getPAC()
  447. }
  448. }
  449. return s, nil
  450. }
  451. // HTTPOfListener returns the HTTP address to ln.
  452. // If the listener is listening on the unspecified address, it
  453. // it tries to find a reasonable interface address on the machine to use.
  454. func HTTPOfListener(ln net.Listener) string {
  455. ta, ok := ln.Addr().(*net.TCPAddr)
  456. if !ok || !ta.IP.IsUnspecified() {
  457. return fmt.Sprintf("http://%v/", ln.Addr())
  458. }
  459. var goodIP string
  460. var privateIP string
  461. ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
  462. ip := pfx.IP()
  463. if ip.IsPrivate() {
  464. if privateIP == "" {
  465. privateIP = ip.String()
  466. }
  467. return
  468. }
  469. goodIP = ip.String()
  470. })
  471. if privateIP != "" {
  472. goodIP = privateIP
  473. }
  474. if goodIP != "" {
  475. return fmt.Sprintf("http://%v/", net.JoinHostPort(goodIP, fmt.Sprint(ta.Port)))
  476. }
  477. return fmt.Sprintf("http://localhost:%v/", fmt.Sprint(ta.Port))
  478. }
  479. var likelyHomeRouterIP func() (netaddr.IP, bool)
  480. // LikelyHomeRouterIP returns the likely IP of the residential router,
  481. // which will always be an IPv4 private address, if found.
  482. // In addition, it returns the IP address of the current machine on
  483. // the LAN using that gateway.
  484. // This is used as the destination for UPnP, NAT-PMP, PCP, etc queries.
  485. func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) {
  486. if likelyHomeRouterIP != nil {
  487. gateway, ok = likelyHomeRouterIP()
  488. if !ok {
  489. return
  490. }
  491. }
  492. if !ok {
  493. return
  494. }
  495. ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
  496. ip := pfx.IP()
  497. if !i.IsUp() || ip.IsZero() || !myIP.IsZero() {
  498. return
  499. }
  500. if gateway.IsPrivate() && ip.IsPrivate() {
  501. myIP = ip
  502. ok = true
  503. return
  504. }
  505. })
  506. return gateway, myIP, !myIP.IsZero()
  507. }
  508. // isUsableV4 reports whether ip is a usable IPv4 address which could
  509. // conceivably be used to get Internet connectivity. Globally routable and
  510. // private IPv4 addresses are always Usable, and link local 169.254.x.x
  511. // addresses are in some environments.
  512. func isUsableV4(ip netaddr.IP) bool {
  513. if !ip.Is4() || ip.IsLoopback() {
  514. return false
  515. }
  516. if ip.IsLinkLocalUnicast() {
  517. return hostinfo.GetEnvType() == hostinfo.AWSLambda
  518. }
  519. return true
  520. }
  521. // isUsableV6 reports whether ip is a usable IPv6 address which could
  522. // conceivably be used to get Internet connectivity. Globally routable
  523. // IPv6 addresses are always Usable, and Unique Local Addresses
  524. // (fc00::/7) are in some environments used with address translation.
  525. func isUsableV6(ip netaddr.IP) bool {
  526. return v6Global1.Contains(ip) ||
  527. (ip.Is6() && ip.IsPrivate() && !tsaddr.TailscaleULARange().Contains(ip))
  528. }
  529. var (
  530. v6Global1 = netaddr.MustParseIPPrefix("2000::/3")
  531. )
  532. // anyInterestingIP reports whether pfxs contains any IP that matches
  533. // isInterestingIP.
  534. func anyInterestingIP(pfxs []netaddr.IPPrefix) bool {
  535. for _, pfx := range pfxs {
  536. if isInterestingIP(pfx.IP()) {
  537. return true
  538. }
  539. }
  540. return false
  541. }
  542. // isInterestingIP reports whether ip is an interesting IP that we
  543. // should log in interfaces.State logging. We don't need to show
  544. // localhost or link-local addresses.
  545. func isInterestingIP(ip netaddr.IP) bool {
  546. if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
  547. return false
  548. }
  549. return true
  550. }
  551. var altNetInterfaces func() ([]Interface, error)
  552. // RegisterInterfaceGetter sets the function that's used to query
  553. // the system network interfaces.
  554. func RegisterInterfaceGetter(getInterfaces func() ([]Interface, error)) {
  555. altNetInterfaces = getInterfaces
  556. }
  557. // List is a list of interfaces on the machine.
  558. type List []Interface
  559. // GetList returns the list of interfaces on the machine.
  560. func GetList() (List, error) {
  561. return netInterfaces()
  562. }
  563. // netInterfaces is a wrapper around the standard library's net.Interfaces
  564. // that returns a []*Interface instead of a []net.Interface.
  565. // It exists because Android SDK 30 no longer permits Go's net.Interfaces
  566. // to work (Issue 2293); this wrapper lets us the Android app register
  567. // an alternate implementation.
  568. func netInterfaces() ([]Interface, error) {
  569. if altNetInterfaces != nil {
  570. return altNetInterfaces()
  571. }
  572. ifs, err := net.Interfaces()
  573. if err != nil {
  574. return nil, err
  575. }
  576. ret := make([]Interface, len(ifs))
  577. for i := range ifs {
  578. ret[i].Interface = &ifs[i]
  579. }
  580. return ret, nil
  581. }