upnp.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !js
  4. // (no raw sockets in JS/WASM)
  5. package portmapper
  6. import (
  7. "bufio"
  8. "bytes"
  9. "cmp"
  10. "context"
  11. "encoding/xml"
  12. "fmt"
  13. "io"
  14. "math/rand"
  15. "net"
  16. "net/http"
  17. "net/netip"
  18. "net/url"
  19. "slices"
  20. "strings"
  21. "sync/atomic"
  22. "time"
  23. "github.com/tailscale/goupnp"
  24. "github.com/tailscale/goupnp/dcps/internetgateway2"
  25. "github.com/tailscale/goupnp/soap"
  26. "tailscale.com/envknob"
  27. "tailscale.com/net/netns"
  28. "tailscale.com/types/logger"
  29. "tailscale.com/util/mak"
  30. )
  31. // References:
  32. //
  33. // WANIP Connection v2: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
  34. // upnpMapping is a port mapping over the upnp protocol. After being created it is immutable,
  35. // but the client field may be shared across mapping instances.
  36. type upnpMapping struct {
  37. gw netip.Addr
  38. external netip.AddrPort
  39. internal netip.AddrPort
  40. goodUntil time.Time
  41. renewAfter time.Time
  42. // rootDev is the UPnP root device, and may be reused across different
  43. // UPnP mappings.
  44. rootDev *goupnp.RootDevice
  45. // loc is the location used to fetch the rootDev
  46. loc *url.URL
  47. // client is the most recent UPnP client used, and should only be used
  48. // to release an existing mapping; new mappings should be selected from
  49. // the rootDev on each attempt.
  50. client upnpClient
  51. }
  52. // upnpProtocolUDP represents the protocol name for UDP, to be used in the UPnP
  53. // <AddPortMapping> message in the <NewProtocol> field.
  54. //
  55. // NOTE: this must be an upper-case string, or certain routers will reject the
  56. // mapping request. Other implementations like miniupnp send an upper-case
  57. // protocol as well. See:
  58. //
  59. // https://github.com/tailscale/tailscale/issues/7377
  60. const upnpProtocolUDP = "UDP"
  61. func (u *upnpMapping) MappingType() string { return "upnp" }
  62. func (u *upnpMapping) GoodUntil() time.Time { return u.goodUntil }
  63. func (u *upnpMapping) RenewAfter() time.Time { return u.renewAfter }
  64. func (u *upnpMapping) External() netip.AddrPort { return u.external }
  65. func (u *upnpMapping) MappingDebug() string {
  66. return fmt.Sprintf("upnpMapping{gw:%v, external:%v, internal:%v, renewAfter:%d, goodUntil:%d, loc:%q}",
  67. u.gw, u.external, u.internal,
  68. u.renewAfter.Unix(), u.goodUntil.Unix(),
  69. u.loc)
  70. }
  71. func (u *upnpMapping) Release(ctx context.Context) {
  72. u.client.DeletePortMapping(ctx, "", u.external.Port(), upnpProtocolUDP)
  73. }
  74. // upnpClient is an interface over the multiple different clients exported by goupnp,
  75. // exposing the functions we need for portmapping. Those clients are auto-generated from XML-specs,
  76. // which is why they're not very idiomatic.
  77. type upnpClient interface {
  78. AddPortMapping(
  79. ctx context.Context,
  80. // remoteHost is the remote device sending packets to this device, in the format of x.x.x.x.
  81. // The empty string, "", means any host out on the internet can send packets in.
  82. remoteHost string,
  83. // externalPort is the exposed port of this port mapping. Visible during NAT operations.
  84. // 0 will let the router select the port, but there is an additional call,
  85. // `AddAnyPortMapping`, which is available on 1 of the 3 possible protocols,
  86. // which should be used if available. See `addAnyPortMapping` below, which calls this if
  87. // `AddAnyPortMapping` is not supported.
  88. externalPort uint16,
  89. // protocol is whether this is over TCP or UDP. Either "TCP" or "UDP".
  90. protocol string,
  91. // internalPort is the port that the gateway device forwards the traffic to.
  92. internalPort uint16,
  93. // internalClient is the IP address that packets will be forwarded to for this mapping.
  94. // Internal client is of the form "x.x.x.x".
  95. internalClient string,
  96. // enabled is whether this portmapping should be enabled or disabled.
  97. enabled bool,
  98. // portMappingDescription is a user-readable description of this portmapping.
  99. portMappingDescription string,
  100. // leaseDurationSec is the duration of this portmapping. The value of this argument must be
  101. // greater than 0. From the spec, it appears if it is set to 0, it will switch to using
  102. // 604800 seconds, but not sure why this is desired. The recommended time is 3600 seconds.
  103. leaseDurationSec uint32,
  104. ) error
  105. DeletePortMapping(ctx context.Context, remoteHost string, externalPort uint16, protocol string) error
  106. GetExternalIPAddress(ctx context.Context) (externalIPAddress string, err error)
  107. GetStatusInfo(ctx context.Context) (status string, lastConnError string, uptime uint32, err error)
  108. }
  109. // tsPortMappingDesc gets sent to UPnP clients as a human-readable label for the portmapping.
  110. // It is not used for anything other than labelling.
  111. const tsPortMappingDesc = "tailscale-portmap"
  112. // addAnyPortMapping abstracts over different UPnP client connections, calling
  113. // the available AddAnyPortMapping call if available for WAN IP connection v2,
  114. // otherwise picking either the previous port (if one is present) or a random
  115. // port and trying to obtain a mapping using AddPortMapping.
  116. //
  117. // It returns the new external port (which may not be identical to the external
  118. // port specified), or an error.
  119. //
  120. // TODO(bradfitz): also returned the actual lease duration obtained. and check it regularly.
  121. func addAnyPortMapping(
  122. ctx context.Context,
  123. upnp upnpClient,
  124. externalPort uint16,
  125. internalPort uint16,
  126. internalClient string,
  127. leaseDuration time.Duration,
  128. ) (newPort uint16, err error) {
  129. // Some devices don't let clients add a port mapping for privileged
  130. // ports (ports below 1024). Additionally, per section 2.3.18 of the
  131. // UPnP spec, regarding the ExternalPort field:
  132. //
  133. // If this value is specified as a wildcard (i.e. 0), connection
  134. // request on all external ports (that are not otherwise mapped)
  135. // will be forwarded to InternalClient. In the wildcard case, the
  136. // value(s) of InternalPort on InternalClient are ignored by the IGD
  137. // for those connections that are forwarded to InternalClient.
  138. // Obviously only one such entry can exist in the NAT at any time
  139. // and conflicts are handled with a “first write wins” behavior.
  140. //
  141. // We obviously do not want to open all ports on the user's device to
  142. // the internet, so we want to do this prior to calling either
  143. // AddAnyPortMapping or AddPortMapping.
  144. //
  145. // Pick an external port that's greater than 1024 by getting a random
  146. // number in [0, 65535 - 1024] and then adding 1024 to it, shifting the
  147. // range to [1024, 65535].
  148. if externalPort < 1024 {
  149. externalPort = uint16(rand.Intn(65535-1024) + 1024)
  150. }
  151. // First off, try using AddAnyPortMapping; if there's a conflict, the
  152. // router will pick another port and return it.
  153. if upnp, ok := upnp.(*internetgateway2.WANIPConnection2); ok {
  154. return upnp.AddAnyPortMapping(
  155. ctx,
  156. "",
  157. externalPort,
  158. upnpProtocolUDP,
  159. internalPort,
  160. internalClient,
  161. true,
  162. tsPortMappingDesc,
  163. uint32(leaseDuration.Seconds()),
  164. )
  165. }
  166. // Fall back to using AddPortMapping, which requests a mapping to/from
  167. // a specific external port.
  168. err = upnp.AddPortMapping(
  169. ctx,
  170. "",
  171. externalPort,
  172. upnpProtocolUDP,
  173. internalPort,
  174. internalClient,
  175. true,
  176. tsPortMappingDesc,
  177. uint32(leaseDuration.Seconds()),
  178. )
  179. return externalPort, err
  180. }
  181. // getUPnPRootDevice fetches the UPnP root device given the discovery response,
  182. // ignoring the underlying protocol for now.
  183. // Adapted from https://github.com/huin/goupnp/blob/master/GUIDE.md.
  184. //
  185. // The gw is the detected gateway.
  186. //
  187. // The meta is the most recently parsed UDP discovery packet response
  188. // from the Internet Gateway Device.
  189. func getUPnPRootDevice(ctx context.Context, logf logger.Logf, debug DebugKnobs, gw netip.Addr, meta uPnPDiscoResponse) (rootDev *goupnp.RootDevice, loc *url.URL, err error) {
  190. if debug.DisableUPnP {
  191. return nil, nil, nil
  192. }
  193. if meta.Location == "" {
  194. return nil, nil, nil
  195. }
  196. if debug.VerboseLogs {
  197. logf("fetching %v", meta.Location)
  198. }
  199. u, err := url.Parse(meta.Location)
  200. if err != nil {
  201. return nil, nil, err
  202. }
  203. ipp, err := netip.ParseAddrPort(u.Host)
  204. if err != nil {
  205. return nil, nil, fmt.Errorf("unexpected host %q in %q", u.Host, meta.Location)
  206. }
  207. if ipp.Addr() != gw {
  208. // https://github.com/tailscale/tailscale/issues/5502
  209. logf("UPnP discovered root %q does not match gateway IP %v; repointing at gateway which is assumed to be floating",
  210. meta.Location, gw)
  211. u.Host = net.JoinHostPort(gw.String(), u.Port())
  212. }
  213. // We're fetching a smallish XML document over plain HTTP
  214. // across the local LAN, without using DNS. There should be
  215. // very few round trips and low latency, so one second is a
  216. // long time.
  217. ctx, cancel := context.WithTimeout(ctx, time.Second)
  218. defer cancel()
  219. // This part does a network fetch.
  220. root, err := goupnp.DeviceByURL(ctx, u)
  221. if err != nil {
  222. return nil, nil, err
  223. }
  224. return root, u, nil
  225. }
  226. // selectBestService picks the "best" service from the given UPnP root device
  227. // to use to create a port mapping. It may return (nil, nil) if no supported
  228. // service was found in the provided *goupnp.RootDevice.
  229. //
  230. // loc is the parsed location that was used to fetch the given RootDevice.
  231. //
  232. // The provided ctx is not retained in the returned upnpClient, but
  233. // its associated HTTP client is (if set via goupnp.WithHTTPClient).
  234. func selectBestService(ctx context.Context, logf logger.Logf, root *goupnp.RootDevice, loc *url.URL) (client upnpClient, err error) {
  235. method := "none"
  236. defer func() {
  237. if client == nil {
  238. return
  239. }
  240. logf("saw UPnP type %v at %v; %v (%v), method=%s",
  241. strings.TrimPrefix(fmt.Sprintf("%T", client), "*internetgateway2."),
  242. loc, root.Device.FriendlyName, root.Device.Manufacturer,
  243. method)
  244. }()
  245. // First, get all available clients from the device, and append to our
  246. // list of possible clients. Order matters here; we want to prefer
  247. // WANIPConnection2 over WANIPConnection1 or WANPPPConnection.
  248. wanIP2, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(ctx, root, loc)
  249. wanIP1, _ := internetgateway2.NewWANIPConnection1ClientsFromRootDevice(ctx, root, loc)
  250. wanPPP, _ := internetgateway2.NewWANPPPConnection1ClientsFromRootDevice(ctx, root, loc)
  251. var clients []upnpClient
  252. for _, v := range wanIP2 {
  253. clients = append(clients, v)
  254. }
  255. for _, v := range wanIP1 {
  256. clients = append(clients, v)
  257. }
  258. for _, v := range wanPPP {
  259. clients = append(clients, v)
  260. }
  261. // If we have no clients, then return right now; if we only have one,
  262. // just select and return it.
  263. if len(clients) == 0 {
  264. return nil, nil
  265. }
  266. if len(clients) == 1 {
  267. method = "single"
  268. metricUPnPSelectSingle.Add(1)
  269. return clients[0], nil
  270. }
  271. metricUPnPSelectMultiple.Add(1)
  272. // In order to maximize the chances that we find a valid UPnP device
  273. // that can give us a port mapping, we check a few properties:
  274. // 1. Whether the device is "online", as defined by GetStatusInfo
  275. // 2. Whether the device has an external IP address, as defined by
  276. // GetExternalIPAddress
  277. // 3. Whether the device's external IP address is a public address
  278. // or a private one.
  279. //
  280. // We prefer a device where all of the above is true, and fall back if
  281. // none are found.
  282. //
  283. // In order to save on network requests, iterate through all devices
  284. // and determine how many "points" they have based on the above
  285. // criteria, but return immediately if we find one that meets all
  286. // three.
  287. var (
  288. connected = make(map[upnpClient]bool)
  289. externalIPs map[upnpClient]netip.Addr
  290. )
  291. for _, svc := range clients {
  292. isConnected := serviceIsConnected(ctx, logf, svc)
  293. connected[svc] = isConnected
  294. // Don't bother checking for an external IP if the device isn't
  295. // connected; technically this could happen with a misbehaving
  296. // device, but that seems unlikely.
  297. if !isConnected {
  298. continue
  299. }
  300. // Check if the device has an external IP address.
  301. extIP, err := svc.GetExternalIPAddress(ctx)
  302. if err != nil {
  303. continue
  304. }
  305. externalIP, err := netip.ParseAddr(extIP)
  306. if err != nil {
  307. continue
  308. }
  309. mak.Set(&externalIPs, svc, externalIP)
  310. // If we get here, this device has a non-private external IP
  311. // and is up, so we can just return it.
  312. if !externalIP.IsPrivate() {
  313. method = "ext-public"
  314. metricUPnPSelectExternalPublic.Add(1)
  315. return svc, nil
  316. }
  317. }
  318. // Okay, we have no devices that meet all the available options. Fall
  319. // back to first checking for devices that are up and have a private
  320. // external IP (order matters), and then devices that are up, and then
  321. // just anything at all.
  322. //
  323. // try=0 Up + private external IP
  324. // try=1 Up
  325. for try := 0; try <= 1; try++ {
  326. for _, svc := range clients {
  327. if !connected[svc] {
  328. continue
  329. }
  330. _, hasExtIP := externalIPs[svc]
  331. if hasExtIP {
  332. method = "ext-private"
  333. metricUPnPSelectExternalPrivate.Add(1)
  334. return svc, nil
  335. } else if try == 1 {
  336. method = "up"
  337. metricUPnPSelectUp.Add(1)
  338. return svc, nil
  339. }
  340. }
  341. }
  342. // Nothing is up, but we have something (length of clients checked
  343. // above); just return the first one.
  344. metricUPnPSelectNone.Add(1)
  345. return clients[0], nil
  346. }
  347. // serviceIsConnected returns whether a given UPnP service is connected, based
  348. // on the NewConnectionStatus field returned from GetStatusInfo.
  349. func serviceIsConnected(ctx context.Context, logf logger.Logf, svc upnpClient) bool {
  350. status, _ /* NewLastConnectionError */, _ /* NewUptime */, err := svc.GetStatusInfo(ctx)
  351. if err != nil {
  352. return false
  353. }
  354. return status == "Connected" || status == "Up"
  355. }
  356. func (c *Client) upnpHTTPClientLocked() *http.Client {
  357. if c.uPnPHTTPClient == nil {
  358. c.uPnPHTTPClient = &http.Client{
  359. Transport: &http.Transport{
  360. DialContext: netns.NewDialer(c.logf, c.netMon).DialContext,
  361. IdleConnTimeout: 2 * time.Second, // LAN is cheap
  362. },
  363. }
  364. if c.debug.LogHTTP {
  365. c.uPnPHTTPClient = requestLogger(c.logf, c.uPnPHTTPClient)
  366. }
  367. }
  368. return c.uPnPHTTPClient
  369. }
  370. var (
  371. disableUPnpEnv = envknob.RegisterBool("TS_DISABLE_UPNP")
  372. )
  373. // getUPnPPortMapping attempts to create a port-mapping over the UPnP protocol. On success,
  374. // it will return the externally exposed IP and port. Otherwise, it will return a zeroed IP and
  375. // port and an error.
  376. func (c *Client) getUPnPPortMapping(
  377. ctx context.Context,
  378. gw netip.Addr,
  379. internal netip.AddrPort,
  380. prevPort uint16,
  381. ) (external netip.AddrPort, ok bool) {
  382. if disableUPnpEnv() || c.debug.DisableUPnP || (c.controlKnobs != nil && c.controlKnobs.DisableUPnP.Load()) {
  383. return netip.AddrPort{}, false
  384. }
  385. now := time.Now()
  386. upnp := &upnpMapping{
  387. gw: gw,
  388. internal: internal,
  389. }
  390. // We can have multiple UPnP "meta" values (which correspond to the
  391. // UPnP discovery responses received). We want to try all of them when
  392. // obtaining a mapping, but also prefer any existing mapping's root
  393. // device (if present), since that will allow us to renew an existing
  394. // mapping instead of creating a new one.
  395. // Start by grabbing the list of metas, any existing mapping, and
  396. // creating a HTTP client for use.
  397. c.mu.Lock()
  398. oldMapping, ok := c.mapping.(*upnpMapping)
  399. metas := c.uPnPMetas
  400. ctx = goupnp.WithHTTPClient(ctx, c.upnpHTTPClientLocked())
  401. c.mu.Unlock()
  402. // Wrapper for a uPnPDiscoResponse with an optional existing root
  403. // device + URL (if we've got a previous cached mapping).
  404. type step struct {
  405. rootDev *goupnp.RootDevice // if nil, use 'meta'
  406. loc *url.URL // non-nil if rootDev is non-nil
  407. meta uPnPDiscoResponse
  408. }
  409. var steps []step
  410. // Now, if we have an existing mapping, swap that mapping's entry to
  411. // the first entry in our "metas" list so we try it first.
  412. haveOldMapping := ok && oldMapping != nil
  413. if haveOldMapping && oldMapping.rootDev != nil {
  414. steps = append(steps, step{rootDev: oldMapping.rootDev, loc: oldMapping.loc})
  415. }
  416. // Note: this includes the meta for a previously-cached mapping, in
  417. // case the rootDev changes.
  418. for _, meta := range metas {
  419. steps = append(steps, step{meta: meta})
  420. }
  421. // Now, iterate through every meta that we have trying to get an
  422. // external IP address. If we succeed, we'll return; if we fail, we
  423. // continue this loop.
  424. var errs []error
  425. for _, step := range steps {
  426. var (
  427. rootDev *goupnp.RootDevice
  428. loc *url.URL
  429. err error
  430. )
  431. if step.rootDev != nil {
  432. rootDev = step.rootDev
  433. loc = step.loc
  434. } else {
  435. rootDev, loc, err = getUPnPRootDevice(ctx, c.logf, c.debug, gw, step.meta)
  436. c.vlogf("getUPnPRootDevice: loc=%q err=%v", loc, err)
  437. if err != nil {
  438. errs = append(errs, err)
  439. continue
  440. }
  441. }
  442. if rootDev == nil {
  443. continue
  444. }
  445. // This actually performs the port mapping operation using this
  446. // root device.
  447. //
  448. // TODO(andrew-d): this can successfully perform a portmap and
  449. // return an externalAddrPort that refers to a non-public IP
  450. // address if the first selected RootDevice is a device that is
  451. // connected to another internal network. This is still better
  452. // than randomly flapping between multiple devices, but we
  453. // should probably split this up further to try the best
  454. // service (one with an external IP) first, instead of
  455. // iterating by device.
  456. //
  457. // This is probably sufficiently unlikely that I'm leaving that
  458. // as a follow-up task if it's necessary.
  459. externalAddrPort, client, err := c.tryUPnPPortmapWithDevice(ctx, internal, prevPort, rootDev, loc)
  460. if err != nil {
  461. errs = append(errs, err)
  462. continue
  463. }
  464. // If we get here, we're successful; we can cache this mapping,
  465. // update our local port, and then return.
  466. //
  467. // NOTE: this time might not technically be accurate if we created a
  468. // permanent lease above, but we should still re-check the presence of
  469. // the lease on a regular basis so we use it anyway.
  470. d := time.Duration(pmpMapLifetimeSec) * time.Second
  471. upnp.goodUntil = now.Add(d)
  472. upnp.renewAfter = now.Add(d / 2)
  473. upnp.external = externalAddrPort
  474. upnp.rootDev = rootDev
  475. upnp.loc = loc
  476. upnp.client = client
  477. c.mu.Lock()
  478. defer c.mu.Unlock()
  479. c.mapping = upnp
  480. c.localPort = externalAddrPort.Port()
  481. return upnp.external, true
  482. }
  483. // If we get here, we didn't get anything.
  484. // TODO(andrew-d): use or log errs?
  485. _ = errs
  486. return netip.AddrPort{}, false
  487. }
  488. // tryUPnPPortmapWithDevice attempts to perform a port forward from the given
  489. // UPnP device to the 'internal' address. It tries to re-use the previous port,
  490. // if a non-zero value is provided, and handles retries and errors about
  491. // unsupported features.
  492. //
  493. // It returns the external address and port that was mapped (i.e. the
  494. // address+port that another Tailscale node can use to make a connection to
  495. // this one) and the UPnP client that was used to obtain that mapping.
  496. func (c *Client) tryUPnPPortmapWithDevice(
  497. ctx context.Context,
  498. internal netip.AddrPort,
  499. prevPort uint16,
  500. rootDev *goupnp.RootDevice,
  501. loc *url.URL,
  502. ) (netip.AddrPort, upnpClient, error) {
  503. // Select the best mapping service from the given root device. This
  504. // makes network requests, and can vary from mapping to mapping if the
  505. // upstream device's connection status changes.
  506. client, err := selectBestService(ctx, c.logf, rootDev, loc)
  507. if err != nil {
  508. return netip.AddrPort{}, nil, err
  509. }
  510. // If we have no client, we cannot continue; this can happen if we get
  511. // a valid UPnP response that does not contain any of the service types
  512. // that we know how to use.
  513. if client == nil {
  514. // For debugging, print all available services that we aren't
  515. // using because they're not supported; use c.vlogf so we don't
  516. // spam the logs unless verbose debugging is turned on.
  517. rootDev.Device.VisitServices(func(s *goupnp.Service) {
  518. c.vlogf("unsupported UPnP service: Type=%q ID=%q ControlURL=%q", s.ServiceType, s.ServiceId, s.ControlURL.Str)
  519. })
  520. return netip.AddrPort{}, nil, fmt.Errorf("no supported UPnP clients")
  521. }
  522. // Start by trying to make a temporary lease with a duration.
  523. var newPort uint16
  524. newPort, err = addAnyPortMapping(
  525. ctx,
  526. client,
  527. prevPort,
  528. internal.Port(),
  529. internal.Addr().String(),
  530. pmpMapLifetimeSec*time.Second,
  531. )
  532. c.vlogf("addAnyPortMapping: %v, err=%q", newPort, err)
  533. // If this is an error and the code is
  534. // "OnlyPermanentLeasesSupported", then we retry with no lease
  535. // duration; see the following issue for details:
  536. // https://github.com/tailscale/tailscale/issues/9343
  537. if err != nil {
  538. code, ok := getUPnPErrorCode(err)
  539. if ok {
  540. getUPnPErrorsMetric(code).Add(1)
  541. }
  542. // From the UPnP spec: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
  543. // 725: OnlyPermanentLeasesSupported
  544. if ok && code == 725 {
  545. newPort, err = addAnyPortMapping(
  546. ctx,
  547. client,
  548. prevPort,
  549. internal.Port(),
  550. internal.Addr().String(),
  551. 0, // permanent
  552. )
  553. c.vlogf("addAnyPortMapping: 725 retry %v, err=%q", newPort, err)
  554. }
  555. }
  556. if err != nil {
  557. return netip.AddrPort{}, nil, err
  558. }
  559. // TODO cache this ip somewhere?
  560. extIP, err := client.GetExternalIPAddress(ctx)
  561. c.vlogf("client.GetExternalIPAddress: %v, %v", extIP, err)
  562. if err != nil {
  563. return netip.AddrPort{}, nil, err
  564. }
  565. externalIP, err := netip.ParseAddr(extIP)
  566. if err != nil {
  567. return netip.AddrPort{}, nil, err
  568. }
  569. return netip.AddrPortFrom(externalIP, newPort), client, nil
  570. }
  571. // processUPnPResponses sorts and deduplicates a list of UPnP discovery
  572. // responses, returning the possibly-reduced list.
  573. //
  574. // It will perform a consistent sort of the provided responses, so if we have
  575. // multiple valid UPnP destinations a consistent option will be picked every
  576. // time.
  577. func processUPnPResponses(metas []uPnPDiscoResponse) []uPnPDiscoResponse {
  578. // Sort and compact all responses to remove duplicates; since
  579. // we send multiple probes, we often get duplicate responses.
  580. slices.SortFunc(metas, func(a, b uPnPDiscoResponse) int {
  581. // Sort the USN in reverse, so that
  582. // "InternetGatewayDevice:2" sorts before
  583. // "InternetGatewayDevice:1".
  584. if ii := cmp.Compare(a.USN, b.USN); ii != 0 {
  585. return -ii
  586. }
  587. if ii := cmp.Compare(a.Location, b.Location); ii != 0 {
  588. return ii
  589. }
  590. return cmp.Compare(a.Server, b.Server)
  591. })
  592. // We can get multiple responses that point to a single Location, since
  593. // we probe for both ssdp:all and InternetGatewayDevice:1 as
  594. // independent packets. Compact by comparing the Location and Server,
  595. // but not the USN (which contains the device being offered).
  596. //
  597. // Since the slices are sorted in reverse above, this means that if we
  598. // get a discovery response for both InternetGatewayDevice:1 and
  599. // InternetGatewayDevice:2, we'll keep the first
  600. // (InternetGatewayDevice:2) response, which is what we want.
  601. metas = slices.CompactFunc(metas, func(a, b uPnPDiscoResponse) bool {
  602. return a.Location == b.Location && a.Server == b.Server
  603. })
  604. return metas
  605. }
  606. // getUPnPErrorCode returns the UPnP error code from the given response, if the
  607. // error is a SOAP error in the proper format, and a boolean indicating whether
  608. // the provided error was actually a UPnP error.
  609. func getUPnPErrorCode(err error) (int, bool) {
  610. soapErr, ok := err.(*soap.SOAPFaultError)
  611. if !ok {
  612. return 0, false
  613. }
  614. var upnpErr struct {
  615. XMLName xml.Name
  616. Code int `xml:"errorCode"`
  617. Description string `xml:"errorDescription"`
  618. }
  619. if err := xml.Unmarshal([]byte(soapErr.Detail.Raw), &upnpErr); err != nil {
  620. return 0, false
  621. }
  622. if upnpErr.XMLName.Local != "UPnPError" {
  623. return 0, false
  624. }
  625. return upnpErr.Code, true
  626. }
  627. type uPnPDiscoResponse struct {
  628. Location string
  629. // Server describes what version the UPnP is, such as MiniUPnPd/2.x.x
  630. Server string
  631. // USN is the serial number of the device, which also contains
  632. // what kind of UPnP service is being offered, i.e. InternetGatewayDevice:2
  633. USN string
  634. }
  635. // parseUPnPDiscoResponse parses a UPnP HTTP-over-UDP discovery response.
  636. func parseUPnPDiscoResponse(body []byte) (uPnPDiscoResponse, error) {
  637. var r uPnPDiscoResponse
  638. res, err := http.ReadResponse(bufio.NewReaderSize(bytes.NewReader(body), 128), nil)
  639. if err != nil {
  640. return r, err
  641. }
  642. r.Location = res.Header.Get("Location")
  643. r.Server = res.Header.Get("Server")
  644. r.USN = res.Header.Get("Usn")
  645. return r, nil
  646. }
  647. type roundTripperFunc func(*http.Request) (*http.Response, error)
  648. func (r roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
  649. return r(req)
  650. }
  651. func requestLogger(logf logger.Logf, client *http.Client) *http.Client {
  652. // Clone the HTTP client, and override the Transport to log to the
  653. // provided logger.
  654. ret := *client
  655. oldTransport := ret.Transport
  656. var requestCounter atomic.Uint64
  657. loggingTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
  658. ctr := requestCounter.Add(1)
  659. // Read the body and re-set it.
  660. var (
  661. body []byte
  662. err error
  663. )
  664. if req.Body != nil {
  665. body, err = io.ReadAll(req.Body)
  666. req.Body.Close()
  667. if err != nil {
  668. return nil, err
  669. }
  670. req.Body = io.NopCloser(bytes.NewReader(body))
  671. }
  672. logf("request[%d]: %s %q body=%q", ctr, req.Method, req.URL, body)
  673. resp, err := oldTransport.RoundTrip(req)
  674. if err != nil {
  675. logf("response[%d]: err=%v", ctr, err)
  676. return nil, err
  677. }
  678. // Read the response body
  679. if resp.Body != nil {
  680. body, err = io.ReadAll(resp.Body)
  681. resp.Body.Close()
  682. if err != nil {
  683. logf("response[%d]: %d bodyErr=%v", ctr, resp.StatusCode, err)
  684. return nil, err
  685. }
  686. resp.Body = io.NopCloser(bytes.NewReader(body))
  687. }
  688. logf("response[%d]: %d body=%q", ctr, resp.StatusCode, body)
  689. return resp, nil
  690. })
  691. ret.Transport = loggingTransport
  692. return &ret
  693. }