| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- /*
- * Copyright (c) 2023, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package psiphon
- import (
- "encoding/json"
- "io/ioutil"
- "os"
- "regexp"
- "testing"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/inproxy"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
- )
- func TestInproxyComponents(t *testing.T) {
- // This is a unit test of the in-proxy components internals, such as
- // replay; actual in-proxy broker round trips are exercised in the
- // psiphon/server end-to-end tests.
- err := runInproxyBrokerDialParametersTest()
- if err != nil {
- t.Fatalf(errors.Trace(err).Error())
- }
- err = runInproxySTUNDialParametersTest()
- if err != nil {
- t.Fatalf(errors.Trace(err).Error())
- }
- err = runInproxyNATStateTest()
- if err != nil {
- t.Fatalf(errors.Trace(err).Error())
- }
- // TODO: test inproxyUDPConn multiplexed IPv6Synthesizer
- }
- func runInproxyBrokerDialParametersTest() error {
- testDataDirName, err := ioutil.TempDir("", "psiphon-inproxy-broker-test")
- if err != nil {
- return errors.Trace(err)
- }
- defer os.RemoveAll(testDataDirName)
- isProxy := false
- propagationChannelID := prng.HexString(8)
- sponsorID := prng.HexString(8)
- networkID := "NETWORK1"
- addressRegex := `[a-z0-9]{5,10}\.example\.org`
- commonCompartmentID, _ := inproxy.MakeID()
- personalCompartmentID, _ := inproxy.MakeID()
- commonCompartmentIDs := []string{commonCompartmentID.String()}
- personalCompartmentIDs := []string{personalCompartmentID.String()}
- privateKey, _ := inproxy.GenerateSessionPrivateKey()
- publicKey, _ := privateKey.GetPublicKey()
- obfuscationSecret, _ := inproxy.GenerateRootObfuscationSecret()
- brokerSpecs := []*parameters.InproxyBrokerSpec{
- {
- BrokerPublicKey: publicKey.String(),
- BrokerRootObfuscationSecret: obfuscationSecret.String(),
- BrokerFrontingSpecs: []*parameters.FrontingSpec{
- {
- FrontingProviderID: prng.HexString(8),
- Addresses: []string{addressRegex},
- VerifyServerName: "example.org",
- Host: "example.org",
- },
- },
- },
- }
- retainFailed := float64(0.0)
- config := &Config{
- DataRootDirectory: testDataDirName,
- PropagationChannelId: propagationChannelID,
- SponsorId: sponsorID,
- NetworkID: networkID,
- }
- err = config.Commit(false)
- if err != nil {
- return errors.Trace(err)
- }
- err = OpenDataStore(config)
- if err != nil {
- return errors.Trace(err)
- }
- defer CloseDataStore()
- manager := NewInproxyBrokerClientManager(config, isProxy)
- // Test: no broker specs
- _, _, err = manager.GetBrokerClient(networkID)
- if err == nil {
- return errors.TraceNew("unexpected success")
- }
- // Test: select broker and common compartment IDs
- config = &Config{
- DataRootDirectory: testDataDirName,
- PropagationChannelId: propagationChannelID,
- SponsorId: sponsorID,
- NetworkID: networkID,
- InproxyBrokerSpecs: brokerSpecs,
- InproxyCommonCompartmentIDs: commonCompartmentIDs,
- InproxyReplayBrokerRetainFailedProbability: &retainFailed,
- }
- err = config.Commit(false)
- if err != nil {
- return errors.Trace(err)
- }
- config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
- manager = NewInproxyBrokerClientManager(config, isProxy)
- brokerClient, brokerDialParams, err := manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if !regexp.MustCompile(addressRegex).Copy().Match(
- []byte(brokerDialParams.FrontingDialAddress)) {
- return errors.TraceNew("unexpected FrontingDialAddress")
- }
- if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 1 ||
- brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()[0].String() !=
- commonCompartmentID.String() {
- return errors.TraceNew("unexpected compartment IDs")
- }
- _ = brokerDialParams.GetMetrics()
- // Test: replay on success
- previousFrontingDialAddress := brokerDialParams.FrontingDialAddress
- previousTLSProfile := brokerDialParams.TLSProfile
- roundTripper, err := brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripper()
- if err != nil {
- return errors.Trace(err)
- }
- brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripperSucceeded(roundTripper)
- manager = NewInproxyBrokerClientManager(config, isProxy)
- brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if !brokerDialParams.isReplay {
- return errors.TraceNew("unexpected non-replay")
- }
- if brokerDialParams.FrontingDialAddress != previousFrontingDialAddress {
- return errors.TraceNew("unexpected replayed FrontingDialAddress")
- }
- if brokerDialParams.TLSProfile != previousTLSProfile {
- return errors.TraceNew("unexpected replayed TLSProfile")
- }
- _ = brokerDialParams.GetMetrics()
- // Test: manager's broker client and dial parameters reinitialized after
- // network ID change
- previousBrokerClient := brokerClient
- previousNetworkID := networkID
- networkID = "NETWORK2"
- config.networkIDGetter = newStaticNetworkGetter(networkID)
- config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
- brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if brokerClient == previousBrokerClient {
- return errors.TraceNew("unexpected brokerClient")
- }
- if brokerDialParams.isReplay {
- return errors.TraceNew("unexpected replay")
- }
- if brokerDialParams.FrontingDialAddress == previousFrontingDialAddress {
- return errors.TraceNew("unexpected non-replayed FrontingDialAddress")
- }
- _ = brokerDialParams.GetMetrics()
- // Test: another replay after switch back to previous network ID
- networkID = previousNetworkID
- config.networkIDGetter = newStaticNetworkGetter(networkID)
- brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if !brokerDialParams.isReplay {
- return errors.TraceNew("unexpected non-replay")
- }
- if brokerDialParams.FrontingDialAddress != previousFrontingDialAddress {
- return errors.TraceNew("unexpected replayed FrontingDialAddress")
- }
- if brokerDialParams.TLSProfile != previousTLSProfile {
- return errors.TraceNew("unexpected replayed TLSProfile")
- }
- _ = brokerDialParams.GetMetrics()
- // Test: clear replay
- roundTripper, err = brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripper()
- if err != nil {
- return errors.Trace(err)
- }
- brokerClient.GetBrokerDialCoordinator().BrokerClientRoundTripperFailed(roundTripper)
- manager = NewInproxyBrokerClientManager(config, isProxy)
- brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if brokerDialParams.isReplay {
- return errors.TraceNew("unexpected replay")
- }
- if brokerDialParams.FrontingDialAddress == previousFrontingDialAddress {
- return errors.TraceNew("unexpected non-replayed FrontingDialAddress")
- }
- _ = brokerDialParams.GetMetrics()
- // Test: no common compartment IDs sent when personal ID is set
- config.InproxyClientPersonalCompartmentIDs = personalCompartmentIDs
- config.InproxyProxyPersonalCompartmentIDs = personalCompartmentIDs
- manager = NewInproxyBrokerClientManager(config, isProxy)
- brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 0 ||
- len(brokerClient.GetBrokerDialCoordinator().PersonalCompartmentIDs()) != 1 ||
- brokerClient.GetBrokerDialCoordinator().PersonalCompartmentIDs()[0].String() !=
- personalCompartmentID.String() {
- return errors.TraceNew("unexpected compartment IDs")
- }
- // Test: use persisted common compartment IDs
- config = &Config{
- PropagationChannelId: propagationChannelID,
- SponsorId: sponsorID,
- NetworkID: networkID,
- }
- config.InproxyBrokerSpecs = brokerSpecs
- config.InproxyCommonCompartmentIDs = nil
- err = config.Commit(false)
- if err != nil {
- return errors.Trace(err)
- }
- config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
- manager = NewInproxyBrokerClientManager(config, isProxy)
- brokerClient, brokerDialParams, err = manager.GetBrokerClient(networkID)
- if err != nil {
- return errors.Trace(err)
- }
- if len(brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()) != 1 ||
- brokerClient.GetBrokerDialCoordinator().CommonCompartmentIDs()[0].String() !=
- commonCompartmentID.String() {
- return errors.TraceNew("unexpected compartment IDs")
- }
- _ = brokerDialParams.GetMetrics()
- return nil
- }
- func runInproxySTUNDialParametersTest() error {
- testDataDirName, err := ioutil.TempDir("", "psiphon-inproxy-stun-test")
- if err != nil {
- return errors.Trace(err)
- }
- defer os.RemoveAll(testDataDirName)
- propagationChannelID := prng.HexString(8)
- sponsorID := prng.HexString(8)
- networkID := "NETWORK1"
- stunServerAddresses := []string{"example.org"}
- config := &Config{
- DataRootDirectory: testDataDirName,
- PropagationChannelId: propagationChannelID,
- SponsorId: sponsorID,
- NetworkID: networkID,
- InproxySTUNServerAddresses: stunServerAddresses,
- InproxySTUNServerAddressesRFC5780: stunServerAddresses,
- }
- err = config.Commit(false)
- if err != nil {
- return errors.Trace(err)
- }
- config.SetResolver(resolver.NewResolver(&resolver.NetworkConfig{}, networkID))
- p := config.GetParameters().Get()
- defer p.Close()
- dialParams, err := MakeInproxySTUNDialParameters(config, p, false)
- if err != nil {
- return errors.Trace(err)
- }
- _ = dialParams.GetMetrics()
- dialParamsJSON, err := json.Marshal(dialParams)
- if err != nil {
- return errors.Trace(err)
- }
- var replayDialParams *InproxySTUNDialParameters
- err = json.Unmarshal(dialParamsJSON, &replayDialParams)
- if err != nil {
- return errors.Trace(err)
- }
- replayDialParams.Prepare()
- _ = replayDialParams.GetMetrics()
- return nil
- }
- func runInproxyNATStateTest() error {
- propagationChannelID := prng.HexString(8)
- sponsorID := prng.HexString(8)
- networkID := "NETWORK1"
- config := &Config{
- PropagationChannelId: propagationChannelID,
- SponsorId: sponsorID,
- NetworkID: networkID,
- }
- err := config.Commit(false)
- if err != nil {
- return errors.Trace(err)
- }
- manager := NewInproxyNATStateManager(config)
- // Test: set values stored and cached
- manager.setNATType(networkID, inproxy.NATTypeSymmetric)
- manager.setPortMappingTypes(networkID, inproxy.PortMappingTypes{inproxy.PortMappingTypeUPnP})
- if manager.getNATType(networkID) != inproxy.NATTypeSymmetric {
- return errors.TraceNew("unexpected NAT type")
- }
- portMappingTypes := manager.getPortMappingTypes(networkID)
- if len(portMappingTypes) != 1 || portMappingTypes[0] != inproxy.PortMappingTypeUPnP {
- return errors.TraceNew("unexpected port mapping types")
- }
- // Test: set values ignored when network ID is changing
- otherNetworkID := "NETWORK2"
- manager.setNATType(otherNetworkID, inproxy.NATTypePortRestrictedCone)
- manager.setPortMappingTypes(otherNetworkID, inproxy.PortMappingTypes{inproxy.PortMappingTypePMP})
- if manager.getNATType(networkID) != inproxy.NATTypeSymmetric {
- return errors.TraceNew("unexpected NAT type")
- }
- portMappingTypes = manager.getPortMappingTypes(networkID)
- if len(portMappingTypes) != 1 || portMappingTypes[0] != inproxy.PortMappingTypeUPnP {
- return errors.TraceNew("unexpected port mapping types")
- }
- // Test: reset
- networkID = "NETWORK2"
- config.networkIDGetter = newStaticNetworkGetter(networkID)
- manager.reset()
- if manager.networkID != networkID {
- return errors.TraceNew("unexpected network ID")
- }
- if manager.getNATType(networkID) != inproxy.NATTypeUnknown {
- return errors.TraceNew("unexpected NAT type")
- }
- if len(manager.getPortMappingTypes(networkID)) != 0 {
- return errors.TraceNew("unexpected port mapping types")
- }
- return nil
- }
|