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. )
  32. func TestInproxyComponents(t *testing.T) {
  33. // This is a unit test of the in-proxy components internals, such as
  34. // replay; actual in-proxy broker round trips are exercised in the
  35. // psiphon/server end-to-end tests.
  36. err := runInproxyBrokerDialParametersTest()
  37. if err != nil {
  38. t.Fatalf(errors.Trace(err).Error())
  39. }
  40. err = runInproxySTUNDialParametersTest()
  41. if err != nil {
  42. t.Fatalf(errors.Trace(err).Error())
  43. }
  44. err = runInproxyNATStateTest()
  45. if err != nil {
  46. t.Fatalf(errors.Trace(err).Error())
  47. }
  48. // TODO: test inproxyUDPConn multiplexed IPv6Synthesizer
  49. }
  50. func runInproxyBrokerDialParametersTest() error {
  51. testDataDirName, err := ioutil.TempDir("", "psiphon-inproxy-broker-test")
  52. if err != nil {
  53. return errors.Trace(err)
  54. }
  55. defer os.RemoveAll(testDataDirName)
  56. isProxy := false
  57. propagationChannelID := prng.HexString(8)
  58. sponsorID := prng.HexString(8)
  59. networkID := "NETWORK1"
  60. addressRegex := `[a-z0-9]{5,10}\.example\.org`
  61. commonCompartmentID, _ := inproxy.MakeID()
  62. personalCompartmentID, _ := inproxy.MakeID()
  63. commonCompartmentIDs := []string{commonCompartmentID.String()}
  64. personalCompartmentIDs := []string{personalCompartmentID.String()}
  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.FrontingDialAddress)) {
  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. previousFrontingDialAddress := brokerDialParams.FrontingDialAddress
  136. previousTLSProfile := brokerDialParams.TLSProfile
  137. roundTripper, err := brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripper()
  138. if err != nil {
  139. return errors.Trace(err)
  140. }
  141. brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripperSucceeded(roundTripper)
  142. manager = NewInproxyBrokerClientManager(config, isProxy)
  143. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  144. if err != nil {
  145. return errors.Trace(err)
  146. }
  147. if !brokerDialParams.isReplay {
  148. return errors.TraceNew("unexpected non-replay")
  149. }
  150. if brokerDialParams.FrontingDialAddress != previousFrontingDialAddress {
  151. return errors.TraceNew("unexpected replayed FrontingDialAddress")
  152. }
  153. if brokerDialParams.TLSProfile != previousTLSProfile {
  154. return errors.TraceNew("unexpected replayed TLSProfile")
  155. }
  156. _ = brokerDialParams.GetMetrics()
  157. // Test: manager's broker client and dial parameters reinitialized after
  158. // network ID change
  159. previousBrokerClient := brokerClient
  160. previousNetworkID := networkID
  161. networkID = "NETWORK2"
  162. config.networkIDGetter = newStaticNetworkGetter(networkID)
  163. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  164. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  165. if err != nil {
  166. return errors.Trace(err)
  167. }
  168. if brokerClient == previousBrokerClient {
  169. return errors.TraceNew("unexpected brokerClient")
  170. }
  171. if brokerDialParams.isReplay {
  172. return errors.TraceNew("unexpected replay")
  173. }
  174. if brokerDialParams.FrontingDialAddress == previousFrontingDialAddress {
  175. return errors.TraceNew("unexpected non-replayed FrontingDialAddress")
  176. }
  177. _ = brokerDialParams.GetMetrics()
  178. // Test: another replay after switch back to previous network ID
  179. networkID = previousNetworkID
  180. config.networkIDGetter = newStaticNetworkGetter(networkID)
  181. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  182. if err != nil {
  183. return errors.Trace(err)
  184. }
  185. if !brokerDialParams.isReplay {
  186. return errors.TraceNew("unexpected non-replay")
  187. }
  188. if brokerDialParams.FrontingDialAddress != previousFrontingDialAddress {
  189. return errors.TraceNew("unexpected replayed FrontingDialAddress")
  190. }
  191. if brokerDialParams.TLSProfile != previousTLSProfile {
  192. return errors.TraceNew("unexpected replayed TLSProfile")
  193. }
  194. _ = brokerDialParams.GetMetrics()
  195. // Test: clear replay
  196. roundTripper, err = brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripper()
  197. if err != nil {
  198. return errors.Trace(err)
  199. }
  200. brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripperFailed(roundTripper)
  201. manager = NewInproxyBrokerClientManager(config, isProxy)
  202. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  203. if err != nil {
  204. return errors.Trace(err)
  205. }
  206. if brokerDialParams.isReplay {
  207. return errors.TraceNew("unexpected replay")
  208. }
  209. if brokerDialParams.FrontingDialAddress == previousFrontingDialAddress {
  210. return errors.TraceNew("unexpected non-replayed FrontingDialAddress")
  211. }
  212. _ = brokerDialParams.GetMetrics()
  213. // Test: no common compartment IDs sent when personal ID is set
  214. config.InproxyClientPersonalCompartmentIDs = personalCompartmentIDs
  215. config.InproxyProxyPersonalCompartmentIDs = personalCompartmentIDs
  216. manager = NewInproxyBrokerClientManager(config, isProxy)
  217. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  218. if err != nil {
  219. return errors.Trace(err)
  220. }
  221. if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 0 ||
  222. len(brokerClient.GetBrokerDialCoordinator().PersonalCompartmentIDs()) != 1 ||
  223. brokerClient.GetBrokerDialCoordinator().PersonalCompartmentIDs()[0].String() !=
  224. personalCompartmentID.String() {
  225. return errors.TraceNew("unexpected compartment IDs")
  226. }
  227. // Test: use persisted common compartment IDs
  228. config = &Config{
  229. PropagationChannelId: propagationChannelID,
  230. SponsorId: sponsorID,
  231. NetworkID: networkID,
  232. }
  233. config.InproxyBrokerSpecs = brokerSpecs
  234. config.InproxyCommonCompartmentIDs = nil
  235. err = config.Commit(false)
  236. if err != nil {
  237. return errors.Trace(err)
  238. }
  239. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  240. manager = NewInproxyBrokerClientManager(config, isProxy)
  241. brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
  242. if err != nil {
  243. return errors.Trace(err)
  244. }
  245. if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 1 ||
  246. brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()[0].String() !=
  247. commonCompartmentID.String() {
  248. return errors.TraceNew("unexpected compartment IDs")
  249. }
  250. _ = brokerDialParams.GetMetrics()
  251. return nil
  252. }
  253. func runInproxySTUNDialParametersTest() error {
  254. testDataDirName, err := ioutil.TempDir("", "psiphon-inproxy-stun-test")
  255. if err != nil {
  256. return errors.Trace(err)
  257. }
  258. defer os.RemoveAll(testDataDirName)
  259. propagationChannelID := prng.HexString(8)
  260. sponsorID := prng.HexString(8)
  261. networkID := "NETWORK1"
  262. stunServerAddresses := []string{"example.org"}
  263. config := &Config{
  264. DataRootDirectory: testDataDirName,
  265. PropagationChannelId: propagationChannelID,
  266. SponsorId: sponsorID,
  267. NetworkID: networkID,
  268. InproxySTUNServerAddresses: stunServerAddresses,
  269. InproxySTUNServerAddressesRFC5780: stunServerAddresses,
  270. }
  271. err = config.Commit(false)
  272. if err != nil {
  273. return errors.Trace(err)
  274. }
  275. config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
  276. p := config.GetParameters().Get()
  277. defer p.Close()
  278. dialParams, err := MakeInproxySTUNDialParameters(config, p, false)
  279. if err != nil {
  280. return errors.Trace(err)
  281. }
  282. _ = dialParams.GetMetrics()
  283. dialParamsJSON, err := json.Marshal(dialParams)
  284. if err != nil {
  285. return errors.Trace(err)
  286. }
  287. var replayDialParams *InproxySTUNDialParameters
  288. err = json.Unmarshal(dialParamsJSON, &replayDialParams)
  289. if err != nil {
  290. return errors.Trace(err)
  291. }
  292. replayDialParams.Prepare()
  293. _ = replayDialParams.GetMetrics()
  294. return nil
  295. }
  296. func runInproxyNATStateTest() error {
  297. propagationChannelID := prng.HexString(8)
  298. sponsorID := prng.HexString(8)
  299. networkID := "NETWORK1"
  300. config := &Config{
  301. PropagationChannelId: propagationChannelID,
  302. SponsorId: sponsorID,
  303. NetworkID: networkID,
  304. }
  305. err := config.Commit(false)
  306. if err != nil {
  307. return errors.Trace(err)
  308. }
  309. manager := NewInproxyNATStateManager(config)
  310. // Test: set values stored and cached
  311. manager.setNATType(networkID, inproxy.NATTypeSymmetric)
  312. manager.setPortMappingTypes(networkID, inproxy.PortMappingTypes{inproxy.PortMappingTypeUPnP})
  313. if manager.getNATType(networkID) != inproxy.NATTypeSymmetric {
  314. return errors.TraceNew("unexpected NAT type")
  315. }
  316. portMappingTypes := manager.getPortMappingTypes(networkID)
  317. if len(portMappingTypes) != 1 || portMappingTypes[0] != inproxy.PortMappingTypeUPnP {
  318. return errors.TraceNew("unexpected port mapping types")
  319. }
  320. // Test: set values ignored when network ID is changing
  321. otherNetworkID := "NETWORK2"
  322. manager.setNATType(otherNetworkID, inproxy.NATTypePortRestrictedCone)
  323. manager.setPortMappingTypes(otherNetworkID, inproxy.PortMappingTypes{inproxy.PortMappingTypePMP})
  324. if manager.getNATType(networkID) != inproxy.NATTypeSymmetric {
  325. return errors.TraceNew("unexpected NAT type")
  326. }
  327. portMappingTypes = manager.getPortMappingTypes(networkID)
  328. if len(portMappingTypes) != 1 || portMappingTypes[0] != inproxy.PortMappingTypeUPnP {
  329. return errors.TraceNew("unexpected port mapping types")
  330. }
  331. // Test: reset
  332. networkID = "NETWORK2"
  333. config.networkIDGetter = newStaticNetworkGetter(networkID)
  334. manager.reset()
  335. if manager.networkID != networkID {
  336. return errors.TraceNew("unexpected network ID")
  337. }
  338. if manager.getNATType(networkID) != inproxy.NATTypeUnknown {
  339. return errors.TraceNew("unexpected NAT type")
  340. }
  341. if len(manager.getPortMappingTypes(networkID)) != 0 {
  342. return errors.TraceNew("unexpected port mapping types")
  343. }
  344. return nil
  345. }