portmapper.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. //go:build PSIPHON_ENABLE_INPROXY
  2. /*
  3. * Copyright (c) 2023, Psiphon Inc.
  4. * All rights reserved.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. package inproxy
  21. import (
  22. "context"
  23. "fmt"
  24. "reflect"
  25. "runtime/debug"
  26. "strings"
  27. "sync"
  28. "unsafe"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  31. "tailscale.com/net/portmapper"
  32. "tailscale.com/util/clientmetric"
  33. )
  34. // initPortMapper resets port mapping metrics state associated with the
  35. // current network when the network changes, as indicated by
  36. // WebRTCDialCoordinator.NetworkID. initPortMapper also configures the port
  37. // mapping routines to use WebRTCDialCoordinator.BindToDevice. Varying
  38. // WebRTCDialCoordinator.BindToDevice between dials in a single process is not
  39. // supported.
  40. func initPortMapper(coordinator WebRTCDialCoordinator) {
  41. // It's safe for multiple, concurrent client dials to call
  42. // resetRespondingPortMappingTypes: as long as the network ID does not
  43. // change, calls won't clear any valid port mapping type metrics that
  44. // were just recorded.
  45. resetRespondingPortMappingTypes(coordinator.NetworkID())
  46. // WebRTCDialCoordinator.BindToDevice is set as a global variable in
  47. // tailscale.com/net/portmapper. It's safe to repeatedly call
  48. // setPortMapperBindToDevice here, under the assumption that
  49. // WebRTCDialCoordinator.BindToDevice is the same single, static function
  50. // for all dials. This assumption is true for Psiphon.
  51. setPortMapperBindToDevice(coordinator)
  52. }
  53. // portMapper represents a UDP port mapping from a local port to an external,
  54. // publicly addressable IP and port. Port mapping is implemented using
  55. // tailscale.com/net/portmapper, which probes the local network and gateway
  56. // for UPnP-IGD, NAT-PMP, and PCP port mapping capabilities.
  57. type portMapper struct {
  58. havePortMappingOnce sync.Once
  59. portMappingAddress chan string
  60. client *portmapper.Client
  61. portMappingLogger func(format string, args ...any)
  62. }
  63. // newPortMapper initializes a new port mapper, configured to map to the
  64. // specified localPort. newPortMapper does not initiate any network
  65. // operations.
  66. //
  67. // newPortMapper requires a PortMappingProbe initialized by probePortMapping,
  68. // as the underlying portmapper.Client.GetCachedMappingOrStartCreatingOne
  69. // requires data populated by Client.Probe, such as UPnP service
  70. // information.
  71. //
  72. // Rather that run a full Client.Probe per port mapping, the service data from
  73. // one probe run is reused.
  74. func newPortMapper(
  75. logger common.Logger,
  76. probe *PortMappingProbe,
  77. localPort int) (*portMapper, error) {
  78. if probe == nil {
  79. return nil, errors.TraceNew("missing probe")
  80. }
  81. portMappingLogger := func(format string, args ...any) {
  82. logger.WithTrace().Info(
  83. "port mapping: " + formatPortMappingLog(format, args...))
  84. }
  85. p := &portMapper{
  86. portMappingAddress: make(chan string, 1),
  87. portMappingLogger: portMappingLogger,
  88. }
  89. // This code assumes assumes tailscale NewClient call does only
  90. // initialization; this is the case as of tailscale.com/net/portmapper
  91. // v1.36.2.
  92. //
  93. // This code further assumes that the onChanged callback passed to
  94. // NewClient will not be invoked until after the
  95. // GetCachedMappingOrStartCreatingOne call in portMapper.start; and so
  96. // the p.client reference within callback will be valid.
  97. client := portmapper.NewClient(portMappingLogger, nil, nil, nil, func() {
  98. if !p.client.HaveMapping() {
  99. return
  100. }
  101. p.havePortMappingOnce.Do(func() {
  102. address, ok := p.client.GetCachedMappingOrStartCreatingOne()
  103. if ok {
  104. // With sync.Once and a buffer size of 1, this send won't block.
  105. p.portMappingAddress <- address.String()
  106. portMappingLogger("address obtained")
  107. } else {
  108. // This is not an expected case; there should be a port
  109. // mapping when NewClient is invoked.
  110. //
  111. // TODO: deliver "" to the channel? Otherwise, receiving on
  112. // portMapper.portMappingExternalAddress will hang, or block
  113. // until a context is done.
  114. portMappingLogger("unexpected missing port mapping")
  115. }
  116. })
  117. })
  118. p.client = client
  119. p.client.SetLocalPort(uint16(localPort))
  120. // Copy the port mapping service data from the input probe.
  121. err := p.cloneProbe(probe)
  122. if err != nil {
  123. return nil, errors.Trace(err)
  124. }
  125. return p, nil
  126. }
  127. func init() {
  128. expectedDependencyVersion := "v1.58.2"
  129. buildInfo, ok := debug.ReadBuildInfo()
  130. // In GOPATH builds, BuildInfo is not available; in `go test` runs,
  131. // BuildInfo dependency information is not available. In these case, we
  132. // skip the check and assume that contemporaneous go module build runs
  133. // will catch a check failure.
  134. if !ok ||
  135. buildInfo.Main.Path == "" ||
  136. buildInfo.Main.Path == "command-line-arguments" ||
  137. strings.HasSuffix(buildInfo.Path, ".test") {
  138. return
  139. }
  140. for _, dep := range buildInfo.Deps {
  141. if dep.Path == "tailscale.com" && dep.Version == expectedDependencyVersion {
  142. return
  143. }
  144. }
  145. panic("portmapper dependency version check failed")
  146. }
  147. // cloneProbe copies the port mapping service data gather by Client.Probe from
  148. // the input probe client.
  149. func (p *portMapper) cloneProbe(probe *PortMappingProbe) error {
  150. // The required portmapper.Client fields are not exported by
  151. // tailscale/net/portmapper, so unsafe reflection is used to copy the
  152. // values. A simple portmapper.Client struct copy can't be performed as
  153. // the struct contains a sync.Mutex field.
  154. //
  155. // The following is assumed, based on the pinned dependency version:
  156. //
  157. // - portmapper.Client.Probe is synchronous, so once probe.client.Probe is
  158. // complete, it's safe to read its fields
  159. //
  160. // - portmapping.Probe does not create a cached mapping.
  161. //
  162. // - Only Probe populates the copied fields and
  163. // portmapper.Client.GetCachedMappingOrStartCreatingOne merely reads
  164. // them (or clears them, in invalidateMappingsLocked)
  165. //
  166. // We further assume that the caller synchronizes access to the input
  167. // probe, so the probe is idle when cloned
  168. // (see Proxy.networkDiscoveryMutex).
  169. //
  170. // An explicit dependency version pin check is made above, since potential
  171. // logic changes in future versions of the dependency may break the above
  172. // assumptions while the reflect operation might still succeed.
  173. //
  174. // TODO: fork the dependency to add internal support for shared probe
  175. // state, trim additional tailscale dependencies, use Psiphon's custom
  176. // dialer, remove globals (see clientmetric.Metrics below), and remove
  177. // the dependency version check.
  178. src := reflect.ValueOf(probe.client).Elem()
  179. dst := reflect.ValueOf(p.client).Elem()
  180. shallowCloneField := func(name string) error {
  181. srcField := src.FieldByName(name)
  182. dstField := dst.FieldByName(name)
  183. // Bypass "reflect: reflect.Value.Set using value obtained using
  184. // unexported field" restriction.
  185. srcField = reflect.NewAt(
  186. srcField.Type(), unsafe.Pointer(srcField.UnsafeAddr())).Elem()
  187. dstField = reflect.NewAt(
  188. dstField.Type(), unsafe.Pointer(dstField.UnsafeAddr())).Elem()
  189. if !srcField.CanSet() || !dstField.CanSet() {
  190. return errors.Tracef("%s: cannot set field", name)
  191. }
  192. dstField.Set(srcField)
  193. return nil
  194. }
  195. // As of the pinned dependency version,
  196. // portmapper.invalidateMappingsLocked sets uPnPMetas to nil, but doesn't
  197. // write to the original slice elements, so a shallow copy is sufficient.
  198. for _, fieldName := range []string{
  199. "lastMyIP",
  200. "lastGW",
  201. "lastProbe",
  202. "pmpPubIP",
  203. "pmpPubIPTime",
  204. "pmpLastEpoch",
  205. "pcpSawTime",
  206. "pcpLastEpoch",
  207. "uPnPSawTime",
  208. "uPnPMetas",
  209. } {
  210. err := shallowCloneField(fieldName)
  211. if err != nil {
  212. return errors.Trace(err)
  213. }
  214. }
  215. return nil
  216. }
  217. // start initiates the port mapping attempt.
  218. func (p *portMapper) start() {
  219. p.portMappingLogger("started")
  220. // There is no cached mapping at this point.
  221. _, _ = p.client.GetCachedMappingOrStartCreatingOne()
  222. }
  223. // portMappingExternalAddress returns a channel which receives a successful
  224. // port mapping external address, if any.
  225. func (p *portMapper) portMappingExternalAddress() <-chan string {
  226. return p.portMappingAddress
  227. }
  228. // close releases the port mapping
  229. func (p *portMapper) close() error {
  230. // TODO: it's not clear whether a concurrent portmapper.Client.createOrGetMapping,
  231. // in progress at the time of the portmapper.Client call, will dispose of
  232. // any created mapping if it completes after Close.
  233. err := p.client.Close()
  234. p.portMappingLogger("closed")
  235. return errors.Trace(err)
  236. }
  237. func formatPortMappingLog(format string, args ...any) string {
  238. truncatePrefix := "[v1] UPnP reply"
  239. if strings.HasPrefix(format, truncatePrefix) {
  240. // Omit packet portion of this log, but still log the event
  241. return truncatePrefix
  242. }
  243. return fmt.Sprintf(format, args...)
  244. }
  245. // PortMappingProbe records information about the port mapping services found
  246. // in a port mapping service probe.
  247. type PortMappingProbe struct {
  248. client *portmapper.Client
  249. }
  250. // probePortMapping discovers and reports which port mapping protocols are
  251. // supported on this network. probePortMapping does not establish a port
  252. // mapping. probePortMapping caches a PortMappingProbe for use in subsequent
  253. // port mapping establishment.
  254. //
  255. // It is intended that in-proxy proxies make a blocking call to
  256. // probePortMapping on start up (and after a network change) in order to
  257. // report fresh port mapping type metrics, for matching optimization in the
  258. // ProxyAnnounce request.
  259. func probePortMapping(
  260. ctx context.Context,
  261. logger common.Logger) (PortMappingTypes, *PortMappingProbe, error) {
  262. portMappingLogger := func(format string, args ...any) {
  263. logger.WithTrace().Info(
  264. "port mapping probe: " + formatPortMappingLog(format, args...))
  265. }
  266. client := portmapper.NewClient(portMappingLogger, nil, nil, nil, nil)
  267. // ErrGatewayRange is "skipping portmap; gateway range likely lacks
  268. // support". The probe did not fail, and the result fields will all be
  269. // false. Drop through and report PortMappingTypeNone in this case.
  270. // Currently, this is the only special case; and Probe doesn't wrap this
  271. // error with the type NoMappingError.
  272. result, err := client.Probe(ctx)
  273. if err != nil && err != portmapper.ErrGatewayRange {
  274. return nil, nil, errors.Trace(err)
  275. }
  276. portMappingTypes := PortMappingTypes{}
  277. if result.UPnP {
  278. portMappingTypes = append(portMappingTypes, PortMappingTypeUPnP)
  279. }
  280. if result.PMP {
  281. portMappingTypes = append(portMappingTypes, PortMappingTypePMP)
  282. }
  283. if result.PCP {
  284. portMappingTypes = append(portMappingTypes, PortMappingTypePCP)
  285. }
  286. var probe *PortMappingProbe
  287. if len(portMappingTypes) == 0 {
  288. // An empty lists means discovery is needed or the available port mappings
  289. // are unknown; a list with None indicates that a probe returned no
  290. // supported port mapping types.
  291. portMappingTypes = append(portMappingTypes, PortMappingTypeNone)
  292. } else {
  293. // Return a probe for use in subsequent port mappings only when
  294. // services were found.
  295. //
  296. // It is not necessary to call PortMappingProbe.client.Close, as it is
  297. // not holding open any actual mappings.
  298. probe = &PortMappingProbe{
  299. client: client,
  300. }
  301. }
  302. return portMappingTypes, probe, nil
  303. }
  304. var respondingPortMappingTypesMutex sync.Mutex
  305. var respondingPortMappingTypesNetworkID string
  306. // resetRespondingPortMappingTypes clears tailscale.com/net/portmapper global
  307. // metrics fields which indicate which port mapping types are responding on
  308. // the current network. These metrics should be cleared whenever the current
  309. // network changes, as indicated by networkID.
  310. //
  311. // Limitations: there may be edge conditions where a
  312. // tailscale.com/net/portmapper client logs metrics concurrent to
  313. // resetRespondingPortMappingTypes being called with a new networkID. If
  314. // incorrect port mapping type metrics are supported, the Broker may log
  315. // incorrect statistics. However, Broker client/in-proxy matching is based on
  316. // actually established port mappings.
  317. func resetRespondingPortMappingTypes(networkID string) {
  318. respondingPortMappingTypesMutex.Lock()
  319. defer respondingPortMappingTypesMutex.Unlock()
  320. if respondingPortMappingTypesNetworkID != networkID {
  321. // Iterating over all metric fields appears to be the only API available.
  322. for _, metric := range clientmetric.Metrics() {
  323. switch metric.Name() {
  324. case "portmap_upnp_ok", "portmap_pmp_ok", "portmap_pcp_ok":
  325. metric.Set(0)
  326. }
  327. }
  328. respondingPortMappingTypesNetworkID = networkID
  329. }
  330. }
  331. // getRespondingPortMappingTypes returns the port mapping types that responded
  332. // during recent portMapper.start invocations as well as probePortMapping
  333. // invocations. The returned list is used for reporting metrics. See
  334. // resetRespondingPortMappingTypes for considerations due to accessing
  335. // tailscale.com/net/portmapper global metrics fields.
  336. //
  337. // To avoid delays, we do not run probePortMapping for regular client dials,
  338. // and so instead use this tailscale.com/net/portmapper metrics field
  339. // approach.
  340. //
  341. // Limitations: the return value represents all port mapping types that
  342. // responded in this session, since the last network change
  343. // (resetRespondingPortMappingTypes call); and do not indicate which of
  344. // several port mapping types may have been used for a particular dial.
  345. func getRespondingPortMappingTypes(networkID string) PortMappingTypes {
  346. respondingPortMappingTypesMutex.Lock()
  347. defer respondingPortMappingTypesMutex.Unlock()
  348. portMappingTypes := PortMappingTypes{}
  349. if respondingPortMappingTypesNetworkID != networkID {
  350. // The network changed since the last resetRespondingPortMappingTypes
  351. // call, and resetRespondingPortMappingTypes has not yet been called
  352. // again. Ignore the current metrics.
  353. return portMappingTypes
  354. }
  355. // Iterating over all metric fields appears to be the only API available.
  356. for _, metric := range clientmetric.Metrics() {
  357. if metric.Name() == "portmap_upnp_ok" && metric.Value() > 1 {
  358. portMappingTypes = append(portMappingTypes, PortMappingTypeUPnP)
  359. }
  360. if metric.Name() == "portmap_pmp_ok" && metric.Value() > 1 {
  361. portMappingTypes = append(portMappingTypes, PortMappingTypePMP)
  362. }
  363. if metric.Name() == "portmap_pcp_ok" && metric.Value() > 1 {
  364. portMappingTypes = append(portMappingTypes, PortMappingTypePCP)
  365. }
  366. }
  367. return portMappingTypes
  368. }