mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
Merge branch 'standalone-broker'
This commit is contained in:
commit
36debdfdd2
8 changed files with 212 additions and 19 deletions
27
appengine/README
Normal file
27
appengine/README
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
This component runs on Google App Engine. It reflects domain-fronted
|
||||||
|
requests from a client to the Snowflake broker.
|
||||||
|
|
||||||
|
You need the Go App Engine SDK in order to deploy the app.
|
||||||
|
https://cloud.google.com/sdk/docs/#linux
|
||||||
|
After unpacking, install the app-engine-go component:
|
||||||
|
google-cloud-sdk/bin/gcloud components install app-engine-go
|
||||||
|
|
||||||
|
To test locally, run
|
||||||
|
google-cloud-sdk/bin/dev_appserver.py app.yaml
|
||||||
|
The app will be running at http://127.0.0.1:8080/.
|
||||||
|
|
||||||
|
To deploy to App Engine, first create a new project and app. You have to
|
||||||
|
think of a unique name (marked as "<appname>" in the commands). You only
|
||||||
|
have to do the "create" step once; subsequent times you can go straight
|
||||||
|
to the "deploy" step. This command will open a browser window so you can
|
||||||
|
log in to a Google account.
|
||||||
|
google-cloud-sdk/bin/gcloud projects create <appname>
|
||||||
|
google-cloud-sdk/bin/gcloud app create --project=<appname>
|
||||||
|
Then to deploy the project, run:
|
||||||
|
google-cloud-sdk/bin/gcloud app deploy --project=<appname>
|
||||||
|
|
||||||
|
To configure the Snowflake client to talk to the App Engine app, provide
|
||||||
|
"https://<appname>.appspot.com/" as the --url option.
|
||||||
|
UseBridges 1
|
||||||
|
Bridge snowflake 0.0.2.0:1
|
||||||
|
ClientTransportPlugin snowflake exec ./client -url https://<appname>.appspot.com/ -front www.google.com
|
|
@ -1,6 +1,3 @@
|
||||||
# override this with appcfg.py -A $YOUR_APP_ID
|
|
||||||
application: snowflake-reg
|
|
||||||
version: 1
|
|
||||||
runtime: go
|
runtime: go
|
||||||
api_version: go1
|
api_version: go1
|
||||||
|
|
108
appengine/reflect.go
Normal file
108
appengine/reflect.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// A web app for Google App Engine that proxies HTTP requests and responses to
|
||||||
|
// the Snowflake broker.
|
||||||
|
package reflect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"appengine"
|
||||||
|
"appengine/urlfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
forwardURL = "https://snowflake-broker.bamsoftware.com/"
|
||||||
|
// A timeout of 0 means to use the App Engine default (5 seconds).
|
||||||
|
urlFetchTimeout = 20 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var context appengine.Context
|
||||||
|
|
||||||
|
// Join two URL paths.
|
||||||
|
func pathJoin(a, b string) string {
|
||||||
|
if len(a) > 0 && a[len(a)-1] == '/' {
|
||||||
|
a = a[:len(a)-1]
|
||||||
|
}
|
||||||
|
if len(b) == 0 || b[0] != '/' {
|
||||||
|
b = "/" + b
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// We reflect only a whitelisted set of header fields. Otherwise, we may copy
|
||||||
|
// headers like Transfer-Encoding that interfere with App Engine's own
|
||||||
|
// hop-by-hop headers.
|
||||||
|
var reflectedHeaderFields = []string{
|
||||||
|
"Content-Type",
|
||||||
|
"X-Session-Id",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a copy of r, with the URL being changed to be relative to forwardURL,
|
||||||
|
// and including only the headers in reflectedHeaderFields.
|
||||||
|
func copyRequest(r *http.Request) (*http.Request, error) {
|
||||||
|
u, err := url.Parse(forwardURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Append the requested path to the path in forwardURL, so that
|
||||||
|
// forwardURL can be something like "https://example.com/reflect".
|
||||||
|
u.Path = pathJoin(u.Path, r.URL.Path)
|
||||||
|
c, err := http.NewRequest(r.Method, u.String(), r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, key := range reflectedHeaderFields {
|
||||||
|
values, ok := r.Header[key]
|
||||||
|
if ok {
|
||||||
|
for _, value := range values {
|
||||||
|
c.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
context = appengine.NewContext(r)
|
||||||
|
fr, err := copyRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
context.Errorf("copyRequest: %s", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Use urlfetch.Transport directly instead of urlfetch.Client because we
|
||||||
|
// want only a single HTTP transaction, not following redirects.
|
||||||
|
transport := urlfetch.Transport{
|
||||||
|
Context: context,
|
||||||
|
// Despite the name, Transport.Deadline is really a timeout and
|
||||||
|
// not an absolute deadline as used in the net package. In
|
||||||
|
// other words it is a time.Duration, not a time.Time.
|
||||||
|
Deadline: urlFetchTimeout,
|
||||||
|
}
|
||||||
|
resp, err := transport.RoundTrip(fr)
|
||||||
|
if err != nil {
|
||||||
|
context.Errorf("RoundTrip: %s", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
for _, key := range reflectedHeaderFields {
|
||||||
|
values, ok := resp.Header[key]
|
||||||
|
if ok {
|
||||||
|
for _, value := range values {
|
||||||
|
w.Header().Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
n, err := io.Copy(w, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
context.Errorf("io.Copy after %d bytes: %s", n, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
}
|
|
@ -22,18 +22,27 @@ The Broker expects:
|
||||||
|
|
||||||
### Running your own
|
### Running your own
|
||||||
|
|
||||||
You can run your own Broker on either localhost or appengine.
|
The server uses TLS by default.
|
||||||
(Other CDNs will be supported soon.)
|
There is a `--disable-tls` option for testing purposes,
|
||||||
|
but you should use TLS in production.
|
||||||
|
|
||||||
|
The server automatically fetches certificates
|
||||||
|
from [Let's Encrypt](https://en.wikipedia.org/wiki/Let's_Encrypt) as needed.
|
||||||
|
Use the `--acme-hostnames` option to tell the server
|
||||||
|
what hostnames it may request certificates for.
|
||||||
|
You can optionally provide a contact email address,
|
||||||
|
using the `--acme-email` option,
|
||||||
|
so that Let's Encrypt can inform you of any problems.
|
||||||
|
|
||||||
To run on localhost, run `dev_appserver.py` or equivalent from this
|
In order to fetch certificates automatically,
|
||||||
directory. (on arch, I use the wrapper script `dev_appserver-go`)
|
the server needs to be listening on port 443 (the default).
|
||||||
|
On Linux, you can use the `setcap` program,
|
||||||
|
part of libcap2, to enable the broker to bind to low-numbered ports
|
||||||
|
without having to run as root:
|
||||||
|
```
|
||||||
|
setcap 'cap_net_bind_service=+ep' /usr/local/bin/broker
|
||||||
|
```
|
||||||
|
You can control the listening port with the --addr option.
|
||||||
|
|
||||||
To run on appengine, you can spin up your own instance with an arbitrary
|
You'll need to provide the URL of the custom broker
|
||||||
name, and use `appcfg.py`.
|
|
||||||
|
|
||||||
In both cases, you'll need to provide the URL of the custom broker
|
|
||||||
to the client plugin using the `--url $URL` flag.
|
to the client plugin using the `--url $URL` flag.
|
||||||
|
|
||||||
See more detailed appengine instructions
|
|
||||||
[here](https://cloud.google.com/appengine/docs/go/).
|
|
||||||
|
|
|
@ -3,16 +3,21 @@ Broker acts as the HTTP signaling channel.
|
||||||
It matches clients and snowflake proxies by passing corresponding
|
It matches clients and snowflake proxies by passing corresponding
|
||||||
SessionDescriptions in order to negotiate a WebRTC connection.
|
SessionDescriptions in order to negotiate a WebRTC connection.
|
||||||
*/
|
*/
|
||||||
package snowflake_broker
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
|
"crypto/tls"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -217,7 +222,27 @@ func robotsTxtHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("User-agent: *\nDisallow:\n"))
|
w.Write([]byte("User-agent: *\nDisallow:\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func ipHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
remoteAddr := r.RemoteAddr
|
||||||
|
if net.ParseIP(remoteAddr).To4() == nil {
|
||||||
|
remoteAddr = "[" + remoteAddr + "]"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.Write([]byte(remoteAddr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var acmeEmail string
|
||||||
|
var acmeHostnamesCommas string
|
||||||
|
var addr string
|
||||||
|
var disableTLS bool
|
||||||
|
|
||||||
|
flag.StringVar(&acmeEmail, "acme-email", "", "optional contact email for Let's Encrypt notifications")
|
||||||
|
flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for TLS certificate")
|
||||||
|
flag.StringVar(&addr, "addr", ":443", "address to listen on")
|
||||||
|
flag.BoolVar(&disableTLS, "disable-tls", false, "don't use HTTPS")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
log.SetFlags(log.LstdFlags | log.LUTC)
|
log.SetFlags(log.LstdFlags | log.LUTC)
|
||||||
|
|
||||||
ctx := NewBrokerContext()
|
ctx := NewBrokerContext()
|
||||||
|
@ -230,4 +255,31 @@ func init() {
|
||||||
http.Handle("/client", SnowflakeHandler{ctx, clientOffers})
|
http.Handle("/client", SnowflakeHandler{ctx, clientOffers})
|
||||||
http.Handle("/answer", SnowflakeHandler{ctx, proxyAnswers})
|
http.Handle("/answer", SnowflakeHandler{ctx, proxyAnswers})
|
||||||
http.Handle("/debug", SnowflakeHandler{ctx, debugHandler})
|
http.Handle("/debug", SnowflakeHandler{ctx, debugHandler})
|
||||||
|
|
||||||
|
var err error
|
||||||
|
server := http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if acmeHostnamesCommas != "" {
|
||||||
|
acmeHostnames := strings.Split(acmeHostnamesCommas, ",")
|
||||||
|
log.Printf("ACME hostnames: %q", acmeHostnames)
|
||||||
|
|
||||||
|
certManager := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(acmeHostnames...),
|
||||||
|
Email: acmeEmail,
|
||||||
|
}
|
||||||
|
|
||||||
|
server.TLSConfig = &tls.Config{GetCertificate: certManager.GetCertificate}
|
||||||
|
err = server.ListenAndServeTLS("", "")
|
||||||
|
} else if disableTLS {
|
||||||
|
err = server.ListenAndServe()
|
||||||
|
} else {
|
||||||
|
log.Fatal("the --acme-hostnames or --disable-tls option is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package snowflake_broker
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "golang.org/x/net/internal/timeseries"
|
// "golang.org/x/net/internal/timeseries"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package snowflake_broker
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Keeping track of pending available snowflake proxies.
|
Keeping track of pending available snowflake proxies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package snowflake_broker
|
package main
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The Snowflake struct contains a single interaction
|
The Snowflake struct contains a single interaction
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue