From a1ac8f272aa38dc0a62fb694418bed5e903583c5 Mon Sep 17 00:00:00 2001 From: giongto35 Date: Fri, 3 Jan 2020 04:37:26 +0800 Subject: [PATCH] Setup HTTPS using autocert (#149) * Add HTTPS WSS * Update https * HTTPS * Add https * Add HTTPS worker * Add HTTPS * add https * Add pingserver to worker * add https * Add public domain whitelist * Not fatal when there existing echo server --- go.mod | 9 ++++ go.sum | 14 +++++ pkg/config/config.go | 1 + pkg/overlord/config.go | 11 ++-- pkg/overlord/handlers.go | 33 ++++++++---- pkg/overlord/overlord.go | 107 ++++++++++++++++++++++++++++++++++----- pkg/overlord/worker.go | 11 ++-- pkg/worker/handlers.go | 31 +++++++++--- pkg/worker/overworker.go | 96 ++++++++++++++++++++++++++++++++--- web/game.html | 2 +- web/js/controller.js | 2 +- web/js/network/socket.js | 8 ++- 12 files changed, 280 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 6a79beda..07ab8c75 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,19 @@ require ( github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654 github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/gorilla/mux v1.7.3 github.com/gorilla/websocket v1.4.0 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pion/webrtc/v2 v2.1.16 github.com/prometheus/client_golang v1.1.0 github.com/spf13/pflag v1.0.3 + golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + golang.org/x/sys v0.0.0-20191218084908-4a24b4065292 // indirect + golang.org/x/tools v0.0.0-20191218191756-61f5e7d2990e // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect gopkg.in/hraban/opus.v2 v2.0.0-20180426093920-0f2e0b4fc6cd ) diff --git a/go.sum b/go.sum index b3a35bc2..84424ee6 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -176,9 +178,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= @@ -191,6 +196,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -209,6 +215,8 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -218,6 +226,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -236,6 +245,8 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdO golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c h1:S/FtSvpNLtFBgjTqcKsRpsa6aVsI6iztaz1bQd9BJwE= golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191218084908-4a24b4065292 h1:Y8q0zsdcgAd+JU8VUA8p8Qv2YhuY9zevDG2ORt5qBUI= +golang.org/x/sys v0.0.0-20191218084908-4a24b4065292/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -252,6 +263,9 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191218191756-61f5e7d2990e/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/pkg/config/config.go b/pkg/config/config.go index 042a8757..0a606515 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -22,6 +22,7 @@ var StunTurnTemplate = `[{"urls":"stun:stun.l.google.com:19302"},{"urls":"stun:% var WSWait = 20 * time.Second var MatchWorkerRandom = false var ProdEnv = "prod" +var StagingEnv = "staging" const NumKeys = 10 diff --git a/pkg/overlord/config.go b/pkg/overlord/config.go index eb03e353..4ace79f6 100644 --- a/pkg/overlord/config.go +++ b/pkg/overlord/config.go @@ -6,16 +6,18 @@ import ( ) type Config struct { - Port int - URLPrefix string - DebugHost string + Port int + PublicDomain string + URLPrefix string + DebugHost string MonitoringConfig monitoring.ServerMonitoringConfig } func NewDefaultConfig() Config { return Config{ - Port: 8800, + Port: 8800, + PublicDomain: "http://localhost:8000", MonitoringConfig: monitoring.ServerMonitoringConfig{ Port: 6601, @@ -34,6 +36,7 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) *Config { fs.IntVarP(&c.MonitoringConfig.Port, "monitoring.port", "", c.MonitoringConfig.Port, "Monitoring server port") fs.StringVarP(&c.MonitoringConfig.URLPrefix, "monitoring.prefix", "", c.MonitoringConfig.URLPrefix, "Monitoring server url prefix") fs.StringVarP(&c.DebugHost, "debughost", "d", "", "Specify the server want to connect directly to debug") + fs.StringVarP(&c.PublicDomain, "domain", "n", c.PublicDomain, "Specify the server want to connect directly to debug") return c } diff --git a/pkg/overlord/handlers.go b/pkg/overlord/handlers.go index 6fbdb0ea..d9f7d891 100644 --- a/pkg/overlord/handlers.go +++ b/pkg/overlord/handlers.go @@ -30,6 +30,9 @@ type Server struct { workerClients map[string]*WorkerClient } +const pingServerTemp = "https://%s.%s/echo" +const devPingServer = "http://localhost:9000/echo" + var upgrader = websocket.Upgrader{} var errNotFound = errors.New("Not found") @@ -65,6 +68,16 @@ func (o *Server) GetWeb(w http.ResponseWriter, r *http.Request) { tmpl.Execute(w, data) } +// getPingServer returns the server for latency check of a zone. In latency check to find best worker step, we use this server to find the closest worker. +func (o *Server) getPingServer(zone string) string { + if *config.Mode == config.ProdEnv || *config.Mode == config.StagingEnv { + return fmt.Sprintf(pingServerTemp, zone, o.cfg.PublicDomain) + } + + // If not Prod or Staging, return dev environment + return devPingServer +} + // WSO handles all connections from a new worker to overlord func (o *Server) WSO(w http.ResponseWriter, r *http.Request) { fmt.Println("Connected") @@ -82,8 +95,11 @@ func (o *Server) WSO(w http.ResponseWriter, r *http.Request) { // Zone of the worker zone := r.URL.Query().Get("zone") + pingServer := o.getPingServer(zone) + fmt.Printf("Is public: %v zone: %v\n", util.IsPublicIP(address), zone) + // In case worker and overlord in the same host if !util.IsPublicIP(address) && *config.Mode == config.ProdEnv { // Don't accept private IP for worker's address in prod mode // However, if the worker in the same host with overlord, we can get public IP of worker @@ -95,7 +111,7 @@ func (o *Server) WSO(w http.ResponseWriter, r *http.Request) { return } } - client := NewWorkerClient(c, serverID, address, fmt.Sprintf(config.StunTurnTemplate, address, address), zone) + client := NewWorkerClient(c, serverID, address, fmt.Sprintf(config.StunTurnTemplate, address, address), zone, pingServer) o.workerClients[serverID] = client defer o.cleanConnection(client, serverID) @@ -213,7 +229,7 @@ func (o *Server) getBestWorkerClient(client *BrowserClient, zone string) (*Worke workerClients := o.getAvailableWorkers() - serverID, err := findBestServerFromBrowser(workerClients, client, zone) + serverID, err := o.findBestServerFromBrowser(workerClients, client, zone) if err != nil { log.Println(err) return nil, err @@ -247,13 +263,13 @@ func (o *Server) getWorkerFromAddress(address string) *WorkerClient { // findBestServerFromBrowser returns the best server for a session // All workers addresses are sent to user and user will ping to get latency -func findBestServerFromBrowser(workerClients map[string]*WorkerClient, client *BrowserClient, zone string) (string, error) { +func (o *Server) findBestServerFromBrowser(workerClients map[string]*WorkerClient, client *BrowserClient, zone string) (string, error) { // TODO: Find best Server by latency, currently return by ping if len(workerClients) == 0 { return "", errors.New("No server found") } - latencies := getLatencyMapFromBrowser(workerClients, client) + latencies := o.getLatencyMapFromBrowser(workerClients, client) log.Println("Latency map", latencies) if len(latencies) == 0 { @@ -280,16 +296,15 @@ func findBestServerFromBrowser(workerClients map[string]*WorkerClient, client *B } // getLatencyMapFromBrowser get all latencies from worker to user -func getLatencyMapFromBrowser(workerClients map[string]*WorkerClient, client *BrowserClient) map[*WorkerClient]int64 { +func (o *Server) getLatencyMapFromBrowser(workerClients map[string]*WorkerClient, client *BrowserClient) map[*WorkerClient]int64 { workersList := []*WorkerClient{} - + addressList := []string{} latencyMap := map[*WorkerClient]int64{} // addressList is the list of worker addresses - addressList := []string{} for _, workerClient := range workerClients { workersList = append(workersList, workerClient) - addressList = append(addressList, workerClient.Address) + addressList = append(addressList, workerClient.PingServer) } // send this address to user and get back latency @@ -307,7 +322,7 @@ func getLatencyMapFromBrowser(workerClients map[string]*WorkerClient, client *Br } for _, workerClient := range workersList { - if latency, ok := respLatency[workerClient.Address]; ok { + if latency, ok := respLatency[workerClient.PingServer]; ok { latencyMap[workerClient] = latency } } diff --git a/pkg/overlord/overlord.go b/pkg/overlord/overlord.go index 7b2b704e..d297cdc3 100644 --- a/pkg/overlord/overlord.go +++ b/pkg/overlord/overlord.go @@ -2,13 +2,23 @@ package overlord import ( "context" + "crypto/tls" + "fmt" "log" "net/http" + "time" + "github.com/giongto35/cloud-game/pkg/config" "github.com/giongto35/cloud-game/pkg/monitoring" "github.com/golang/glog" + "github.com/gorilla/mux" + + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" ) +const stagingLEURL = "https://acme-staging-v02.api.letsencrypt.org/directory" + type Overlord struct { ctx context.Context cfg Config @@ -45,24 +55,97 @@ func (o *Overlord) Shutdown() { } } +func makeServerFromMux(mux *http.ServeMux) *http.Server { + // set timeouts so that a slow or malicious client doesn't + // hold resources forever + return &http.Server{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: 120 * time.Second, + Handler: mux, + } +} + +func makeHTTPServer(server *Server) *http.Server { + r := mux.NewRouter() + r.HandleFunc("/", server.GetWeb) + r.HandleFunc("/ws", server.WS) + r.HandleFunc("/wso", server.WSO) + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./web")))) + + svmux := &http.ServeMux{} + svmux.Handle("/", r) + + return makeServerFromMux(svmux) +} + +func makeHTTPToHTTPSRedirectServer(server *Server) *http.Server { + handleRedirect := func(w http.ResponseWriter, r *http.Request) { + newURI := "https://" + r.Host + r.URL.String() + http.Redirect(w, r, newURI, http.StatusFound) + } + r := mux.NewRouter() + r.HandleFunc("/", handleRedirect) + r.HandleFunc("/ws", handleRedirect) + r.HandleFunc("/wso", handleRedirect) + r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./web")))) + + svmux := &http.ServeMux{} + svmux.Handle("/", r) + + return makeServerFromMux(svmux) +} + // initializeOverlord setup an overlord server func (o *Overlord) initializeOverlord() { overlord := NewServer(o.cfg) - http.HandleFunc("/", overlord.GetWeb) - http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./web")))) + var certManager *autocert.Manager + var httpsSrv *http.Server - // browser facing port - go func() { - http.HandleFunc("/ws", overlord.WS) - }() + log.Println("Initializing Overlord Server") + if *config.Mode == config.ProdEnv || *config.Mode == config.StagingEnv { + var leurl string + if *config.Mode == config.StagingEnv { + leurl = stagingLEURL + } else { + leurl = acme.LetsEncryptURL + } - // worker facing port - http.HandleFunc("/wso", overlord.WSO) - log.Println("Listening at port: localhost:8000") - err := http.ListenAndServe(":8000", nil) - // Print err if overlord cannot launch + certManager = &autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(o.cfg.PublicDomain), + Cache: autocert.DirCache("assets/cache"), + Client: &acme.Client{DirectoryURL: leurl}, + } + + httpsSrv = makeHTTPServer(overlord) + httpsSrv.Addr = ":443" + httpsSrv.TLSConfig = &tls.Config{GetCertificate: certManager.GetCertificate} + + go func() { + fmt.Printf("Starting HTTPS server on %s\n", httpsSrv.Addr) + err := httpsSrv.ListenAndServeTLS("", "") + if err != nil { + log.Fatalf("httpsSrv.ListendAndServeTLS() failed with %s", err) + } + }() + } + + var httpSrv *http.Server + if *config.Mode == config.ProdEnv || *config.Mode == config.StagingEnv { + httpSrv = makeHTTPToHTTPSRedirectServer(overlord) + } else { + httpSrv = makeHTTPServer(overlord) + } + + if certManager != nil { + httpSrv.Handler = certManager.HTTPHandler(httpSrv.Handler) + } + + httpSrv.Addr = ":8000" + err := httpSrv.ListenAndServe() if err != nil { - log.Fatal(err) + log.Fatalf("httpSrv.ListenAndServe() failed with %s", err) } } diff --git a/pkg/overlord/worker.go b/pkg/overlord/worker.go index 03a28b85..1dd2a3ce 100644 --- a/pkg/overlord/worker.go +++ b/pkg/overlord/worker.go @@ -7,10 +7,14 @@ import ( "github.com/gorilla/websocket" ) +const pingServer = "%s://%s/echo" + type WorkerClient struct { *cws.Client - ServerID string - Address string + ServerID string + Address string // ip address of worker + // public server used for ping check (Cannot use worker address because they are not publicly exposed) + PingServer string StunTurnServer string IsAvailable bool Zone string @@ -57,10 +61,11 @@ func (o *Server) RouteWorker(workerClient *WorkerClient) { } // NewWorkerClient returns a client connecting to worker. This connection exchanges information between workers and server -func NewWorkerClient(c *websocket.Conn, serverID string, address string, stunturn string, zone string) *WorkerClient { +func NewWorkerClient(c *websocket.Conn, serverID string, address string, stunturn string, zone, pingServer string) *WorkerClient { return &WorkerClient{ Client: cws.NewClient(c), ServerID: serverID, + PingServer: pingServer, Address: address, StunTurnServer: stunturn, IsAvailable: true, diff --git a/pkg/worker/handlers.go b/pkg/worker/handlers.go index 982ddf12..5ff3af77 100644 --- a/pkg/worker/handlers.go +++ b/pkg/worker/handlers.go @@ -1,12 +1,14 @@ package worker import ( + "crypto/tls" "log" "net/url" "os" "path" "time" + "github.com/giongto35/cloud-game/pkg/config" "github.com/giongto35/cloud-game/pkg/config/worker" "github.com/giongto35/cloud-game/pkg/util" @@ -61,13 +63,13 @@ func (h *Handler) Run() { for { oClient, err := setupOverlordConnection(h.overlordHost, h.cfg.Zone) if err != nil { - log.Println("Cannot connect to overlord. Retrying...") + log.Printf("Cannot connect to overlord. %v Retrying...", err) time.Sleep(time.Second) continue } h.oClient = oClient - log.Println("Connected to overlord successfully.") + log.Println("Connected to overlord successfully.", oClient, err) go h.oClient.Heartbeat() h.RouteOverlord() h.oClient.Listen() @@ -76,28 +78,43 @@ func (h *Handler) Run() { } func setupOverlordConnection(ohost string, zone string) (*OverlordClient, error) { + var scheme string + + if *config.Mode == config.ProdEnv || *config.Mode == config.StagingEnv { + scheme = "wss" + } else { + scheme = "ws" + } + overlordURL := url.URL{ - Scheme: "ws", + Scheme: scheme, Host: ohost, Path: "/wso", RawQuery: "zone=" + zone, } log.Println("Worker connecting to overlord:", overlordURL.String()) - conn, err := createOverlordConnection(overlordURL.String()) + conn, err := createOverlordConnection(&overlordURL) if err != nil { return nil, err } return NewOverlordClient(conn), nil } -func createOverlordConnection(ohost string) (*websocket.Conn, error) { - c, _, err := websocket.DefaultDialer.Dial(ohost, nil) +func createOverlordConnection(ourl *url.URL) (*websocket.Conn, error) { + var d websocket.Dialer + if ourl.Scheme == "wss" { + d = websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + } else { + d = websocket.Dialer{} + } + + ws, _, err := d.Dial(ourl.String(), nil) if err != nil { return nil, err } - return c, nil + return ws, nil } func (h *Handler) GetOverlordClient() *OverlordClient { diff --git a/pkg/worker/overworker.go b/pkg/worker/overworker.go index 62468b35..0474cb45 100644 --- a/pkg/worker/overworker.go +++ b/pkg/worker/overworker.go @@ -2,13 +2,18 @@ package worker import ( "context" + "crypto/tls" "fmt" "log" "net" "net/http" "strconv" + "time" + "github.com/giongto35/cloud-game/pkg/config" "github.com/giongto35/cloud-game/pkg/config/worker" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" "github.com/giongto35/cloud-game/pkg/monitoring" "github.com/golang/glog" @@ -21,6 +26,8 @@ type OverWorker struct { monitoringServer *monitoring.ServerMonitoring } +const stagingLEURL = "https://acme-staging-v02.api.letsencrypt.org/directory" + func New(ctx context.Context, cfg worker.Config) *OverWorker { return &OverWorker{ ctx: ctx, @@ -50,6 +57,87 @@ func (o *OverWorker) Shutdown() { } } +func makeServerFromMux(mux *http.ServeMux) *http.Server { + // set timeouts so that a slow or malicious client doesn't + // hold resources forever + return &http.Server{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: 120 * time.Second, + Handler: mux, + } +} + +func makeHTTPServer() *http.Server { + mux := &http.ServeMux{} + mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + log.Println(w, "echo") + }) + + return makeServerFromMux(mux) +} + +func makeHTTPToHTTPSRedirectServer() *http.Server { + handleRedirect := func(w http.ResponseWriter, r *http.Request) { + newURI := "https://" + r.Host + r.URL.String() + http.Redirect(w, r, newURI, http.StatusFound) + } + mux := &http.ServeMux{} + mux.HandleFunc("/", handleRedirect) + + return makeServerFromMux(mux) +} + +func (o *OverWorker) spawnServer(port int) { + var certManager *autocert.Manager + var httpsSrv *http.Server + + if *config.Mode == config.ProdEnv || *config.Mode == config.StagingEnv { + var leurl string + if *config.Mode == config.StagingEnv { + leurl = stagingLEURL + } else { + leurl = acme.LetsEncryptURL + } + + certManager = &autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache("assets/cache"), + Client: &acme.Client{DirectoryURL: leurl}, + } + + httpsSrv = makeHTTPServer() + httpsSrv.Addr = ":443" + httpsSrv.TLSConfig = &tls.Config{GetCertificate: certManager.GetCertificate} + + go func() { + fmt.Printf("Starting HTTPS server on %s\n", httpsSrv.Addr) + err := httpsSrv.ListenAndServeTLS("", "") + if err != nil { + log.Printf("httpsSrv.ListendAndServeTLS() failed with %s", err) + } + }() + } + + var httpSrv *http.Server + if *config.Mode == config.ProdEnv || *config.Mode == config.StagingEnv { + httpSrv = makeHTTPToHTTPSRedirectServer() + } else { + httpSrv = makeHTTPServer() + } + + if certManager != nil { + httpSrv.Handler = certManager.HTTPHandler(httpSrv.Handler) + } + + httpSrv.Addr = ":" + strconv.Itoa(port) + err := httpSrv.ListenAndServe() + if err != nil { + log.Printf("httpSrv.ListenAndServe() failed with %s", err) + } +} + // initializeWorker setup a worker func (o *OverWorker) initializeWorker() { worker := NewHandler(o.cfg) @@ -77,12 +165,6 @@ func (o *OverWorker) initializeWorker() { l.Close() - // echo endpoint is where user will request to test latency - http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - fmt.Fprintf(w, "echo") - }) - - http.ListenAndServe(":"+strconv.Itoa(port), nil) + o.spawnServer(port) } } diff --git a/web/game.html b/web/game.html index baa19dbb..89b2c0d5 100644 --- a/web/game.html +++ b/web/game.html @@ -99,7 +99,7 @@ - + diff --git a/web/js/controller.js b/web/js/controller.js index ddcd174b..08067a3d 100644 --- a/web/js/controller.js +++ b/web/js/controller.js @@ -50,7 +50,7 @@ Promise.all((data.addresses || []).map(address => { let beforeTime = Date.now(); - return ajax.fetch(`http://${address}:9000/echo?_=${beforeTime}`, {}, timeoutMs) + return ajax.fetch(`${address}?_=${beforeTime}`, {method: "GET", redirect: "follow"}, timeoutMs) .then(() => ({[address]: Date.now() - beforeTime}), () => ({[address]: maxTimeoutMs})); })).then(results => { // const latencies = Object.assign({}, ...results); diff --git a/web/js/network/socket.js b/web/js/network/socket.js index 060f05ea..f912d017 100644 --- a/web/js/network/socket.js +++ b/web/js/network/socket.js @@ -14,7 +14,13 @@ const socket = (() => { const init = (roomId, zone) => { const paramString = new URLSearchParams({room_id: roomId, zone: zone}) - conn = new WebSocket(`ws://${location.host}/ws?${paramString.toString()}`); + // if localhost + if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { + scheme = "ws" + } else { + scheme = "wss" + } + conn = new WebSocket(`${scheme}://${location.host}/ws?${paramString.toString()}`); // Clear old roomID conn.onopen = () => {