commit f368c871095dae3aa990e7d46a1d6612af9909b9
Author: Cecylia Bocovich <[email protected]>
Date:   Tue Oct 13 17:18:50 2020 -0400

    Add a remote service to test NAT compatability
    
    Add a remote probetest service that will allow proxies to test their
    compatability with symmetric NATs.
---
 .gitignore             |   1 +
 probetest/probetest.go | 226 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 227 insertions(+)

diff --git a/.gitignore b/.gitignore
index 9f36c7c..002f95e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,6 @@ broker/broker
 client/client
 server/server
 proxy/proxy
+probetest/probetest
 snowflake.log
 ignore/
diff --git a/probetest/probetest.go b/probetest/probetest.go
new file mode 100644
index 0000000..af08e32
--- /dev/null
+++ b/probetest/probetest.go
@@ -0,0 +1,226 @@
+/*
+Probe test server to check the reachability of Snowflake proxies from
+clients with symmetric NATs.
+
+The probe server receives an offer from a proxy, returns an answer, and then
+attempts to establish a datachannel connection to that proxy. The proxy will
+self-determine whether the connection opened successfully.
+*/
+package main
+
+import (
+       "crypto/tls"
+       "flag"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "log"
+       "net/http"
+       "os"
+       "strings"
+       "time"
+
+       "git.torproject.org/pluggable-transports/snowflake.git/common/messages"
+       "git.torproject.org/pluggable-transports/snowflake.git/common/safelog"
+       "git.torproject.org/pluggable-transports/snowflake.git/common/util"
+
+       "github.com/pion/webrtc/v2"
+       "golang.org/x/crypto/acme/autocert"
+)
+
+const (
+       readLimit          = 100000                         //Maximum number of 
bytes to be read from an HTTP request
+       dataChannelTimeout = 20 * time.Second               //time after which 
we assume proxy data channel will not open
+       stunUrl            = "stun:stun.l.google.com:19302" //default STUN URL
+)
+
+// Create a PeerConnection from an SDP offer. Blocks until the gathering of ICE
+// candidates is complete and the answer is available in LocalDescription.
+func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription,
+       dataChan chan struct{}) (*webrtc.PeerConnection, error) {
+
+       config := webrtc.Configuration{
+               ICEServers: []webrtc.ICEServer{
+                       {
+                               URLs: []string{stunUrl},
+                       },
+               },
+       }
+       pc, err := webrtc.NewPeerConnection(config)
+       if err != nil {
+               return nil, fmt.Errorf("accept: NewPeerConnection: %s", err)
+       }
+       pc.OnDataChannel(func(dc *webrtc.DataChannel) {
+               close(dataChan)
+       })
+
+       err = pc.SetRemoteDescription(*sdp)
+       if err != nil {
+               if inerr := pc.Close(); inerr != nil {
+                       log.Printf("unable to call pc.Close after 
pc.SetRemoteDescription with error: %v", inerr)
+               }
+               return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err)
+       }
+
+       answer, err := pc.CreateAnswer(nil)
+       if err != nil {
+               if inerr := pc.Close(); inerr != nil {
+                       log.Printf("ICE gathering has generated an error when 
calling pc.Close: %v", inerr)
+               }
+               return nil, err
+       }
+
+       err = pc.SetLocalDescription(answer)
+       if err != nil {
+               if err = pc.Close(); err != nil {
+                       log.Printf("pc.Close after setting local description 
returned : %v", err)
+               }
+               return nil, err
+       }
+
+       return pc, nil
+}
+
+func probeHandler(w http.ResponseWriter, r *http.Request) {
+       w.Header().Set("Access-Control-Allow-Origin", "*")
+       resp, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, readLimit))
+       if nil != err {
+               log.Println("Invalid data.")
+               w.WriteHeader(http.StatusBadRequest)
+               return
+       }
+
+       offer, _, err := messages.DecodePollResponse(resp)
+       if err != nil {
+               log.Printf("Error reading offer: %s", err.Error())
+               w.WriteHeader(http.StatusBadRequest)
+               return
+       }
+       if offer == "" {
+               log.Printf("Error processing session description: %s", 
err.Error())
+               w.WriteHeader(http.StatusBadRequest)
+               return
+       }
+       sdp, err := util.DeserializeSessionDescription(offer)
+       if err != nil {
+               log.Printf("Error processing session description: %s", 
err.Error())
+               w.WriteHeader(http.StatusBadRequest)
+               return
+       }
+
+       dataChan := make(chan struct{})
+       pc, err := makePeerConnectionFromOffer(sdp, dataChan)
+       if err != nil {
+               log.Printf("Error making WebRTC connection: %s", err)
+               w.WriteHeader(http.StatusInternalServerError)
+               return
+       }
+
+       sdp = &webrtc.SessionDescription{
+               Type: pc.LocalDescription().Type,
+               SDP:  util.StripLocalAddresses(pc.LocalDescription().SDP),
+       }
+       answer, err := util.SerializeSessionDescription(sdp)
+       if err != nil {
+               log.Printf("Error making WebRTC connection: %s", err)
+               w.WriteHeader(http.StatusInternalServerError)
+               return
+       }
+       body, err := messages.EncodeAnswerRequest(answer, "")
+       if err != nil {
+               log.Printf("Error making WebRTC connection: %s", err)
+               w.WriteHeader(http.StatusInternalServerError)
+               return
+       }
+
+       w.Write(body)
+       // Set a timeout on peerconnection. If the connection state has not
+       // advanced to PeerConnectionStateConnected in this time,
+       // destroy the peer connection and return the token.
+       select {
+       case <-dataChan:
+               if err := pc.Close(); err != nil {
+                       log.Printf("Error calling pc.Close: %v", err)
+               }
+       case <-time.After(dataChannelTimeout):
+               if err := pc.Close(); err != nil {
+                       log.Printf("Error calling pc.Close: %v", err)
+               }
+       }
+       return
+
+}
+
+func main() {
+       var acmeEmail string
+       var acmeHostnamesCommas string
+       var acmeCertCacheDir string
+       var addr string
+       var disableTLS bool
+       var certFilename, keyFilename string
+       var unsafeLogging 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(&acmeCertCacheDir, "acme-cert-cache", "acme-cert-cache", 
"directory in which certificates should be cached")
+       flag.StringVar(&certFilename, "cert", "", "TLS certificate file")
+       flag.StringVar(&keyFilename, "key", "", "TLS private key file")
+       flag.StringVar(&addr, "addr", ":8443", "address to listen on")
+       flag.BoolVar(&disableTLS, "disable-tls", false, "don't use HTTPS")
+       flag.BoolVar(&unsafeLogging, "unsafe-logging", false, "prevent logs 
from being scrubbed")
+       flag.Parse()
+
+       var logOutput io.Writer = os.Stderr
+       if unsafeLogging {
+               log.SetOutput(logOutput)
+       } else {
+               // Scrub log output just in case an address ends up there
+               log.SetOutput(&safelog.LogScrubber{Output: logOutput})
+       }
+
+       log.SetFlags(log.LstdFlags | log.LUTC)
+
+       http.HandleFunc("/probe", probeHandler)
+
+       server := http.Server{
+               Addr: addr,
+       }
+
+       var err error
+       if acmeHostnamesCommas != "" {
+               acmeHostnames := strings.Split(acmeHostnamesCommas, ",")
+               log.Printf("ACME hostnames: %q", acmeHostnames)
+
+               var cache autocert.Cache
+               if err = os.MkdirAll(acmeCertCacheDir, 0700); err != nil {
+                       log.Printf("Warning: Couldn't create cache directory %q 
(reason: %s) so we're *not* using our certificate cache.", acmeCertCacheDir, 
err)
+               } else {
+                       cache = autocert.DirCache(acmeCertCacheDir)
+               }
+
+               certManager := autocert.Manager{
+                       Cache:      cache,
+                       Prompt:     autocert.AcceptTOS,
+                       HostPolicy: autocert.HostWhitelist(acmeHostnames...),
+                       Email:      acmeEmail,
+               }
+               // start certificate manager handler
+               go func() {
+                       log.Printf("Starting HTTP-01 listener")
+                       log.Fatal(http.ListenAndServe(":80", 
certManager.HTTPHandler(nil)))
+               }()
+
+               server.TLSConfig = &tls.Config{GetCertificate: 
certManager.GetCertificate}
+               err = server.ListenAndServeTLS("", "")
+       } else if certFilename != "" && keyFilename != "" {
+               err = server.ListenAndServeTLS(certFilename, keyFilename)
+       } else if disableTLS {
+               err = server.ListenAndServe()
+       } else {
+               log.Fatal("the --cert and --key, --acme-hostnames, or 
--disable-tls option is required")
+       }
+
+       if err != nil {
+               log.Println(err)
+       }
+}



_______________________________________________
tor-commits mailing list
[email protected]
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits

Reply via email to