Explorar o código

Add ApplicationParameters

Rod Hynes %!s(int64=6) %!d(string=hai) anos
pai
achega
46e25d32f3

+ 5 - 0
MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java

@@ -96,6 +96,7 @@ public class PsiphonTunnel {
         default public void onStartedWaitingForNetworkConnectivity() {}
         default public void onStoppedWaitingForNetworkConnectivity() {}
         default public void onActiveAuthorizationIDs(List<String> authorizations) {}
+        default public void onApplicationParameter(String key, Object value) {}
         default public void onExiting() {}
     }
 
@@ -783,6 +784,10 @@ public class PsiphonTunnel {
                       enableUdpGwKeepalive();
                     }
                 }
+            } else if (noticeType.equals("ApplicationParameter")) {
+                mHostService.onApplicationParameter(
+                    notice.getJSONObject("data").getString("key"),
+                    notice.getJSONObject("data").get("value"));
             }
 
             if (diagnostic) {

+ 20 - 0
psiphon/common/parameters/clientParameters.go

@@ -222,6 +222,8 @@ const (
 	RecordRemoteServerListPersistentStatsProbability = "RecordRemoteServerListPersistentStatsProbability"
 	RecordFailedTunnelPersistentStatsProbability     = "RecordFailedTunnelPersistentStatsProbability"
 	ServerEntryMinimumAgeForPruning                  = "ServerEntryMinimumAgeForPruning"
+	ApplicationParametersProbability                 = "ApplicationParametersProbability"
+	ApplicationParameters                            = "ApplicationParameters"
 )
 
 const (
@@ -458,6 +460,9 @@ var defaultClientParameters = map[string]struct {
 	RecordFailedTunnelPersistentStatsProbability:     {value: 0.0, minimum: 0.0},
 
 	ServerEntryMinimumAgeForPruning: {value: 7 * 24 * time.Hour, minimum: 24 * time.Hour},
+
+	ApplicationParametersProbability: {value: 1.0, minimum: 0.0},
+	ApplicationParameters:            {value: KeyValues{}},
 }
 
 // IsServerSideOnly indicates if the parameter specified by name is used
@@ -644,6 +649,14 @@ func (p *ClientParameters) Set(
 					}
 					return nil, common.ContextError(err)
 				}
+			case KeyValues:
+				err := v.Validate()
+				if err != nil {
+					if skipOnError {
+						continue
+					}
+					return nil, common.ContextError(err)
+				}
 			}
 
 			// Enforce any minimums. Assumes defaultClientParameters[name]
@@ -1019,3 +1032,10 @@ func (p ClientParametersAccessor) CustomTLSProfile(name string) *protocol.Custom
 	}
 	return nil
 }
+
+// KeyValues returns a KeyValues parameter value.
+func (p ClientParametersAccessor) KeyValues(name string) KeyValues {
+	value := KeyValues{}
+	p.snapshot.getValue(name, &value)
+	return value
+}

+ 70 - 0
psiphon/common/parameters/clientParameters_test.go

@@ -20,6 +20,7 @@
 package parameters
 
 import (
+	"encoding/json"
 	"net/http"
 	"reflect"
 	"testing"
@@ -102,6 +103,11 @@ func TestGetDefaultParameters(t *testing.T) {
 			if !reflect.DeepEqual(names, g) {
 				t.Fatalf("CustomTLSProfileNames returned %+v expected %+v", g, names)
 			}
+		case KeyValues:
+			g := p.Get().KeyValues(name)
+			if !reflect.DeepEqual(v, g) {
+				t.Fatalf("KeyValues returned %+v expected %+v", g, v)
+			}
 		default:
 			t.Fatalf("Unhandled default type: %s", name)
 		}
@@ -332,3 +338,67 @@ func TestCustomTLSProfiles(t *testing.T) {
 		t.Fatalf("Unexpected profile")
 	}
 }
+
+func TestApplicationParameters(t *testing.T) {
+
+	parametersJSON := []byte(`
+    {
+       "ApplicationParameters" : {
+         "AppFlag1" : true,
+         "AppConfig1" : {"Option1" : "A", "Option2" : "B"},
+         "AppSwitches1" : [1, 2, 3, 4]
+       }
+    }
+    `)
+
+	validators := map[string]func(v interface{}) bool{
+		"AppFlag1": func(v interface{}) bool { return reflect.DeepEqual(v, true) },
+		"AppConfig1": func(v interface{}) bool {
+			return reflect.DeepEqual(v, map[string]interface{}{"Option1": "A", "Option2": "B"})
+		},
+		"AppSwitches1": func(v interface{}) bool {
+			return reflect.DeepEqual(v, []interface{}{float64(1), float64(2), float64(3), float64(4)})
+		},
+	}
+
+	var applyParameters map[string]interface{}
+	err := json.Unmarshal(parametersJSON, &applyParameters)
+	if err != nil {
+		t.Fatalf("Unmarshal failed: %s", err)
+	}
+
+	p, err := NewClientParameters(nil)
+	if err != nil {
+		t.Fatalf("NewClientParameters failed: %s", err)
+	}
+
+	_, err = p.Set("", false, applyParameters)
+	if err != nil {
+		t.Fatalf("Set failed: %s", err)
+	}
+
+	keyValues := p.Get().KeyValues(ApplicationParameters)
+
+	if len(keyValues) != len(validators) {
+		t.Fatalf("Unexpected key value count")
+	}
+
+	for key, value := range keyValues {
+
+		validator, ok := validators[key]
+		if !ok {
+			t.Fatalf("Unexpected key: %s", key)
+		}
+
+		var unmarshaledValue interface{}
+		err := json.Unmarshal(value, &unmarshaledValue)
+		if err != nil {
+			t.Fatalf("Unmarshal failed: %s", err)
+		}
+
+		if !validator(unmarshaledValue) {
+			t.Fatalf("Invalid value: %s, %T: %+v",
+				key, unmarshaledValue, unmarshaledValue)
+		}
+	}
+}

+ 41 - 0
psiphon/common/parameters/keyValues.go

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019, 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 parameters
+
+import (
+	"encoding/json"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
+)
+
+// KeyValues represents a set of name/JSON pairs.
+type KeyValues map[string]json.RawMessage
+
+// Validate checks that the JSON values are well-formed.
+func (keyValues KeyValues) Validate() error {
+	for _, value := range keyValues {
+		var v interface{}
+		err := json.Unmarshal(value, &v)
+		if err != nil {
+			return common.ContextError(err)
+		}
+	}
+	return nil
+}

+ 14 - 0
psiphon/config.go

@@ -562,6 +562,9 @@ type Config struct {
 	SelectRandomizedTLSProfileProbability *float64
 	NoDefaultTLSSessionIDProbability      *float64
 
+	// ApplicationParameters is for testing purposes.
+	ApplicationParameters parameters.KeyValues
+
 	// clientParameters is the active ClientParameters with defaults, config
 	// values, and, optionally, tactics applied.
 	//
@@ -853,6 +856,13 @@ func (config *Config) SetClientParameters(tag string, skipOnError bool, applyPar
 		p.Float(parameters.NetworkLatencyMultiplierMin),
 		p.Float(parameters.NetworkLatencyMultiplierMax))
 
+	// Application Parameters are feature flags/config info, delivered as Client
+	// Parameters via tactics/etc., to be communicated to the outer application.
+	// Emit these now, as notices.
+	if p.WeightedCoinFlip(parameters.ApplicationParametersProbability) {
+		NoticeApplicationParameters(p.KeyValues(parameters.ApplicationParameters))
+	}
+
 	return nil
 }
 
@@ -1132,6 +1142,10 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.NoDefaultTLSSessionIDProbability] = *config.NoDefaultTLSSessionIDProbability
 	}
 
+	if config.ApplicationParameters != nil {
+		applyParameters[parameters.ApplicationParameters] = config.ApplicationParameters
+	}
+
 	return applyParameters
 }
 

+ 10 - 0
psiphon/notice.go

@@ -33,6 +33,7 @@ import (
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
 )
 
 type noticeLogger struct {
@@ -827,6 +828,15 @@ func NoticeFragmentor(diagnosticID string, message string) {
 		"message", message)
 }
 
+func NoticeApplicationParameters(keyValues parameters.KeyValues) {
+	for key, value := range keyValues {
+		singletonNoticeLogger.outputNotice(
+			"ApplicationParameter", 0,
+			"key", key,
+			"value", value)
+	}
+}
+
 type repetitiveNoticeState struct {
 	message string
 	repeats int

+ 6 - 1
psiphon/server/server_test.go

@@ -1810,7 +1810,12 @@ func paveTacticsConfigFile(
               "TunnelConnectTimeout" : "20s",
               "TunnelRateLimits" : {"WriteBytesPerSecond": 1000000},
               "TransformHostNameProbability" : 1.0,
-              "PickUserAgentProbability" : 1.0
+              "PickUserAgentProbability" : 1.0,
+              "ApplicationParameters" : {
+                "AppFlag1" : true,
+                "AppConfig1" : {"Option1" : "A", "Option2" : "B"},
+                "AppSwitches1" : [1, 2, 3, 4]
+              }
             }
           }
         }