Просмотр исходного кода

Add jitter to meek polling schedule

Rod Hynes 9 лет назад
Родитель
Сommit
5c4f5582f2
3 измененных файлов с 88 добавлено и 4 удалено
  1. 17 0
      psiphon/common/utils.go
  2. 42 0
      psiphon/common/utils_test.go
  3. 29 4
      psiphon/meekConn.go

+ 17 - 0
psiphon/common/utils.go

@@ -28,6 +28,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
+	"math"
 	"math/big"
 	"math/big"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
@@ -134,6 +135,22 @@ func MakeRandomStringBase64(byteLength int) (string, error) {
 	return base64.RawURLEncoding.EncodeToString(bytes), nil
 	return base64.RawURLEncoding.EncodeToString(bytes), nil
 }
 }
 
 
+// JitterPercentage returns n +/- the given percentage.
+// For example, for n = 100 and p = 0.1, the return value
+// will be in the range [90, 110].
+func JitterPercentage(n int64, percentage float64) int64 {
+	a := int64(math.Ceil(float64(n) * percentage))
+	r, _ := MakeSecureRandomInt64(2*a + 1)
+	return n + r - a
+}
+
+// JitterDurationPercentage is a helper function that
+// wraps JitterPercentage.
+func JitterDurationPercentage(
+	d time.Duration, percentage float64) time.Duration {
+	return time.Duration(JitterPercentage(int64(d), percentage))
+}
+
 // GetCurrentTimestamp returns the current time in UTC as
 // GetCurrentTimestamp returns the current time in UTC as
 // an RFC 3339 formatted string.
 // an RFC 3339 formatted string.
 func GetCurrentTimestamp() string {
 func GetCurrentTimestamp() string {

+ 42 - 0
psiphon/common/utils_test.go

@@ -21,6 +21,8 @@ package common
 
 
 import (
 import (
 	"bytes"
 	"bytes"
+	"fmt"
+	"math"
 	"testing"
 	"testing"
 	"time"
 	"time"
 )
 )
@@ -54,6 +56,46 @@ func TestMakeRandomPeriod(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestJitterPercentage(t *testing.T) {
+
+	testCases := []struct {
+		n           int64
+		p           float64
+		expectedMin int64
+		expectedMax int64
+	}{
+		{100, 0.1, 90, 110},
+		{1000, 0.3, 700, 1300},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(fmt.Sprintf("jitter case: %+v", testCase), func(t *testing.T) {
+
+			min := int64(math.MaxInt64)
+			max := int64(0)
+
+			for i := 0; i < 100000; i++ {
+
+				x := JitterPercentage(testCase.n, testCase.p)
+				if x < min {
+					min = x
+				}
+				if x > max {
+					max = x
+				}
+			}
+
+			if min != testCase.expectedMin {
+				t.Errorf("unexpected minimum jittered value: %d", min)
+			}
+
+			if max != testCase.expectedMax {
+				t.Errorf("unexpected maximum jittered value: %d", max)
+			}
+		})
+	}
+}
+
 func TestCompress(t *testing.T) {
 func TestCompress(t *testing.T) {
 
 
 	originalData := []byte("test data")
 	originalData := []byte("test data")

+ 29 - 4
psiphon/meekConn.go

@@ -54,8 +54,11 @@ const (
 	FULL_RECEIVE_BUFFER_LENGTH     = 4194304
 	FULL_RECEIVE_BUFFER_LENGTH     = 4194304
 	READ_PAYLOAD_CHUNK_LENGTH      = 65536
 	READ_PAYLOAD_CHUNK_LENGTH      = 65536
 	MIN_POLL_INTERVAL              = 100 * time.Millisecond
 	MIN_POLL_INTERVAL              = 100 * time.Millisecond
+	MIN_POLL_INTERVAL_JITTER       = 0.3
 	MAX_POLL_INTERVAL              = 5 * time.Second
 	MAX_POLL_INTERVAL              = 5 * time.Second
-	POLL_INTERNAL_MULTIPLIER       = 1.5
+	MAX_POLL_INTERVAL_JITTER       = 0.1
+	POLL_INTERVAL_MULTIPLIER       = 1.5
+	POLL_INTERVAL_JITTER           = 0.1
 	MEEK_ROUND_TRIP_RETRY_DEADLINE = 1 * time.Second
 	MEEK_ROUND_TRIP_RETRY_DEADLINE = 1 * time.Second
 	MEEK_ROUND_TRIP_RETRY_DELAY    = 50 * time.Millisecond
 	MEEK_ROUND_TRIP_RETRY_DELAY    = 50 * time.Millisecond
 	MEEK_ROUND_TRIP_TIMEOUT        = 20 * time.Second
 	MEEK_ROUND_TRIP_TIMEOUT        = 20 * time.Second
@@ -517,15 +520,37 @@ func (meek *MeekConn) relay() {
 			go meek.Close()
 			go meek.Close()
 			return
 			return
 		}
 		}
+
+		// Calculate polling interval. When data is received,
+		// immediately request more. Otherwise, schedule next
+		// poll with exponential back off. Jitter and coin
+		// flips are used to avoid trivial, static traffic
+		// timing patterns.
+
 		if receivedPayloadSize > 0 || sendPayloadSize > 0 {
 		if receivedPayloadSize > 0 || sendPayloadSize > 0 {
+
 			interval = 0
 			interval = 0
+
 		} else if interval == 0 {
 		} else if interval == 0 {
-			interval = MIN_POLL_INTERVAL
+
+			interval = common.JitterDurationPercentage(
+				MIN_POLL_INTERVAL,
+				MIN_POLL_INTERVAL_JITTER)
+
 		} else {
 		} else {
-			interval = time.Duration(float64(interval) * POLL_INTERNAL_MULTIPLIER)
+
+			if common.FlipCoin() {
+				interval = common.JitterDurationPercentage(
+					time.Duration(float64(interval)*POLL_INTERVAL_MULTIPLIER),
+					POLL_INTERVAL_JITTER)
+			}
+
 			if interval >= MAX_POLL_INTERVAL {
 			if interval >= MAX_POLL_INTERVAL {
-				interval = MAX_POLL_INTERVAL
+				interval = common.JitterDurationPercentage(
+					MAX_POLL_INTERVAL,
+					MAX_POLL_INTERVAL_JITTER)
 			}
 			}
+
 		}
 		}
 	}
 	}
 }
 }