[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [snowflake/main] Implement ampCacheRendezvous.
commit 5adb99402861569a0c3d0e46299d48a583f48725
Author: David Fifield <david@xxxxxxxxxxxxxxx>
Date: Sun Jul 18 14:57:45 2021 -0600
Implement ampCacheRendezvous.
---
client/lib/rendezvous_ampcache.go | 56 ++++++++++++++--
client/lib/rendezvous_test.go | 132 +++++++++++++++++++++++++++++++++++++-
2 files changed, 181 insertions(+), 7 deletions(-)
diff --git a/client/lib/rendezvous_ampcache.go b/client/lib/rendezvous_ampcache.go
index 89745f4..4856893 100644
--- a/client/lib/rendezvous_ampcache.go
+++ b/client/lib/rendezvous_ampcache.go
@@ -1,11 +1,14 @@
package lib
import (
- "bytes"
"errors"
+ "io"
+ "io/ioutil"
"log"
"net/http"
"net/url"
+
+ "git.torproject.org/pluggable-transports/snowflake.git/common/amp"
)
// ampCacheRendezvous is a rendezvousMethod that communicates with the
@@ -49,9 +52,22 @@ func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) {
log.Println("AMP cache URL:", r.cacheURL)
log.Println("Front domain:", r.front)
- // Suffix the path with the broker's client registration handler.
- reqURL := r.brokerURL.ResolveReference(&url.URL{Path: "client"})
- req, err := http.NewRequest("POST", reqURL.String(), bytes.NewReader(encPollReq))
+ // We cannot POST a body through an AMP cache, so instead we GET and
+ // encode the client poll request message into the URL.
+ reqURL := r.brokerURL.ResolveReference(&url.URL{
+ Path: "amp/client/" + amp.EncodePath(encPollReq),
+ })
+
+ if r.cacheURL != nil {
+ // Rewrite reqURL to its AMP cache version.
+ var err error
+ reqURL, err = amp.CacheURL(reqURL, r.cacheURL, "c")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ req, err := http.NewRequest("GET", reqURL.String(), nil)
if err != nil {
return nil, err
}
@@ -71,8 +87,38 @@ func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) {
log.Printf("AMP cache rendezvous response: %s", resp.Status)
if resp.StatusCode != http.StatusOK {
+ // A non-200 status indicates an error:
+ // * If the broker returns a page with invalid AMP, then the AMP
+ // cache returns a redirect that would bypass the cache.
+ // * If the broker returns a 5xx status, the AMP cache
+ // translates it to a 404.
+ // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#redirect-%26-error-handling
+ return nil, errors.New(BrokerErrorUnexpected)
+ }
+ if _, err := resp.Location(); err == nil {
+ // The Google AMP Cache may return a "silent redirect" with
+ // status 200, a Location header set, and a JavaScript redirect
+ // in the body. The redirect points directly at the origin
+ // server for the request (bypassing the AMP cache). We do not
+ // follow redirects nor execute JavaScript, but in any case we
+ // cannot extract information from this response and can only
+ // treat it as an error.
return nil, errors.New(BrokerErrorUnexpected)
}
- return limitedRead(resp.Body, readLimit)
+ lr := io.LimitReader(resp.Body, readLimit+1)
+ dec, err := amp.NewArmorDecoder(lr)
+ if err != nil {
+ return nil, err
+ }
+ encPollResp, err := ioutil.ReadAll(dec)
+ if err != nil {
+ return nil, err
+ }
+ if lr.(*io.LimitedReader).N == 0 {
+ // We hit readLimit while decoding AMP armor, that's an error.
+ return nil, io.ErrUnexpectedEOF
+ }
+
+ return encPollResp, err
}
diff --git a/client/lib/rendezvous_test.go b/client/lib/rendezvous_test.go
index c263e37..6a1a071 100644
--- a/client/lib/rendezvous_test.go
+++ b/client/lib/rendezvous_test.go
@@ -9,6 +9,7 @@ import (
"net/http"
"testing"
+ "git.torproject.org/pluggable-transports/snowflake.git/common/amp"
"git.torproject.org/pluggable-transports/snowflake.git/common/messages"
"git.torproject.org/pluggable-transports/snowflake.git/common/nat"
. "github.com/smartystreets/goconvey/convey"
@@ -64,6 +65,8 @@ func makeEncPollResp(answer, errorStr string) []byte {
return encPollResp
}
+var fakeEncPollReq = makeEncPollReq(`{"type":"offer","sdp":"test"}`)
+
func TestHTTPRendezvous(t *testing.T) {
Convey("HTTP rendezvous", t, func() {
Convey("Construct httpRendezvous with no front domain", func() {
@@ -86,8 +89,6 @@ func TestHTTPRendezvous(t *testing.T) {
So(rend.transport, ShouldEqual, transport)
})
- fakeEncPollReq := makeEncPollReq(`{"type":"offer","sdp":"test"}`)
-
Convey("httpRendezvous.Exchange responds with answer", func() {
fakeEncPollResp := makeEncPollResp(
`{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`,
@@ -143,3 +144,130 @@ func TestHTTPRendezvous(t *testing.T) {
})
})
}
+
+func ampArmorEncode(p []byte) []byte {
+ var buf bytes.Buffer
+ enc, err := amp.NewArmorEncoder(&buf)
+ if err != nil {
+ panic(err)
+ }
+ _, err = enc.Write(p)
+ if err != nil {
+ panic(err)
+ }
+ err = enc.Close()
+ if err != nil {
+ panic(err)
+ }
+ return buf.Bytes()
+}
+
+func TestAMPCacheRendezvous(t *testing.T) {
+ Convey("AMP cache rendezvous", t, func() {
+ Convey("Construct ampCacheRendezvous with no cache and no front domain", func() {
+ transport := &mockTransport{http.StatusOK, []byte{}}
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "", transport)
+ So(err, ShouldBeNil)
+ So(rend.brokerURL, ShouldNotBeNil)
+ So(rend.brokerURL.String(), ShouldResemble, "http://test.broker")
+ So(rend.cacheURL, ShouldBeNil)
+ So(rend.front, ShouldResemble, "")
+ So(rend.transport, ShouldEqual, transport)
+ })
+
+ Convey("Construct ampCacheRendezvous with cache and no front domain", func() {
+ transport := &mockTransport{http.StatusOK, []byte{}}
+ rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", "", transport)
+ So(err, ShouldBeNil)
+ So(rend.brokerURL, ShouldNotBeNil)
+ So(rend.brokerURL.String(), ShouldResemble, "http://test.broker")
+ So(rend.cacheURL, ShouldNotBeNil)
+ So(rend.cacheURL.String(), ShouldResemble, "https://amp.cache/")
+ So(rend.front, ShouldResemble, "")
+ So(rend.transport, ShouldEqual, transport)
+ })
+
+ Convey("Construct ampCacheRendezvous with no cache and front domain", func() {
+ transport := &mockTransport{http.StatusOK, []byte{}}
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "front", transport)
+ So(err, ShouldBeNil)
+ So(rend.brokerURL, ShouldNotBeNil)
+ So(rend.brokerURL.String(), ShouldResemble, "http://test.broker")
+ So(rend.cacheURL, ShouldBeNil)
+ So(rend.front, ShouldResemble, "front")
+ So(rend.transport, ShouldEqual, transport)
+ })
+
+ Convey("Construct ampCacheRendezvous with cache and front domain", func() {
+ transport := &mockTransport{http.StatusOK, []byte{}}
+ rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", "front", transport)
+ So(err, ShouldBeNil)
+ So(rend.brokerURL, ShouldNotBeNil)
+ So(rend.brokerURL.String(), ShouldResemble, "http://test.broker")
+ So(rend.cacheURL, ShouldNotBeNil)
+ So(rend.cacheURL.String(), ShouldResemble, "https://amp.cache/")
+ So(rend.front, ShouldResemble, "front")
+ So(rend.transport, ShouldEqual, transport)
+ })
+
+ Convey("ampCacheRendezvous.Exchange responds with answer", func() {
+ fakeEncPollResp := makeEncPollResp(
+ `{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`,
+ "",
+ )
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "",
+ &mockTransport{http.StatusOK, ampArmorEncode(fakeEncPollResp)})
+ So(err, ShouldBeNil)
+ answer, err := rend.Exchange(fakeEncPollReq)
+ So(err, ShouldBeNil)
+ So(answer, ShouldResemble, fakeEncPollResp)
+ })
+
+ Convey("ampCacheRendezvous.Exchange responds with no answer", func() {
+ fakeEncPollResp := makeEncPollResp(
+ "",
+ `{"error": "no snowflake proxies currently available"}`,
+ )
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "",
+ &mockTransport{http.StatusOK, ampArmorEncode(fakeEncPollResp)})
+ So(err, ShouldBeNil)
+ answer, err := rend.Exchange(fakeEncPollReq)
+ So(err, ShouldBeNil)
+ So(answer, ShouldResemble, fakeEncPollResp)
+ })
+
+ Convey("ampCacheRendezvous.Exchange fails with unexpected HTTP status code", func() {
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "",
+ &mockTransport{http.StatusInternalServerError, []byte{}})
+ So(err, ShouldBeNil)
+ answer, err := rend.Exchange(fakeEncPollReq)
+ So(err, ShouldNotBeNil)
+ So(answer, ShouldBeNil)
+ So(err.Error(), ShouldResemble, BrokerErrorUnexpected)
+ })
+
+ Convey("ampCacheRendezvous.Exchange fails with error", func() {
+ transportErr := errors.New("error")
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "",
+ &errorTransport{err: transportErr})
+ So(err, ShouldBeNil)
+ answer, err := rend.Exchange(fakeEncPollReq)
+ So(err, ShouldEqual, transportErr)
+ So(answer, ShouldBeNil)
+ })
+
+ Convey("ampCacheRendezvous.Exchange fails with large read", func() {
+ // readLimit should apply to the raw HTTP body, not the
+ // encoded bytes. Encode readLimit bytesâ??the encoded
+ // size will be largerâ??and try to read the body. It
+ // should fail.
+ rend, err := newAMPCacheRendezvous("http://test.broker", "", "",
+ &mockTransport{http.StatusOK, ampArmorEncode(make([]byte, readLimit))})
+ So(err, ShouldBeNil)
+ _, err = rend.Exchange(fakeEncPollReq)
+ // We may get io.ErrUnexpectedEOF here, or something
+ // like "missing </pre> tag".
+ So(err, ShouldNotBeNil)
+ })
+ })
+}
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits