This is an automated email from the git hooks/post-receive script.
arlo pushed a change to branch main in repository pluggable-transports/snowflake.
from b73add1 Make the proxy type configurable for users of the library new 281d917 Stop storing version in ClientPollRequest new b563141 Forward bridge fingerprint new fa2f682 Add some test cases for client poll requests
The 3 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: broker/broker.go | 7 +++--- broker/http.go | 5 ++--- broker/ipc.go | 40 +++++++++------------------------- client/lib/rendezvous.go | 8 ++++--- client/lib/rendezvous_test.go | 5 ++--- client/lib/snowflake.go | 3 +++ client/snowflake.go | 3 +++ client/torrc | 2 +- common/messages/client.go | 35 ++++++++++++++++++++---------- common/messages/messages_test.go | 46 ++++++++++++++++++++++++++++++++-------- 10 files changed, 91 insertions(+), 63 deletions(-)
This is an automated email from the git hooks/post-receive script.
arlo pushed a commit to branch main in repository pluggable-transports/snowflake.
commit 281d917bebe19642914ec94d7d5ef911f03c8f57 Author: Arlo Breault arlolra@gmail.com AuthorDate: Wed Mar 16 20:26:40 2022 -0400
Stop storing version in ClientPollRequest
This continues to asserts the known version while decoding. The client will only ever generate the latest version while encoding and if the response needs to change, the impetus will be a new feature, set in the deserialized request, which can be used as a distinguisher. --- broker/http.go | 5 ++--- broker/ipc.go | 39 +++++++++------------------------------ client/lib/rendezvous.go | 5 ++--- client/lib/rendezvous_test.go | 5 ++--- common/messages/client.go | 16 +++++----------- common/messages/messages_test.go | 5 ++--- 6 files changed, 22 insertions(+), 53 deletions(-)
diff --git a/broker/http.go b/broker/http.go index 7acc465..3b0ba1f 100644 --- a/broker/http.go +++ b/broker/http.go @@ -146,9 +146,8 @@ func clientOffers(i *IPC, w http.ResponseWriter, r *http.Request) { if len(body) > 0 && body[0] == '{' { isLegacy = true req := messages.ClientPollRequest{ - Offer: string(body), - NAT: r.Header.Get("Snowflake-NAT-Type"), - Version: messages.ClientVersion1_0, + Offer: string(body), + NAT: r.Header.Get("Snowflake-NAT-Type"), } body, err = req.EncodeClientPollRequest() if err != nil { diff --git a/broker/ipc.go b/broker/ipc.go index 9b47b90..5cc595b 100644 --- a/broker/ipc.go +++ b/broker/ipc.go @@ -129,15 +129,9 @@ func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error { return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) }
- var offer *ClientOffer - switch req.Version { - case messages.ClientVersion1_0: - offer = &ClientOffer{ - natType: req.NAT, - sdp: []byte(req.Offer), - } - default: - panic("unknown version") + offer := &ClientOffer{ + natType: req.NAT, + sdp: []byte(req.Offer), }
// Only hand out known restricted snowflakes to unrestricted clients @@ -162,13 +156,8 @@ func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error { i.ctx.metrics.clientRestrictedDeniedCount++ } i.ctx.metrics.lock.Unlock() - switch req.Version { - case messages.ClientVersion1_0: - resp := &messages.ClientPollResponse{Error: messages.StrNoProxies} - return sendClientResponse(resp, response) - default: - panic("unknown version") - } + resp := &messages.ClientPollResponse{Error: messages.StrNoProxies} + return sendClientResponse(resp, response) }
// Otherwise, find the most available snowflake proxy, and pass the offer to it. @@ -185,24 +174,14 @@ func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error { i.ctx.metrics.clientProxyMatchCount++ i.ctx.metrics.promMetrics.ClientPollTotal.With(prometheus.Labels{"nat": offer.natType, "status": "matched"}).Inc() i.ctx.metrics.lock.Unlock() - switch req.Version { - case messages.ClientVersion1_0: - resp := &messages.ClientPollResponse{Answer: answer} - err = sendClientResponse(resp, response) - default: - panic("unknown version") - } + resp := &messages.ClientPollResponse{Answer: answer} + err = sendClientResponse(resp, response) // Initial tracking of elapsed time. i.ctx.metrics.clientRoundtripEstimate = time.Since(startTime) / time.Millisecond case <-time.After(time.Second * ClientTimeout): log.Println("Client: Timed out.") - switch req.Version { - case messages.ClientVersion1_0: - resp := &messages.ClientPollResponse{Error: messages.StrTimedOut} - err = sendClientResponse(resp, response) - default: - panic("unknown version") - } + resp := &messages.ClientPollResponse{Error: messages.StrTimedOut} + err = sendClientResponse(resp, response) }
i.ctx.snowflakeLock.Lock() diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index 73c62ed..d908b77 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -116,9 +116,8 @@ func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) ( // Encode the client poll request. bc.lock.Lock() req := &messages.ClientPollRequest{ - Offer: offerSDP, - NAT: bc.natType, - Version: messages.ClientVersion1_0, + Offer: offerSDP, + NAT: bc.natType, } encReq, err := req.EncodeClientPollRequest() bc.lock.Unlock() diff --git a/client/lib/rendezvous_test.go b/client/lib/rendezvous_test.go index a233e7d..21b9f57 100644 --- a/client/lib/rendezvous_test.go +++ b/client/lib/rendezvous_test.go @@ -43,9 +43,8 @@ func (t errorTransport) RoundTrip(req *http.Request) (*http.Response, error) { // offer. func makeEncPollReq(offer string) []byte { encPollReq, err := (&messages.ClientPollRequest{ - Offer: offer, - NAT: nat.NATUnknown, - Version: messages.ClientVersion1_0, + Offer: offer, + NAT: nat.NATUnknown, }).EncodeClientPollRequest() if err != nil { panic(err) diff --git a/common/messages/client.go b/common/messages/client.go index 2a35594..b6155f7 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -11,7 +11,7 @@ import ( "git.torproject.org/pluggable-transports/snowflake.git/v2/common/nat" )
-const ClientVersion1_0 = "1.0" +const ClientVersion = "1.0"
/* Client--Broker protocol v1.x specification:
@@ -50,21 +50,17 @@ for the error. */
type ClientPollRequest struct { - Offer string `json:"offer"` - NAT string `json:"nat"` - Version string `json:"-"` + Offer string `json:"offer"` + NAT string `json:"nat"` }
// Encodes a poll message from a snowflake client func (req *ClientPollRequest) EncodeClientPollRequest() ([]byte, error) { - if req.Version != ClientVersion1_0 { - return nil, fmt.Errorf("unsupported message version") - } body, err := json.Marshal(req) if err != nil { return nil, err } - return append([]byte(req.Version+"\n"), body...), nil + return append([]byte(ClientVersion+"\n"), body...), nil }
// Decodes a poll message from a snowflake client @@ -78,9 +74,7 @@ func DecodeClientPollRequest(data []byte) (*ClientPollRequest, error) {
var message ClientPollRequest
- if string(parts[0]) == ClientVersion1_0 { - message.Version = ClientVersion1_0 - } else { + if string(parts[0]) != ClientVersion { return nil, fmt.Errorf("unsupported message version") }
diff --git a/common/messages/messages_test.go b/common/messages/messages_test.go index e0aa2a8..ae0d4f9 100644 --- a/common/messages/messages_test.go +++ b/common/messages/messages_test.go @@ -327,9 +327,8 @@ func TestDecodeClientPollRequest(t *testing.T) { func TestEncodeClientPollRequests(t *testing.T) { Convey("Context", t, func() { req1 := &ClientPollRequest{ - NAT: "unknown", - Offer: "fake", - Version: ClientVersion1_0, + NAT: "unknown", + Offer: "fake", } b, err := req1.EncodeClientPollRequest() So(err, ShouldEqual, nil)
This is an automated email from the git hooks/post-receive script.
arlo pushed a commit to branch main in repository pluggable-transports/snowflake.
commit b563141c6abba128386bc1ad18122d5e13e09789 Author: Arlo Breault arlolra@gmail.com AuthorDate: Tue Mar 8 16:27:52 2022 -0500
Forward bridge fingerprint
gitlab 28651 --- broker/broker.go | 7 ++++--- broker/ipc.go | 5 +++-- client/lib/rendezvous.go | 7 +++++-- client/lib/snowflake.go | 3 +++ client/snowflake.go | 3 +++ client/torrc | 2 +- common/messages/client.go | 25 ++++++++++++++++++++++--- 7 files changed, 41 insertions(+), 11 deletions(-)
diff --git a/broker/broker.go b/broker/broker.go index 7a29265..6e85fbd 100644 --- a/broker/broker.go +++ b/broker/broker.go @@ -139,10 +139,11 @@ func (ctx *BrokerContext) AddSnowflake(id string, proxyType string, natType stri return snowflake }
-// Client offer contains an SDP and the NAT type of the client +// Client offer contains an SDP, bridge fingerprint and the NAT type of the client type ClientOffer struct { - natType string - sdp []byte + natType string + sdp []byte + fingerprint string }
func main() { diff --git a/broker/ipc.go b/broker/ipc.go index 5cc595b..2ef4ccd 100644 --- a/broker/ipc.go +++ b/broker/ipc.go @@ -130,8 +130,9 @@ func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error { }
offer := &ClientOffer{ - natType: req.NAT, - sdp: []byte(req.Offer), + natType: req.NAT, + sdp: []byte(req.Offer), + fingerprint: req.Fingerprint, }
// Only hand out known restricted snowflakes to unrestricted clients diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index d908b77..38e4620 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -43,6 +43,7 @@ type BrokerChannel struct { keepLocalAddresses bool natType string lock sync.Mutex + BridgeFingerprint string }
// We make a copy of DefaultTransport because we want the default Dial @@ -92,6 +93,7 @@ func newBrokerChannelFromConfig(config ClientConfig) (*BrokerChannel, error) { Rendezvous: rendezvous, keepLocalAddresses: config.KeepLocalAddresses, natType: nat.NATUnknown, + BridgeFingerprint: config.BridgeFingerprint, }, nil }
@@ -116,8 +118,9 @@ func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) ( // Encode the client poll request. bc.lock.Lock() req := &messages.ClientPollRequest{ - Offer: offerSDP, - NAT: bc.natType, + Offer: offerSDP, + NAT: bc.natType, + Fingerprint: bc.BridgeFingerprint, } encReq, err := req.EncodeClientPollRequest() bc.lock.Unlock() diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 1b236a6..dd78c12 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -103,6 +103,9 @@ type ClientConfig struct { // UTLSRemoveSNI is the flag to control whether SNI should be removed from Client Hello // when uTLS is used. UTLSRemoveSNI bool + // BridgeFingerprint is the fingerprint of the bridge that the client will eventually + // connect to, as specified in the Bridge line of the torrc. + BridgeFingerprint string }
// NewSnowflakeClient creates a new Snowflake transport client that can spawn multiple diff --git a/client/snowflake.go b/client/snowflake.go index 5856750..33834ad 100644 --- a/client/snowflake.go +++ b/client/snowflake.go @@ -95,6 +95,9 @@ func socksAcceptLoop(ln *pt.SocksListener, config sf.ClientConfig, shutdown chan if arg, ok := conn.Req.Args.Get("utls-imitate"); ok { config.UTLSClientID = arg } + if arg, ok := conn.Req.Args.Get("fingerprint"); ok { + config.BridgeFingerprint = arg + } transport, err := sf.NewSnowflakeClient(config) if err != nil { conn.Reject() diff --git a/client/torrc b/client/torrc index 039653f..aee4df1 100644 --- a/client/torrc +++ b/client/torrc @@ -3,6 +3,6 @@ DataDirectory datadir
ClientTransportPlugin snowflake exec ./client -log snowflake.log
-Bridge snowflake 192.0.2.3:1 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 +Bridge snowflake 192.0.2.3:1 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478 [...]
SocksPort auto diff --git a/common/messages/client.go b/common/messages/client.go index b6155f7..4d435ab 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -29,10 +29,13 @@ each encoded in JSON format { offer: <sdp offer> [nat: (unknown|restricted|unrestricted)] + [fingerprint: <fingerprint string>] }
The NAT field is optional, and if it is missing a -value of "unknown" will be assumed. +value of "unknown" will be assumed. The fingerprint +is also optional and, if absent, will be assigned the +fingerprint of the default bridge.
== ClientPollResponse == <poll response> := @@ -49,13 +52,25 @@ for the error.
*/
+// The bridge fingerprint to assume, for client poll requests that do not +// specify a fingerprint. Before #28651, there was only one bridge with one +// fingerprint, which all clients expected to be connected to implicitly. +// If a client is old enough that it does not specify a fingerprint, this is +// the fingerprint it expects. Clients that do set a fingerprint in the +// SOCKS params will also be assumed to want to connect to the default bridge. +const defaultBridgeFingerprint = "2B280B23E1107BB62ABFC40DDCC8824814F80A72" + type ClientPollRequest struct { - Offer string `json:"offer"` - NAT string `json:"nat"` + Offer string `json:"offer"` + NAT string `json:"nat"` + Fingerprint string `json:"fingerprint"` }
// Encodes a poll message from a snowflake client func (req *ClientPollRequest) EncodeClientPollRequest() ([]byte, error) { + if req.Fingerprint == "" { + req.Fingerprint = defaultBridgeFingerprint + } body, err := json.Marshal(req) if err != nil { return nil, err @@ -87,6 +102,10 @@ func DecodeClientPollRequest(data []byte) (*ClientPollRequest, error) { return nil, fmt.Errorf("no supplied offer") }
+ if message.Fingerprint == "" { + message.Fingerprint = defaultBridgeFingerprint + } + switch message.NAT { case "": message.NAT = nat.NATUnknown
This is an automated email from the git hooks/post-receive script.
arlo pushed a commit to branch main in repository pluggable-transports/snowflake.
commit fa2f6824d924f3317c82bc740130f354f6a1780c Author: Arlo Breault arlolra@gmail.com AuthorDate: Thu Mar 17 11:23:49 2022 -0400
Add some test cases for client poll requests --- common/messages/messages_test.go | 45 +++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-)
diff --git a/common/messages/messages_test.go b/common/messages/messages_test.go index ae0d4f9..5365aa8 100644 --- a/common/messages/messages_test.go +++ b/common/messages/messages_test.go @@ -326,15 +326,44 @@ func TestDecodeClientPollRequest(t *testing.T) {
func TestEncodeClientPollRequests(t *testing.T) { Convey("Context", t, func() { - req1 := &ClientPollRequest{ - NAT: "unknown", - Offer: "fake", + for i, test := range []struct { + natType string + offer string + fingerprint string + err error + }{ + { + "unknown", + "fake", + "", + nil, + }, + { + "unknown", + "fake", + defaultBridgeFingerprint, + nil, + }, + } { + req1 := &ClientPollRequest{ + NAT: test.natType, + Offer: test.offer, + Fingerprint: test.fingerprint, + } + b, err := req1.EncodeClientPollRequest() + So(err, ShouldEqual, nil) + req2, err := DecodeClientPollRequest(b) + So(err, ShouldHaveSameTypeAs, test.err) + if test.err == nil { + So(req2.Offer, ShouldEqual, req1.Offer) + So(req2.NAT, ShouldEqual, req1.NAT) + fingerprint := test.fingerprint + if i == 0 { + fingerprint = defaultBridgeFingerprint + } + So(req2.Fingerprint, ShouldEqual, fingerprint) + } } - b, err := req1.EncodeClientPollRequest() - So(err, ShouldEqual, nil) - req2, err := DecodeClientPollRequest(b) - So(err, ShouldEqual, nil) - So(req2, ShouldResemble, req1) }) }
tor-commits@lists.torproject.org