aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeskio <meskio@torproject.org>2024-03-12 08:28:53 -0300
committermeskio <meskio@torproject.org>2024-03-12 08:28:53 -0300
commitf502eca67d345e0a6b460f2b677d4ad50dbc9709 (patch)
tree57cf2a1831eb86f0b19ff828fa923784187d0ac1
parentd657098340cffa49fcd5400e58cd614e76fd71af (diff)
parent8968535c56e00389b371e7cdb3b1e84628a796ae (diff)
downloadsnowflake-f502eca67d345e0a6b460f2b677d4ad50dbc9709.tar.gz
snowflake-f502eca67d345e0a6b460f2b677d4ad50dbc9709.zip
Merge remote-tracking branch 'origin/mr/258'
-rw-r--r--broker/amp.go3
-rw-r--r--broker/http.go7
-rw-r--r--broker/ipc.go14
-rw-r--r--broker/metrics.go81
-rw-r--r--broker/snowflake-broker_test.go29
-rw-r--r--broker/sqs.go21
-rw-r--r--broker/sqs_test.go3
-rw-r--r--common/util/util.go68
-rw-r--r--common/util/util_test.go47
-rw-r--r--doc/broker-spec.txt24
-rw-r--r--go.mod1
-rw-r--r--go.sum2
12 files changed, 276 insertions, 24 deletions
diff --git a/broker/amp.go b/broker/amp.go
index 4c7a036..99289de 100644
--- a/broker/amp.go
+++ b/broker/amp.go
@@ -7,6 +7,7 @@ import (
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp"
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
+ "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util"
)
// ampClientOffers is the AMP-speaking endpoint for client poll messages,
@@ -35,7 +36,7 @@ func ampClientOffers(i *IPC, w http.ResponseWriter, r *http.Request) {
if err == nil {
arg := messages.Arg{
Body: encPollReq,
- RemoteAddr: "",
+ RemoteAddr: util.GetClientIp(r),
RendezvousMethod: messages.RendezvousAmpCache,
}
err = i.ClientOffers(arg, &response)
diff --git a/broker/http.go b/broker/http.go
index d1ba20d..d3e43c1 100644
--- a/broker/http.go
+++ b/broker/http.go
@@ -10,6 +10,7 @@ import (
"os"
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
+ "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util"
)
const (
@@ -102,7 +103,7 @@ func proxyPolls(i *IPC, w http.ResponseWriter, r *http.Request) {
arg := messages.Arg{
Body: body,
- RemoteAddr: r.RemoteAddr,
+ RemoteAddr: util.GetClientIp(r),
}
var response []byte
@@ -167,7 +168,7 @@ func clientOffers(i *IPC, w http.ResponseWriter, r *http.Request) {
arg := messages.Arg{
Body: body,
- RemoteAddr: "",
+ RemoteAddr: util.GetClientIp(r),
RendezvousMethod: messages.RendezvousHttp,
}
@@ -227,7 +228,7 @@ func proxyAnswers(i *IPC, w http.ResponseWriter, r *http.Request) {
arg := messages.Arg{
Body: body,
- RemoteAddr: "",
+ RemoteAddr: util.GetClientIp(r),
}
var response []byte
diff --git a/broker/ipc.go b/broker/ipc.go
index 9116a1a..1752a9b 100644
--- a/broker/ipc.go
+++ b/broker/ipc.go
@@ -5,7 +5,6 @@ import (
"encoding/hex"
"fmt"
"log"
- "net"
"time"
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint"
@@ -101,7 +100,7 @@ func (i *IPC) ProxyPolls(arg messages.Arg, response *[]byte) error {
}
// Log geoip stats
- remoteIP, _, err := net.SplitHostPort(arg.RemoteAddr)
+ remoteIP := arg.RemoteAddr
if err != nil {
log.Println("Warning: cannot process proxy IP: ", err.Error())
} else {
@@ -196,13 +195,7 @@ func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error {
snowflake.offerChannel <- offer
} else {
i.ctx.metrics.lock.Lock()
- i.ctx.metrics.clientDeniedCount[arg.RendezvousMethod]++
- i.ctx.metrics.promMetrics.ClientPollTotal.With(prometheus.Labels{"nat": offer.natType, "status": "denied", "rendezvous_method": string(arg.RendezvousMethod)}).Inc()
- if offer.natType == NATUnrestricted {
- i.ctx.metrics.clientUnrestrictedDeniedCount[arg.RendezvousMethod]++
- } else {
- i.ctx.metrics.clientRestrictedDeniedCount[arg.RendezvousMethod]++
- }
+ i.ctx.metrics.UpdateRendezvousStats(arg.RemoteAddr, arg.RendezvousMethod, offer.natType, false)
i.ctx.metrics.lock.Unlock()
resp := &messages.ClientPollResponse{Error: messages.StrNoProxies}
return sendClientResponse(resp, response)
@@ -212,8 +205,7 @@ func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error {
select {
case answer := <-snowflake.answerChannel:
i.ctx.metrics.lock.Lock()
- i.ctx.metrics.clientProxyMatchCount[arg.RendezvousMethod]++
- i.ctx.metrics.promMetrics.ClientPollTotal.With(prometheus.Labels{"nat": offer.natType, "status": "matched", "rendezvous_method": string(arg.RendezvousMethod)}).Inc()
+ i.ctx.metrics.UpdateRendezvousStats(arg.RemoteAddr, arg.RendezvousMethod, offer.natType, true)
i.ctx.metrics.lock.Unlock()
resp := &messages.ClientPollResponse{Answer: answer}
err = sendClientResponse(resp, response)
diff --git a/broker/metrics.go b/broker/metrics.go
index b24eec9..a40b1bc 100644
--- a/broker/metrics.go
+++ b/broker/metrics.go
@@ -24,6 +24,12 @@ const (
metricsResolution = 60 * 60 * 24 * time.Second //86400 seconds
)
+var rendezvoudMethodList = [...]messages.RendezvousMethod{
+ messages.RendezvousHttp,
+ messages.RendezvousAmpCache,
+ messages.RendezvousSqs,
+}
+
type CountryStats struct {
// map[proxyType][address]bool
proxies map[string]map[string]bool
@@ -49,6 +55,8 @@ type Metrics struct {
clientUnrestrictedDeniedCount map[messages.RendezvousMethod]uint
clientProxyMatchCount map[messages.RendezvousMethod]uint
+ rendezvousCountryStats map[messages.RendezvousMethod]map[string]int
+
proxyPollWithRelayURLExtension uint
proxyPollWithoutRelayURLExtension uint
proxyPollRejectedWithRelayURLExtension uint
@@ -96,7 +104,6 @@ func (s CountryStats) Display() string {
}
func (m *Metrics) UpdateCountryStats(addr string, proxyType string, natType string) {
-
var country string
var ok bool
@@ -137,7 +144,59 @@ func (m *Metrics) UpdateCountryStats(addr string, proxyType string, natType stri
default:
m.countryStats.natUnknown[addr] = true
}
+}
+
+func (m *Metrics) UpdateRendezvousStats(addr string, rendezvousMethod messages.RendezvousMethod, natType string, matched bool) {
+ ip := net.ParseIP(addr)
+ country := "??"
+ if m.geoipdb != nil {
+ country_by_addr, ok := m.geoipdb.GetCountryByAddr(ip)
+ if ok {
+ country = country_by_addr
+ }
+ }
+
+ var status string
+ if !matched {
+ m.clientDeniedCount[rendezvousMethod]++
+ if natType == NATUnrestricted {
+ m.clientUnrestrictedDeniedCount[rendezvousMethod]++
+ } else {
+ m.clientRestrictedDeniedCount[rendezvousMethod]++
+ }
+ status = "denied"
+ } else {
+ status = "matched"
+ m.clientProxyMatchCount[rendezvousMethod]++
+ }
+ m.rendezvousCountryStats[rendezvousMethod][country]++
+ m.promMetrics.ClientPollTotal.With(prometheus.Labels{
+ "nat": natType,
+ "status": status,
+ "rendezvous_method": string(rendezvousMethod),
+ "cc": country,
+ }).Inc()
+}
+
+func (m *Metrics) DisplayRendezvousStatsByCountry(rendezvoudMethod messages.RendezvousMethod) string {
+ output := ""
+
+ // Use the records struct to sort our counts map by value.
+ rs := records{}
+ for cc, count := range m.rendezvousCountryStats[rendezvoudMethod] {
+ rs = append(rs, record{cc: cc, count: count})
+ }
+ sort.Sort(sort.Reverse(rs))
+ for _, r := range rs {
+ output += fmt.Sprintf("%s=%d,", r.cc, binCount(uint(r.count)))
+ }
+
+ // cut off trailing ","
+ if len(output) > 0 {
+ return output[:len(output)-1]
+ }
+ return output
}
func (m *Metrics) LoadGeoipDatabases(geoipDB string, geoip6DB string) error {
@@ -157,6 +216,11 @@ func NewMetrics(metricsLogger *log.Logger) (*Metrics, error) {
m.clientUnrestrictedDeniedCount = make(map[messages.RendezvousMethod]uint)
m.clientProxyMatchCount = make(map[messages.RendezvousMethod]uint)
+ m.rendezvousCountryStats = make(map[messages.RendezvousMethod]map[string]int)
+ for _, rendezvousMethod := range rendezvoudMethodList {
+ m.rendezvousCountryStats[rendezvousMethod] = make(map[string]int)
+ }
+
m.countryStats = CountryStats{
counts: make(map[string]int),
proxies: make(map[string]map[string]bool),
@@ -211,14 +275,11 @@ func (m *Metrics) printMetrics() {
m.logger.Println("client-unrestricted-denied-count", binCount(sumMapValues(&m.clientUnrestrictedDeniedCount)))
m.logger.Println("client-snowflake-match-count", binCount(sumMapValues(&m.clientProxyMatchCount)))
- for _, rendezvousMethod := range [3]messages.RendezvousMethod{
- messages.RendezvousHttp,
- messages.RendezvousAmpCache,
- messages.RendezvousSqs,
- } {
+ for _, rendezvousMethod := range rendezvoudMethodList {
m.logger.Printf("client-%s-count %d\n", rendezvousMethod, binCount(
m.clientDeniedCount[rendezvousMethod]+m.clientProxyMatchCount[rendezvousMethod],
))
+ m.logger.Printf("client-%s-ips %s\n", rendezvousMethod, m.DisplayRendezvousStatsByCountry(rendezvousMethod))
}
m.logger.Println("snowflake-ips-nat-restricted", len(m.countryStats.natRestricted))
@@ -237,6 +298,12 @@ func (m *Metrics) zeroMetrics() {
m.proxyPollWithRelayURLExtension = 0
m.proxyPollWithoutRelayURLExtension = 0
m.clientProxyMatchCount = make(map[messages.RendezvousMethod]uint)
+
+ m.rendezvousCountryStats = make(map[messages.RendezvousMethod]map[string]int)
+ for _, rendezvousMethod := range rendezvoudMethodList {
+ m.rendezvousCountryStats[rendezvousMethod] = make(map[string]int)
+ }
+
m.countryStats.counts = make(map[string]int)
for pType := range m.countryStats.proxies {
m.countryStats.proxies[pType] = make(map[string]bool)
@@ -339,7 +406,7 @@ func initPrometheus() *PromMetrics {
Name: "rounded_client_poll_total",
Help: "The number of snowflake client polls, rounded up to a multiple of 8",
},
- []string{"nat", "status", "rendezvous_method"},
+ []string{"nat", "status", "cc", "rendezvous_method"},
)
// We need to register our metrics so they can be exported.
diff --git a/broker/snowflake-broker_test.go b/broker/snowflake-broker_test.go
index 92d5700..bb4360e 100644
--- a/broker/snowflake-broker_test.go
+++ b/broker/snowflake-broker_test.go
@@ -157,8 +157,11 @@ client-restricted-denied-count 8
client-unrestricted-denied-count 0
client-snowflake-match-count 0
client-http-count 8
+client-http-ips ??=8
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
+client-sqs-ips
`)
})
@@ -184,8 +187,11 @@ client-restricted-denied-count 0
client-unrestricted-denied-count 0
client-snowflake-match-count 8
client-http-count 8
+client-http-ips ??=8
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
+client-sqs-ips
`)
})
@@ -260,8 +266,11 @@ client-restricted-denied-count 8
client-unrestricted-denied-count 0
client-snowflake-match-count 0
client-http-count 8
+client-http-ips ??=8
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
+client-sqs-ips
`)
})
@@ -287,8 +296,11 @@ client-restricted-denied-count 0
client-unrestricted-denied-count 0
client-snowflake-match-count 8
client-http-count 8
+client-http-ips ??=8
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
+client-sqs-ips
`)
})
@@ -340,8 +352,11 @@ client-restricted-denied-count 8
client-unrestricted-denied-count 0
client-snowflake-match-count 0
client-http-count 0
+client-http-ips
client-ampcache-count 8
+client-ampcache-ips ??=8
client-sqs-count 0
+client-sqs-ips
`)
})
@@ -369,8 +384,11 @@ client-restricted-denied-count 0
client-unrestricted-denied-count 0
client-snowflake-match-count 8
client-http-count 0
+client-http-ips
client-ampcache-count 8
+client-ampcache-ips ??=8
client-sqs-count 0
+client-sqs-ips
`)
})
@@ -728,8 +746,11 @@ client-restricted-denied-count 0
client-unrestricted-denied-count 0
client-snowflake-match-count 0
client-http-count 0
+client-http-ips
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
+client-sqs-ips
snowflake-ips-nat-restricted 0
snowflake-ips-nat-unrestricted 0
snowflake-ips-nat-unknown 1
@@ -742,6 +763,7 @@ snowflake-ips-nat-unknown 1
data, err := createClientOffer(sdp, NATUnknown, "")
So(err, ShouldBeNil)
r, err := http.NewRequest("POST", "snowflake.broker/client", data)
+ r.RemoteAddr = "129.97.208.23:8888" //CA geoip
So(err, ShouldBeNil)
clientOffers(i, w, r)
@@ -752,9 +774,11 @@ client-restricted-denied-count 8
client-unrestricted-denied-count 0
client-snowflake-match-count 0
client-http-count 8
+client-http-ips CA=8
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
-`)
+client-sqs-ips `)
// Test reset
buf.Reset()
@@ -774,8 +798,11 @@ client-restricted-denied-count 0
client-unrestricted-denied-count 0
client-snowflake-match-count 0
client-http-count 0
+client-http-ips
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 0
+client-sqs-ips
snowflake-ips-nat-restricted 0
snowflake-ips-nat-unrestricted 0
snowflake-ips-nat-unknown 0
diff --git a/broker/sqs.go b/broker/sqs.go
index 42e5dd3..614dafe 100644
--- a/broker/sqs.go
+++ b/broker/sqs.go
@@ -12,6 +12,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqsclient"
+ "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util"
)
const (
@@ -144,9 +145,27 @@ func (r *sqsHandler) handleMessage(context context.Context, message *types.Messa
answerSQSURL := res.QueueUrl
encPollReq = []byte(*message.Body)
+
+ // Get best guess Client IP for geolocating
+ remoteAddr := ""
+ req, err := messages.DecodeClientPollRequest(encPollReq)
+ if err != nil {
+ log.Printf("SQSHandler: error encounted when decoding client poll request %s: %v\n", *clientID, err)
+ } else {
+ sdp, err := util.DeserializeSessionDescription(req.Offer)
+ if err != nil {
+ log.Printf("SQSHandler: error encounted when deserializing session desc %s: %v\n", *clientID, err)
+ } else {
+ candidateAddrs := util.GetCandidateAddrs(sdp.SDP)
+ if len(candidateAddrs) > 0 {
+ remoteAddr = candidateAddrs[0].String()
+ }
+ }
+ }
+
arg := messages.Arg{
Body: encPollReq,
- RemoteAddr: "",
+ RemoteAddr: remoteAddr,
RendezvousMethod: messages.RendezvousSqs,
}
err = r.IPC.ClientOffers(arg, &response)
diff --git a/broker/sqs_test.go b/broker/sqs_test.go
index 216e146..40b70ba 100644
--- a/broker/sqs_test.go
+++ b/broker/sqs_test.go
@@ -195,8 +195,11 @@ client-restricted-denied-count 0
client-unrestricted-denied-count 0
client-snowflake-match-count 8
client-http-count 0
+client-http-ips
client-ampcache-count 0
+client-ampcache-ips
client-sqs-count 8
+client-sqs-ips ??=8
`)
wg.Done()
}
diff --git a/common/util/util.go b/common/util/util.go
index 00f7302..844ef2f 100644
--- a/common/util/util.go
+++ b/common/util/util.go
@@ -3,11 +3,16 @@ package util
import (
"encoding/json"
"errors"
+ "log"
"net"
+ "net/http"
+ "slices"
+ "sort"
"github.com/pion/ice/v2"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3"
+ "github.com/realclientip/realclientip-go"
)
func SerializeSessionDescription(desc *webrtc.SessionDescription) (string, error) {
@@ -97,3 +102,66 @@ func StripLocalAddresses(str string) string {
}
return string(bts)
}
+
+// Attempts to retrieve the client IP of where the HTTP request originating.
+// There is no standard way to do this since the original client IP can be included in a number of different headers,
+// depending on the proxies and load balancers between the client and the server. We attempt to check as many of these
+// headers as possible to determine a "best guess" of the client IP
+// Using this as a reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
+func GetClientIp(req *http.Request) string {
+ // We check the "Fowarded" header first, followed by the "X-Forwarded-For" header, and then use the "RemoteAddr" as
+ // a last resort. We use the leftmost address since it is the closest one to the client.
+ strat := realclientip.NewChainStrategy(
+ realclientip.Must(realclientip.NewLeftmostNonPrivateStrategy("Forwarded")),
+ realclientip.Must(realclientip.NewLeftmostNonPrivateStrategy("X-Forwarded-For")),
+ realclientip.RemoteAddrStrategy{},
+ )
+ clientIp := strat.ClientIP(req.Header, req.RemoteAddr)
+ return clientIp
+}
+
+// Returns a list of IP addresses of ICE candidates, roughly in descending order for accuracy for geolocation
+func GetCandidateAddrs(sdpStr string) []net.IP {
+ var desc sdp.SessionDescription
+ err := desc.Unmarshal([]byte(sdpStr))
+ if err != nil {
+ log.Printf("GetCandidateAddrs: failed to unmarshal SDP: %v\n", err)
+ return []net.IP{}
+ }
+
+ iceCandidates := make([]ice.Candidate, 0)
+
+ for _, m := range desc.MediaDescriptions {
+ for _, a := range m.Attributes {
+ if a.IsICECandidate() {
+ c, err := ice.UnmarshalCandidate(a.Value)
+ if err == nil {
+ iceCandidates = append(iceCandidates, c)
+ }
+ }
+ }
+ }
+
+ // ICE candidates are first sorted in asecending order of priority, to match convention of providing a custom Less
+ // function to sort
+ sort.Slice(iceCandidates, func(i, j int) bool {
+ if iceCandidates[i].Type() != iceCandidates[j].Type() {
+ // Sort by candidate type first, in the order specified in https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.2
+ // Higher priority candidate types are more efficient, which likely means they are closer to the client
+ // itself, providing a more accurate result for geolocation
+ return ice.CandidateType(iceCandidates[i].Type().Preference()) < ice.CandidateType(iceCandidates[j].Type().Preference())
+ }
+ // Break ties with the ICE candidate's priority property
+ return iceCandidates[i].Priority() < iceCandidates[j].Priority()
+ })
+ slices.Reverse(iceCandidates)
+
+ sortedIpAddr := make([]net.IP, 0)
+ for _, c := range iceCandidates {
+ ip := net.ParseIP(c.Address())
+ if ip != nil {
+ sortedIpAddr = append(sortedIpAddr, ip)
+ }
+ }
+ return sortedIpAddr
+}
diff --git a/common/util/util_test.go b/common/util/util_test.go
index 9d52f62..701a4d6 100644
--- a/common/util/util_test.go
+++ b/common/util/util_test.go
@@ -1,6 +1,8 @@
package util
import (
+ "net"
+ "net/http"
"testing"
. "github.com/smartystreets/goconvey/convey"
@@ -25,4 +27,49 @@ func TestUtil(t *testing.T) {
So(StripLocalAddresses(offer), ShouldEqual, offerStart+goodCandidate+offerEnd)
})
+
+ Convey("GetClientIp", t, func() {
+ // Should use Forwarded header
+ req1, _ := http.NewRequest("GET", "https://example.com", nil)
+ req1.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1")
+ req1.Header.Add("Forwarded", `For=fe80::abcd;By=fe80::1234, Proto=https;For=::ffff:188.0.2.128, For="[2001:db8:cafe::17]:4848", For=fc00::1`)
+ req1.RemoteAddr = "192.168.1.2:8888"
+ So(GetClientIp(req1), ShouldEqual, "188.0.2.128")
+
+ // Should use X-Forwarded-For header
+ req2, _ := http.NewRequest("GET", "https://example.com", nil)
+ req2.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1")
+ req2.RemoteAddr = "192.168.1.2:8888"
+ So(GetClientIp(req2), ShouldEqual, "1.1.1.1")
+
+ // Should use RemoteAddr
+ req3, _ := http.NewRequest("GET", "https://example.com", nil)
+ req3.RemoteAddr = "192.168.1.2:8888"
+ So(GetClientIp(req3), ShouldEqual, "192.168.1.2")
+
+ // Should return empty client IP
+ req4, _ := http.NewRequest("GET", "https://example.com", nil)
+ So(GetClientIp(req4), ShouldEqual, "")
+ })
+
+ Convey("GetCandidateAddrs", t, func() {
+ // Should prioritize type in the following order: https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.2
+ // Break ties using priority value
+ const offerStart = "v=0\r\no=- 4358805017720277108 2 IN IP4 8.8.8.8\r\ns=-\r\nt=0 0\r\na=group:BUNDLE data\r\na=msid-semantic: WMS\r\nm=application 56688 DTLS/SCTP 5000\r\nc=IN IP4 8.8.8.8\r\n"
+ const offerEnd = "a=ice-ufrag:aMAZ\r\na=ice-pwd:jcHb08Jjgrazp2dzjdrvPPvV\r\na=ice-options:trickle\r\na=fingerprint:sha-256 C8:88:EE:B9:E7:02:2E:21:37:ED:7A:D1:EB:2B:A3:15:A2:3B:5B:1C:3D:D4:D5:1F:06:CF:52:40:03:F8:DD:66\r\na=setup:actpass\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n"
+
+ const sdp = offerStart + "a=candidate:3769337065 1 udp 2122260223 8.8.8.8 56688 typ prflx\r\n" +
+ "a=candidate:3769337065 1 udp 2122260223 129.97.124.13 56688 typ relay\r\n" +
+ "a=candidate:3769337065 1 udp 2122260223 129.97.124.14 56688 typ srflx\r\n" +
+ "a=candidate:3769337065 1 udp 2122260223 129.97.124.15 56688 typ host\r\n" +
+ "a=candidate:3769337065 1 udp 2122260224 129.97.124.16 56688 typ host\r\n" + offerEnd
+
+ So(GetCandidateAddrs(sdp), ShouldEqual, []net.IP{
+ net.ParseIP("129.97.124.16"),
+ net.ParseIP("129.97.124.15"),
+ net.ParseIP("8.8.8.8"),
+ net.ParseIP("129.97.124.14"),
+ net.ParseIP("129.97.124.13"),
+ })
+ })
}
diff --git a/doc/broker-spec.txt b/doc/broker-spec.txt
index 6738cf6..3f9ce7a 100644
--- a/doc/broker-spec.txt
+++ b/doc/broker-spec.txt
@@ -88,6 +88,14 @@ Metrics data from the Snowflake broker can be retrieved by sending an HTTP GET r
A count of the number of times a client has requested a proxy using
the HTTP rendezvous method from the broker, rounded up to the nearest
multiple of 8.
+
+ "client-http-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL
+ [At most once.]
+
+ List of mappings from two-letter country codes to the number of
+ times a client has requested a proxy using the HTTP rendezvous method,
+ rounded up to the nearest multiple of 8. Each country code only appears
+ once.
"client-ampcache-count" NUM NL
[At most once.]
@@ -95,6 +103,14 @@ Metrics data from the Snowflake broker can be retrieved by sending an HTTP GET r
A count of the number of times a client has requested a proxy using
the ampcache rendezvous method from the broker, rounded up to the
nearest multiple of 8.
+
+ "client-ampcache-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL
+ [At most once.]
+
+ List of mappings from two-letter country codes to the number of
+ times a client has requested a proxy using the ampcache rendezvous
+ method, rounded up to the nearest multiple of 8. Each country code only
+ appears once.
"client-sqs-count" NUM NL
[At most once.]
@@ -103,6 +119,14 @@ Metrics data from the Snowflake broker can be retrieved by sending an HTTP GET r
the sqs rendezvous method from the broker, rounded up to the nearest
multiple of 8.
+ "client-sqs-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL
+ [At most once.]
+
+ List of mappings from two-letter country codes to the number of
+ times a client has requested a proxy using the sqs rendezvous method,
+ rounded up to the nearest multiple of 8. Each country code only appears
+ once.
+
"snowflake-ips-nat-restricted" NUM NL
[At most once.]
diff --git a/go.mod b/go.mod
index d85fb88..f23dc72 100644
--- a/go.mod
+++ b/go.mod
@@ -72,6 +72,7 @@ require (
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/quic-go v0.40.1 // indirect
+ github.com/realclientip/realclientip-go v1.0.0 // indirect
github.com/smarty/assertions v1.15.0 // indirect
github.com/templexxx/cpu v0.1.0 // indirect
github.com/templexxx/xorsimd v0.4.2 // indirect
diff --git a/go.sum b/go.sum
index d8b0241..644baa0 100644
--- a/go.sum
+++ b/go.sum
@@ -181,6 +181,8 @@ github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
+github.com/realclientip/realclientip-go v1.0.0 h1:+yPxeC0mEaJzq1BfCt2h4BxlyrvIIBzR6suDc3BEF1U=
+github.com/realclientip/realclientip-go v1.0.0/go.mod h1:CXnUdVwFRcXFJIRb/dTYqbT7ud48+Pi2pFm80bxDmcI=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=