inproxy_test.go 13 KB

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