Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
David Fifield
Snowflake
Commits
a2af6fb4
Commit
a2af6fb4
authored
Jul 18, 2021
by
David Fifield
Browse files
Implement ampCacheRendezvous.
parent
06ca0e86
Changes
2
Hide whitespace changes
Inline
Side-by-side
client/lib/rendezvous_ampcache.go
View file @
a2af6fb4
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
}
client/lib/rendezvous_test.go
View file @
a2af6fb4
...
...
@@ -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
)
})
})
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment