|
|
@@ -226,11 +226,9 @@ type Config struct {
|
|
|
// This parameter is only applicable to library deployments.
|
|
|
NetworkIDGetter NetworkIDGetter
|
|
|
|
|
|
- // NetworkID, when specified, is used as the identifier for the host's
|
|
|
+ // NetworkID, when not blank, is used as the identifier for the host's
|
|
|
// current active network.
|
|
|
- //
|
|
|
- // This parameter is only applicable to non-library deployments which
|
|
|
- // do not use NetworkIDGetter.
|
|
|
+ // NetworkID is ignored when NetworkIDGetter is set.
|
|
|
NetworkID string
|
|
|
|
|
|
// DisableTactics disables tactics operations including requests, payload
|
|
|
@@ -460,10 +458,21 @@ type Config struct {
|
|
|
// New tactics must be applied by calling Config.SetClientParameters;
|
|
|
// calling clientParameters.Set directly will fail to add config values.
|
|
|
clientParameters *parameters.ClientParameters
|
|
|
+
|
|
|
+ deviceBinder DeviceBinder
|
|
|
+ networkIDGetter NetworkIDGetter
|
|
|
+
|
|
|
+ committed bool
|
|
|
}
|
|
|
|
|
|
-// LoadConfig parses and validates a JSON format Psiphon config JSON
|
|
|
-// string and returns a Config struct populated with config values.
|
|
|
+// LoadConfig parses a JSON format Psiphon config JSON string and returns a
|
|
|
+// Config struct populated with config values.
|
|
|
+//
|
|
|
+// The Config struct may then be programmatically populated with additional
|
|
|
+// values, including callbacks such as DeviceBinder.
|
|
|
+//
|
|
|
+// Before using the Config, Commit must be called, which will perform further
|
|
|
+// validation and initialize internal data structures.
|
|
|
func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
|
|
|
var config Config
|
|
|
@@ -472,6 +481,20 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
return nil, common.ContextError(err)
|
|
|
}
|
|
|
|
|
|
+ return &config, nil
|
|
|
+}
|
|
|
+
|
|
|
+// IsCommitted checks if Commit was called.
|
|
|
+func (config *Config) IsCommitted() bool {
|
|
|
+ return config.committed
|
|
|
+}
|
|
|
+
|
|
|
+// Commit validates Config fields finalizes initialization.
|
|
|
+//
|
|
|
+// Config fields should not be set after calling Config, as any changes may
|
|
|
+// not be reflected in internal data structures.
|
|
|
+func (config *Config) Commit() error {
|
|
|
+
|
|
|
// Do SetEmitDiagnosticNotices first, to ensure config file errors are emitted.
|
|
|
|
|
|
if config.EmitDiagnosticNotices {
|
|
|
@@ -500,10 +523,11 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
// Supply default values.
|
|
|
|
|
|
if config.DataStoreDirectory == "" {
|
|
|
- config.DataStoreDirectory, err = os.Getwd()
|
|
|
+ wd, err := os.Getwd()
|
|
|
if err != nil {
|
|
|
- return nil, common.ContextError(err)
|
|
|
+ return common.ContextError(err)
|
|
|
}
|
|
|
+ config.DataStoreDirectory = wd
|
|
|
}
|
|
|
|
|
|
if config.ClientVersion == "" {
|
|
|
@@ -517,32 +541,32 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
// Validate config fields.
|
|
|
|
|
|
if config.PropagationChannelId == "" {
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
errors.New("propagation channel ID is missing from the configuration file"))
|
|
|
}
|
|
|
if config.SponsorId == "" {
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
errors.New("sponsor ID is missing from the configuration file"))
|
|
|
}
|
|
|
|
|
|
- _, err = strconv.Atoi(config.ClientVersion)
|
|
|
+ _, err := strconv.Atoi(config.ClientVersion)
|
|
|
if err != nil {
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
fmt.Errorf("invalid client version: %s", err))
|
|
|
}
|
|
|
|
|
|
if config.NetworkConnectivityChecker != nil {
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
errors.New("NetworkConnectivityChecker interface must be set at runtime"))
|
|
|
}
|
|
|
|
|
|
if config.DeviceBinder != nil {
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
errors.New("DeviceBinder interface must be set at runtime"))
|
|
|
}
|
|
|
|
|
|
if config.DnsServerGetter != nil {
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
errors.New("DnsServerGetter interface must be set at runtime"))
|
|
|
}
|
|
|
|
|
|
@@ -550,7 +574,7 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
[]string{"", protocol.PSIPHON_SSH_API_PROTOCOL, protocol.PSIPHON_WEB_API_PROTOCOL},
|
|
|
config.TargetApiProtocol) {
|
|
|
|
|
|
- return nil, common.ContextError(
|
|
|
+ return common.ContextError(
|
|
|
errors.New("invalid TargetApiProtocol"))
|
|
|
}
|
|
|
|
|
|
@@ -558,19 +582,19 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
|
|
|
if config.RemoteServerListURLs != nil {
|
|
|
if config.RemoteServerListSignaturePublicKey == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey"))
|
|
|
+ return common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey"))
|
|
|
}
|
|
|
if config.RemoteServerListDownloadFilename == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing RemoteServerListDownloadFilename"))
|
|
|
+ return common.ContextError(errors.New("missing RemoteServerListDownloadFilename"))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if config.ObfuscatedServerListRootURLs != nil {
|
|
|
if config.RemoteServerListSignaturePublicKey == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey"))
|
|
|
+ return common.ContextError(errors.New("missing RemoteServerListSignaturePublicKey"))
|
|
|
}
|
|
|
if config.ObfuscatedServerListDownloadDirectory == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing ObfuscatedServerListDownloadDirectory"))
|
|
|
+ return common.ContextError(errors.New("missing ObfuscatedServerListDownloadDirectory"))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -578,26 +602,26 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
|
|
|
if config.SplitTunnelRoutesURLFormat != "" {
|
|
|
if config.SplitTunnelRoutesSignaturePublicKey == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing SplitTunnelRoutesSignaturePublicKey"))
|
|
|
+ return common.ContextError(errors.New("missing SplitTunnelRoutesSignaturePublicKey"))
|
|
|
}
|
|
|
if config.SplitTunnelDNSServer == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing SplitTunnelDNSServer"))
|
|
|
+ return common.ContextError(errors.New("missing SplitTunnelDNSServer"))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if config.UpgradeDownloadURLs != nil {
|
|
|
if config.UpgradeDownloadClientVersionHeader == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing UpgradeDownloadClientVersionHeader"))
|
|
|
+ return common.ContextError(errors.New("missing UpgradeDownloadClientVersionHeader"))
|
|
|
}
|
|
|
if config.UpgradeDownloadFilename == "" {
|
|
|
- return nil, common.ContextError(errors.New("missing UpgradeDownloadFilename"))
|
|
|
+ return common.ContextError(errors.New("missing UpgradeDownloadFilename"))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// This constraint is expected by logic in Controller.runTunnels().
|
|
|
|
|
|
if config.PacketTunnelTunFileDescriptor > 0 && config.TunnelPoolSize != 1 {
|
|
|
- return nil, common.ContextError(errors.New("packet tunnel mode requires TunnelPoolSize to be 1"))
|
|
|
+ return common.ContextError(errors.New("packet tunnel mode requires TunnelPoolSize to be 1"))
|
|
|
}
|
|
|
|
|
|
// SessionID must be PSIPHON_API_CLIENT_SESSION_ID_LENGTH lowercase hex-encoded bytes.
|
|
|
@@ -605,7 +629,7 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
if config.SessionID == "" {
|
|
|
sessionID, err := MakeSessionId()
|
|
|
if err != nil {
|
|
|
- return nil, common.ContextError(err)
|
|
|
+ return common.ContextError(err)
|
|
|
}
|
|
|
config.SessionID = sessionID
|
|
|
}
|
|
|
@@ -614,7 +638,7 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
-1 != strings.IndexFunc(config.SessionID, func(c rune) bool {
|
|
|
return !unicode.Is(unicode.ASCII_Hex_Digit, c) || unicode.IsUpper(c)
|
|
|
}) {
|
|
|
- return nil, common.ContextError(errors.New("invalid SessionID"))
|
|
|
+ return common.ContextError(errors.New("invalid SessionID"))
|
|
|
}
|
|
|
|
|
|
config.clientParameters, err = parameters.NewClientParameters(
|
|
|
@@ -622,17 +646,48 @@ func LoadConfig(configJson []byte) (*Config, error) {
|
|
|
NoticeAlert("ClientParameters getValue failed: %s", err)
|
|
|
})
|
|
|
if err != nil {
|
|
|
- return nil, common.ContextError(err)
|
|
|
+ return common.ContextError(err)
|
|
|
}
|
|
|
|
|
|
// clientParameters.Set will validate the config fields applied to parameters.
|
|
|
|
|
|
err = config.SetClientParameters("", false, nil)
|
|
|
if err != nil {
|
|
|
- return nil, common.ContextError(err)
|
|
|
+ return common.ContextError(err)
|
|
|
}
|
|
|
|
|
|
- return &config, nil
|
|
|
+ // Initialize config.deviceBinder and config.config.networkIDGetter. These
|
|
|
+ // wrap config.DeviceBinder and config.NetworkIDGetter/NetworkID with
|
|
|
+ // loggers.
|
|
|
+ //
|
|
|
+ // New variables are set to avoid mutating input config fields.
|
|
|
+ // Internally, code must use config.deviceBinder and
|
|
|
+ // config.networkIDGetter and not the input/exported fields.
|
|
|
+
|
|
|
+ if config.DeviceBinder != nil {
|
|
|
+ config.deviceBinder = &loggingDeviceBinder{config.DeviceBinder}
|
|
|
+ }
|
|
|
+
|
|
|
+ networkIDGetter := config.NetworkIDGetter
|
|
|
+
|
|
|
+ if networkIDGetter == nil && config.NetworkID != "" {
|
|
|
+
|
|
|
+ // Enable tactics when a NetworkID is specified
|
|
|
+ //
|
|
|
+ // Limitation: unlike NetworkIDGetter, which calls back to platform APIs
|
|
|
+ // this method of network identification is not dynamic and will not reflect
|
|
|
+ // network changes that occur while running.
|
|
|
+
|
|
|
+ networkIDGetter = newStaticNetworkGetter(config.NetworkID)
|
|
|
+ }
|
|
|
+
|
|
|
+ if networkIDGetter != nil {
|
|
|
+ config.networkIDGetter = &loggingNetworkIDGetter{networkIDGetter}
|
|
|
+ }
|
|
|
+
|
|
|
+ config.committed = true
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// GetClientParameters returns a snapshot of the current client parameters.
|
|
|
@@ -668,9 +723,12 @@ func (config *Config) SetClientParameters(tag string, skipOnError bool, applyPar
|
|
|
NoticeInfo("applied %v parameters with tag '%s'", counts, tag)
|
|
|
|
|
|
// Emit certain individual parameter values for quick reference in diagnostics.
|
|
|
- NoticeInfo(
|
|
|
- "NetworkLatencyMultiplier: %f",
|
|
|
- config.clientParameters.Get().Float(parameters.NetworkLatencyMultiplier))
|
|
|
+ networkLatencyMultiplier := config.clientParameters.Get().Float(parameters.NetworkLatencyMultiplier)
|
|
|
+ if networkLatencyMultiplier != 0.0 {
|
|
|
+ NoticeInfo(
|
|
|
+ "NetworkLatencyMultiplier: %f",
|
|
|
+ config.clientParameters.Get().Float(parameters.NetworkLatencyMultiplier))
|
|
|
+ }
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
@@ -732,6 +790,13 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
|
|
|
applyParameters[parameters.FetchUpgradeRetryPeriod] = fmt.Sprintf("%dms", *config.FetchUpgradeRetryPeriodMilliseconds)
|
|
|
}
|
|
|
|
|
|
+ switch config.TransformHostNames {
|
|
|
+ case "always":
|
|
|
+ applyParameters[parameters.TransformHostNameProbability] = 1.0
|
|
|
+ case "never":
|
|
|
+ applyParameters[parameters.TransformHostNameProbability] = 0.0
|
|
|
+ }
|
|
|
+
|
|
|
if !config.DisableRemoteServerListFetcher {
|
|
|
|
|
|
if config.RemoteServerListURLs != nil {
|
|
|
@@ -769,3 +834,58 @@ func promoteLegacyDownloadURL(URL string) parameters.DownloadURLs {
|
|
|
}
|
|
|
return downloadURLs
|
|
|
}
|
|
|
+
|
|
|
+type loggingDeviceBinder struct {
|
|
|
+ d DeviceBinder
|
|
|
+}
|
|
|
+
|
|
|
+func newLoggingDeviceBinder(d DeviceBinder) *loggingDeviceBinder {
|
|
|
+ return &loggingDeviceBinder{d: d}
|
|
|
+}
|
|
|
+
|
|
|
+func (d *loggingDeviceBinder) BindToDevice(fileDescriptor int) (string, error) {
|
|
|
+ deviceInfo, err := d.d.BindToDevice(fileDescriptor)
|
|
|
+ if err == nil && deviceInfo != "" {
|
|
|
+ NoticeBindToDevice(deviceInfo)
|
|
|
+ }
|
|
|
+ return deviceInfo, err
|
|
|
+}
|
|
|
+
|
|
|
+type staticNetworkGetter struct {
|
|
|
+ networkID string
|
|
|
+}
|
|
|
+
|
|
|
+func newStaticNetworkGetter(networkID string) *staticNetworkGetter {
|
|
|
+ return &staticNetworkGetter{networkID: networkID}
|
|
|
+}
|
|
|
+
|
|
|
+func (n *staticNetworkGetter) GetNetworkID() string {
|
|
|
+ return n.networkID
|
|
|
+}
|
|
|
+
|
|
|
+type loggingNetworkIDGetter struct {
|
|
|
+ n NetworkIDGetter
|
|
|
+}
|
|
|
+
|
|
|
+func newLoggingNetworkIDGetter(n NetworkIDGetter) *loggingNetworkIDGetter {
|
|
|
+ return &loggingNetworkIDGetter{n: n}
|
|
|
+}
|
|
|
+
|
|
|
+func (n *loggingNetworkIDGetter) GetNetworkID() string {
|
|
|
+ networkID := n.n.GetNetworkID()
|
|
|
+
|
|
|
+ // All PII must appear after the initial "-"
|
|
|
+ // See: https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter
|
|
|
+ logNetworkID := networkID
|
|
|
+ index := strings.Index(logNetworkID, "-")
|
|
|
+ if index != -1 {
|
|
|
+ logNetworkID = logNetworkID[:index]
|
|
|
+ }
|
|
|
+ if len(logNetworkID)+1 < len(networkID) {
|
|
|
+ // Indicate when additional network info was present after the first "-".
|
|
|
+ logNetworkID += "+<network info>"
|
|
|
+ }
|
|
|
+ NoticeNetworkID(logNetworkID)
|
|
|
+
|
|
|
+ return networkID
|
|
|
+}
|