inproxy_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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. &parameters.InproxyBrokerSpec{
  70. BrokerPublicKey: publicKey.String(),
  71. BrokerRootObfuscationSecret: obfuscationSecret.String(),
  72. BrokerFrontingSpecs: []*parameters.FrontingSpec{
  73. &parameters.FrontingSpec{
  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.InproxyPersonalCompartmentIDs = personalCompartmentIDs
  215. manager = NewInproxyBrokerClientManager(config, isProxy)
  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)
  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 = newStaticNetworkGetter(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. }