inproxy_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. /*
  2. * Copyright (c) 2023, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package psiphon
  20. import (
  21. "encoding/json"
  22. "io/ioutil"
  23. "os"
  24. "regexp"
  25. "testing"
  26. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/inproxy"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
  31. utls "github.com/Psiphon-Labs/utls"
  32. "github.com/stretchr/testify/assert"
  33. )
  34. func TestInproxyComponents(t *testing.T) {
  35. // This is a unit test of the in-proxy components internals, such as
  36. // replay; actual in-proxy broker round trips are exercised in the
  37. // psiphon/server end-to-end tests.
  38. err := runInproxyBrokerDialParametersTest(t)
  39. if err != nil {
  40. t.Fatal(errors.Trace(err).Error())
  41. }
  42. err = runInproxySTUNDialParametersTest()
  43. if err != nil {
  44. t.Fatal(errors.Trace(err).Error())
  45. }
  46. err = runInproxyNATStateTest()
  47. if err != nil {
  48. t.Fatal(errors.Trace(err).Error())
  49. }
  50. // TODO: test inproxyUDPConn multiplexed IPv6Synthesizer
  51. }
  52. func runInproxyBrokerDialParametersTest(t *testing.T) error {
  53. testDataDirName, err := ioutil.TempDir("", "psiphon-inproxy-broker-test")
  54. if err != nil {
  55. return errors.Trace(err)
  56. }
  57. defer os.RemoveAll(testDataDirName)
  58. isProxy := false
  59. propagationChannelID := prng.HexString(8)
  60. sponsorID := prng.HexString(8)
  61. networkID := "NETWORK1"
  62. addressRegex := `[a-z0-9]{5,10}\.example\.org`
  63. commonCompartmentID, _ := inproxy.MakeID()
  64. commonCompartmentIDs := []string{commonCompartmentID.String()}
  65. personalCompartmentID, _ := inproxy.MakeID()
  66. privateKey, _ := inproxy.GenerateSessionPrivateKey()
  67. publicKey, _ := privateKey.GetPublicKey()
  68. obfuscationSecret, _ := inproxy.GenerateRootObfuscationSecret()
  69. brokerSpecs := []*parameters.InproxyBrokerSpec{
  70. {
  71. BrokerPublicKey: publicKey.String(),
  72. BrokerRootObfuscationSecret: obfuscationSecret.String(),
  73. BrokerFrontingSpecs: []*parameters.FrontingSpec{
  74. {
  75. FrontingProviderID: prng.HexString(8),
  76. Addresses: []string{addressRegex},
  77. VerifyServerName: "example.org",
  78. Host: "example.org",
  79. },
  80. },
  81. },
  82. }
  83. retainFailed := float64(0.0)
  84. config := &Config{
  85. DataRootDirectory: testDataDirName,
  86. PropagationChannelId: propagationChannelID,
  87. SponsorId: sponsorID,
  88. NetworkID: networkID,
  89. }
  90. err = config.Commit(false)
  91. if err != nil {
  92. return errors.Trace(err)
  93. }
  94. err = OpenDataStore(config)
  95. if err != nil {
  96. return errors.Trace(err)
  97. }
  98. defer CloseDataStore()
  99. tlsCache := utls.NewLRUClientSessionCache(0)
  100. manager := NewInproxyBrokerClientManager(config, isProxy, tlsCache)
  101. // Test: no broker specs
  102. _, _, err = manager.GetBrokerClient(networkID)
  103. if err == nil {
  104. return errors.TraceNew("unexpected success")
  105. }
  106. // Test: select broker and common compartment IDs
  107. config = &Config{
  108. DataRootDirectory: testDataDirName,
  109. PropagationChannelId: propagationChannelID,
  110. SponsorId: sponsorID,
  111. NetworkID: networkID,
  112. InproxyBrokerSpecs: brokerSpecs,
  113. InproxyCommonCompartmentIDs: commonCompartmentIDs,
  114. InproxyReplayBrokerRetainFailedProbability: &retainFailed,
  115. }
  116. err = config.Commit(false)
  117. if err != nil {
  118. return errors.Trace(err)
  119. }
  120. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  121. manager = NewInproxyBrokerClientManager(config, isProxy, tlsCache)
  122. brokerClient, brokerDialParams, err := manager.GetBrokerClient(networkID)
  123. if err != nil {
  124. return errors.Trace(err)
  125. }
  126. if !regexp.MustCompile(addressRegex).Copy().Match(
  127. []byte(brokerDialParams.FrontedHTTPDialParameters.meekConfig.DialAddress)) {
  128. return errors.TraceNew("unexpected FrontingDialAddress")
  129. }
  130. if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 1 ||
  131. brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()[0].String() !=
  132. commonCompartmentID.String() {
  133. return errors.TraceNew("unexpected compartment IDs")
  134. }
  135. _ = brokerDialParams.GetMetrics()
  136. // Test: replay on success
  137. prevBrokerDialParams := brokerDialParams
  138. previousFrontingDialAddress := brokerDialParams.FrontedHTTPDialParameters.meekConfig.DialAddress
  139. previousTLSProfile := brokerDialParams.FrontedHTTPDialParameters.meekConfig.TLSProfile
  140. roundTripper, err := brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripper()
  141. if err != nil {
  142. return errors.Trace(err)
  143. }
  144. brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripperSucceeded(roundTripper)
  145. manager = NewInproxyBrokerClientManager(config, isProxy, tlsCache)
  146. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  147. if err != nil {
  148. return errors.Trace(err)
  149. }
  150. if !brokerDialParams.isReplay {
  151. return errors.TraceNew("unexpected non-replay")
  152. }
  153. // All exported fields should be replayed
  154. assert.EqualExportedValues(t, brokerDialParams, prevBrokerDialParams)
  155. _ = brokerDialParams.GetMetrics()
  156. // Test: manager's broker client and dial parameters reinitialized after
  157. // network ID change
  158. previousBrokerClient := brokerClient
  159. previousNetworkID := networkID
  160. networkID = "NETWORK2"
  161. config.networkIDGetter = newCachingNetworkIDGetter(config, newStaticNetworkIDGetter(networkID))
  162. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  163. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  164. if err != nil {
  165. return errors.Trace(err)
  166. }
  167. if brokerClient == previousBrokerClient {
  168. return errors.TraceNew("unexpected brokerClient")
  169. }
  170. if brokerDialParams.isReplay {
  171. return errors.TraceNew("unexpected replay")
  172. }
  173. if brokerDialParams.FrontedHTTPDialParameters.meekConfig.DialAddress == previousFrontingDialAddress {
  174. return errors.TraceNew("unexpected non-replayed FrontingDialAddress")
  175. }
  176. _ = brokerDialParams.GetMetrics()
  177. // Test: another replay after switch back to previous network ID
  178. networkID = previousNetworkID
  179. config.networkIDGetter = newCachingNetworkIDGetter(config, newStaticNetworkIDGetter(networkID))
  180. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  181. if err != nil {
  182. return errors.Trace(err)
  183. }
  184. if !brokerDialParams.isReplay {
  185. return errors.TraceNew("unexpected non-replay")
  186. }
  187. if brokerDialParams.FrontedHTTPDialParameters.meekConfig.DialAddress != previousFrontingDialAddress {
  188. return errors.TraceNew("unexpected replayed FrontingDialAddress")
  189. }
  190. if brokerDialParams.FrontedHTTPDialParameters.meekConfig.TLSProfile != previousTLSProfile {
  191. return errors.TraceNew("unexpected replayed TLSProfile")
  192. }
  193. _ = brokerDialParams.GetMetrics()
  194. // Test: clear replay
  195. roundTripper, err = brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripper()
  196. if err != nil {
  197. return errors.Trace(err)
  198. }
  199. brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripperFailed(roundTripper)
  200. manager = NewInproxyBrokerClientManager(config, isProxy, tlsCache)
  201. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  202. if err != nil {
  203. return errors.Trace(err)
  204. }
  205. if brokerDialParams.isReplay {
  206. return errors.TraceNew("unexpected replay")
  207. }
  208. if brokerDialParams.FrontedHTTPDialParameters.meekConfig.DialAddress == previousFrontingDialAddress {
  209. return errors.TraceNew("unexpected non-replayed FrontingDialAddress")
  210. }
  211. _ = brokerDialParams.GetMetrics()
  212. // Test: no common compartment IDs sent when personal ID is set
  213. config.InproxyClientPersonalCompartmentID = personalCompartmentID.String()
  214. config.InproxyProxyPersonalCompartmentID = personalCompartmentID.String()
  215. manager = NewInproxyBrokerClientManager(config, isProxy, tlsCache)
  216. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  217. if err != nil {
  218. return errors.Trace(err)
  219. }
  220. if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 0 ||
  221. len(brokerClient.GetBrokerDialCoordinator().PersonalCompartmentIDs()) != 1 ||
  222. brokerClient.GetBrokerDialCoordinator().PersonalCompartmentIDs()[0].String() !=
  223. personalCompartmentID.String() {
  224. return errors.TraceNew("unexpected compartment IDs")
  225. }
  226. // Test: use persisted common compartment IDs
  227. config = &Config{
  228. PropagationChannelId: propagationChannelID,
  229. SponsorId: sponsorID,
  230. NetworkID: networkID,
  231. }
  232. config.InproxyBrokerSpecs = brokerSpecs
  233. config.InproxyCommonCompartmentIDs = nil
  234. err = config.Commit(false)
  235. if err != nil {
  236. return errors.Trace(err)
  237. }
  238. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  239. manager = NewInproxyBrokerClientManager(config, isProxy, tlsCache)
  240. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  241. if err != nil {
  242. return errors.Trace(err)
  243. }
  244. if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 1 ||
  245. brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()[0].String() !=
  246. commonCompartmentID.String() {
  247. return errors.TraceNew("unexpected compartment IDs")
  248. }
  249. _ = brokerDialParams.GetMetrics()
  250. return nil
  251. }
  252. func runInproxySTUNDialParametersTest() error {
  253. testDataDirName, err := ioutil.TempDir("", "psiphon-inproxy-stun-test")
  254. if err != nil {
  255. return errors.Trace(err)
  256. }
  257. defer os.RemoveAll(testDataDirName)
  258. propagationChannelID := prng.HexString(8)
  259. sponsorID := prng.HexString(8)
  260. networkID := "NETWORK1"
  261. stunServerAddresses := []string{"example.org"}
  262. config := &Config{
  263. DataRootDirectory: testDataDirName,
  264. PropagationChannelId: propagationChannelID,
  265. SponsorId: sponsorID,
  266. NetworkID: networkID,
  267. InproxySTUNServerAddresses: stunServerAddresses,
  268. InproxySTUNServerAddressesRFC5780: stunServerAddresses,
  269. }
  270. err = config.Commit(false)
  271. if err != nil {
  272. return errors.Trace(err)
  273. }
  274. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  275. p := config.GetParameters().Get()
  276. defer p.Close()
  277. dialParams, err := MakeInproxySTUNDialParameters(config, p, false)
  278. if err != nil {
  279. return errors.Trace(err)
  280. }
  281. _ = dialParams.GetMetrics()
  282. dialParamsJSON, err := json.Marshal(dialParams)
  283. if err != nil {
  284. return errors.Trace(err)
  285. }
  286. var replayDialParams *InproxySTUNDialParameters
  287. err = json.Unmarshal(dialParamsJSON, &replayDialParams)
  288. if err != nil {
  289. return errors.Trace(err)
  290. }
  291. replayDialParams.Prepare()
  292. _ = replayDialParams.GetMetrics()
  293. return nil
  294. }
  295. func runInproxyNATStateTest() error {
  296. propagationChannelID := prng.HexString(8)
  297. sponsorID := prng.HexString(8)
  298. networkID := "NETWORK1"
  299. config := &Config{
  300. PropagationChannelId: propagationChannelID,
  301. SponsorId: sponsorID,
  302. NetworkID: networkID,
  303. }
  304. err := config.Commit(false)
  305. if err != nil {
  306. return errors.Trace(err)
  307. }
  308. manager := NewInproxyNATStateManager(config)
  309. // Test: set values stored and cached
  310. manager.setNATType(networkID, inproxy.NATTypeSymmetric)
  311. manager.setPortMappingTypes(networkID, inproxy.PortMappingTypes{inproxy.PortMappingTypeUPnP})
  312. if manager.getNATType(networkID) != inproxy.NATTypeSymmetric {
  313. return errors.TraceNew("unexpected NAT type")
  314. }
  315. portMappingTypes := manager.getPortMappingTypes(networkID)
  316. if len(portMappingTypes) != 1 || portMappingTypes[0] != inproxy.PortMappingTypeUPnP {
  317. return errors.TraceNew("unexpected port mapping types")
  318. }
  319. // Test: set values ignored when network ID is changing
  320. otherNetworkID := "NETWORK2"
  321. manager.setNATType(otherNetworkID, inproxy.NATTypePortRestrictedCone)
  322. manager.setPortMappingTypes(otherNetworkID, inproxy.PortMappingTypes{inproxy.PortMappingTypePMP})
  323. if manager.getNATType(networkID) != inproxy.NATTypeSymmetric {
  324. return errors.TraceNew("unexpected NAT type")
  325. }
  326. portMappingTypes = manager.getPortMappingTypes(networkID)
  327. if len(portMappingTypes) != 1 || portMappingTypes[0] != inproxy.PortMappingTypeUPnP {
  328. return errors.TraceNew("unexpected port mapping types")
  329. }
  330. // Test: reset
  331. networkID = "NETWORK2"
  332. config.networkIDGetter = newCachingNetworkIDGetter(config, newStaticNetworkIDGetter(networkID))
  333. manager.reset()
  334. if manager.networkID != networkID {
  335. return errors.TraceNew("unexpected network ID")
  336. }
  337. if manager.getNATType(networkID) != inproxy.NATTypeUnknown {
  338. return errors.TraceNew("unexpected NAT type")
  339. }
  340. if len(manager.getPortMappingTypes(networkID)) != 0 {
  341. return errors.TraceNew("unexpected port mapping types")
  342. }
  343. return nil
  344. }