├── .gitignore ├── .travis.yml ├── CONTRIBUTORS ├── LICENSE ├── Makefile ├── README.md ├── assets ├── client │ ├── page.html │ ├── static │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ └── highlight.min.css │ │ ├── img │ │ │ └── glyphicons-halflings.png │ │ └── js │ │ │ ├── angular-sanitize.min.js │ │ │ ├── angular.js │ │ │ ├── base64.js │ │ │ ├── highlight.min.js │ │ │ ├── jquery-1.9.1.min.js │ │ │ ├── jquery.timeago.js │ │ │ ├── ngrok.js │ │ │ └── vkbeautify.js │ └── tls │ │ ├── ngrokroot.crt │ │ └── snakeoilca.crt └── server │ └── tls │ ├── snakeoil.crt │ └── snakeoil.key ├── contrib └── com.ngrok.client.plist ├── docs ├── CHANGELOG.md ├── DEVELOPMENT.md └── SELFHOSTING.md └── src └── ngrok ├── cache └── lru.go ├── client ├── cli.go ├── config.go ├── controller.go ├── debug.go ├── main.go ├── metrics.go ├── model.go ├── mvc │ ├── controller.go │ ├── model.go │ ├── state.go │ └── view.go ├── release.go ├── tls.go ├── update_debug.go ├── update_release.go └── views │ ├── term │ ├── area.go │ ├── http.go │ └── view.go │ └── web │ ├── http.go │ └── view.go ├── conn ├── conn.go └── tee.go ├── log └── logger.go ├── main ├── ngrok │ └── ngrok.go └── ngrokd │ └── ngrokd.go ├── msg ├── conn.go ├── msg.go └── pack.go ├── proto ├── http.go ├── interface.go └── tcp.go ├── server ├── cli.go ├── control.go ├── http.go ├── main.go ├── metrics.go ├── registry.go ├── tls.go └── tunnel.go ├── util ├── broadcast.go ├── errors.go ├── id.go ├── ring.go └── shutdown.go └── version └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | bin/ 3 | pkg/ 4 | src/code.google.com 5 | src/github.com 6 | src/bitbucket.org 7 | src/launchpad.net 8 | src/gopkg.in 9 | src/ngrok/client/assets/ 10 | src/ngrok/server/assets/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | script: make release-all 4 | install: true 5 | go: 6 | - 1.3 7 | - 1.4 8 | - 1.5 9 | - tip 10 | 11 | matrix: 12 | allow_failures: 13 | - go: tip 14 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Contributors to ngrok, both large and small: 2 | 3 | - Alan Shreve 4 | - Brandon Philips 5 | - Caleb Spare 6 | - Jay Hayes 7 | - Kevin Burke 8 | - Kyle Conroy 9 | - Nick Presta 10 | - Stephen Huenneke 11 | - inconshreveable 12 | - jzs 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Alan Shreve 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default server client deps fmt clean all release-all assets client-assets server-assets contributors 2 | export GOPATH:=$(shell pwd) 3 | 4 | BUILDTAGS=debug 5 | default: all 6 | 7 | deps: assets 8 | go get -tags '$(BUILDTAGS)' -d -v ngrok/... 9 | 10 | server: deps 11 | go install -tags '$(BUILDTAGS)' ngrok/main/ngrokd 12 | 13 | fmt: 14 | go fmt ngrok/... 15 | 16 | client: deps 17 | go install -tags '$(BUILDTAGS)' ngrok/main/ngrok 18 | 19 | assets: client-assets server-assets 20 | 21 | bin/go-bindata: 22 | GOOS="" GOARCH="" go get github.com/jteeuwen/go-bindata/go-bindata 23 | 24 | client-assets: bin/go-bindata 25 | bin/go-bindata -nomemcopy -pkg=assets -tags=$(BUILDTAGS) \ 26 | -debug=$(if $(findstring debug,$(BUILDTAGS)),true,false) \ 27 | -o=src/ngrok/client/assets/assets_$(BUILDTAGS).go \ 28 | assets/client/... 29 | 30 | server-assets: bin/go-bindata 31 | bin/go-bindata -nomemcopy -pkg=assets -tags=$(BUILDTAGS) \ 32 | -debug=$(if $(findstring debug,$(BUILDTAGS)),true,false) \ 33 | -o=src/ngrok/server/assets/assets_$(BUILDTAGS).go \ 34 | assets/server/... 35 | 36 | release-client: BUILDTAGS=release 37 | release-client: client 38 | 39 | release-server: BUILDTAGS=release 40 | release-server: server 41 | 42 | release-all: fmt release-client release-server 43 | 44 | all: fmt client server 45 | 46 | clean: 47 | go clean -i -r ngrok/... 48 | rm -rf src/ngrok/client/assets/ src/ngrok/server/assets/ 49 | 50 | contributors: 51 | echo "Contributors to ngrok, both large and small:\n" > CONTRIBUTORS 52 | git log --raw | grep "^Author: " | sort | uniq | cut -d ' ' -f2- | sed 's/^/- /' | cut -d '<' -f1 >> CONTRIBUTORS 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/inconshreveable/ngrok) 3 | 4 | # ngrok - Introspected tunnels to localhost ([homepage](https://ngrok.com)) 5 | ### "I want to securely expose a web server to the internet and capture all traffic for detailed inspection and replay" 6 |  7 | 8 | ## What is ngrok? 9 | ngrok is a reverse proxy that creates a secure tunnel from a public endpoint to a locally running web service. 10 | ngrok captures and analyzes all traffic over the tunnel for later inspection and replay. 11 | 12 | ## ngrok 2.0 13 | **NOTE** This repository contains the code for ngrok 1.0. The code for ngrok 2.0 is not open source. 14 | 15 | ## What can I do with ngrok? 16 | - Expose any http service behind a NAT or firewall to the internet on a subdomain of ngrok.com 17 | - Expose any tcp service behind a NAT or firewall to the internet on a random port of ngrok.com 18 | - Inspect all http requests/responses that are transmitted over the tunnel 19 | - Replay any request that was transmitted over the tunnel 20 | 21 | 22 | ## What is ngrok useful for? 23 | - Temporarily sharing a website that is only running on your development machine 24 | - Demoing an app at a hackathon without deploying 25 | - Developing any services which consume webhooks (HTTP callbacks) by allowing you to replay those requests 26 | - Debugging and understanding any web service by inspecting the HTTP traffic 27 | - Running networked services on machines that are firewalled off from the internet 28 | 29 | 30 | ## Downloading and installing ngrok 31 | ngrok has _no_ runtime dependencies. Just download a single binary for your platform and run it. Some premium features 32 | are only available by creating an account on ngrok.com. If you need them, [create an account on ngrok.com](https://ngrok.com/signup). 33 | 34 | #### [Download ngrok for your platform](https://ngrok.com/download) 35 | 36 | ## Developing on ngrok 37 | [ngrok developer's guide](docs/DEVELOPMENT.md) 38 | -------------------------------------------------------------------------------- /assets/client/page.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |{{ txn.Req.MethodPath }} |
73 | {{ txn.Resp.Status }} | 74 |{{ txn.Duration }} | 75 |
{{ Req.RawText }}
111 | {{ Req.RawBytes }}
115 | {{ Resp.RawText }}
135 | {{ Resp.RawBytes }}
139 | {{ key }} | ' + 203 | '{{ value }} | ' + 204 | '
---|
' +
258 | 'Unable to initiate connection to %s. A web server must be running on port %s to complete the tunnel.
32 | ` 33 | ) 34 | 35 | type ClientModel struct { 36 | log.Logger 37 | 38 | id string 39 | tunnels map[string]mvc.Tunnel 40 | serverVersion string 41 | metrics *ClientMetrics 42 | updateStatus mvc.UpdateStatus 43 | connStatus mvc.ConnStatus 44 | protoMap map[string]proto.Protocol 45 | protocols []proto.Protocol 46 | ctl mvc.Controller 47 | serverAddr string 48 | proxyUrl string 49 | authToken string 50 | tlsConfig *tls.Config 51 | tunnelConfig map[string]*TunnelConfiguration 52 | configPath string 53 | } 54 | 55 | func newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel { 56 | protoMap := make(map[string]proto.Protocol) 57 | protoMap["http"] = proto.NewHttp() 58 | protoMap["https"] = protoMap["http"] 59 | protoMap["tcp"] = proto.NewTcp() 60 | protocols := []proto.Protocol{protoMap["http"], protoMap["tcp"]} 61 | 62 | m := &ClientModel{ 63 | Logger: log.NewPrefixLogger("client"), 64 | 65 | // server address 66 | serverAddr: config.ServerAddr, 67 | 68 | // proxy address 69 | proxyUrl: config.HttpProxy, 70 | 71 | // auth token 72 | authToken: config.AuthToken, 73 | 74 | // connection status 75 | connStatus: mvc.ConnConnecting, 76 | 77 | // update status 78 | updateStatus: mvc.UpdateNone, 79 | 80 | // metrics 81 | metrics: NewClientMetrics(), 82 | 83 | // protocols 84 | protoMap: protoMap, 85 | 86 | // protocol list 87 | protocols: protocols, 88 | 89 | // open tunnels 90 | tunnels: make(map[string]mvc.Tunnel), 91 | 92 | // controller 93 | ctl: ctl, 94 | 95 | // tunnel configuration 96 | tunnelConfig: config.Tunnels, 97 | 98 | // config path 99 | configPath: config.Path, 100 | } 101 | 102 | // configure TLS 103 | if config.TrustHostRootCerts { 104 | m.Info("Trusting host's root certificates") 105 | m.tlsConfig = &tls.Config{} 106 | } else { 107 | m.Info("Trusting root CAs: %v", rootCrtPaths) 108 | var err error 109 | if m.tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil { 110 | panic(err) 111 | } 112 | } 113 | 114 | // configure TLS SNI 115 | m.tlsConfig.ServerName = serverName(m.serverAddr) 116 | 117 | return m 118 | } 119 | 120 | // mvc.State interface 121 | func (c ClientModel) GetProtocols() []proto.Protocol { return c.protocols } 122 | func (c ClientModel) GetClientVersion() string { return version.MajorMinor() } 123 | func (c ClientModel) GetServerVersion() string { return c.serverVersion } 124 | func (c ClientModel) GetTunnels() []mvc.Tunnel { 125 | tunnels := make([]mvc.Tunnel, 0) 126 | for _, t := range c.tunnels { 127 | tunnels = append(tunnels, t) 128 | } 129 | return tunnels 130 | } 131 | func (c ClientModel) GetConnStatus() mvc.ConnStatus { return c.connStatus } 132 | func (c ClientModel) GetUpdateStatus() mvc.UpdateStatus { return c.updateStatus } 133 | 134 | func (c ClientModel) GetConnectionMetrics() (metrics.Meter, metrics.Timer) { 135 | return c.metrics.connMeter, c.metrics.connTimer 136 | } 137 | 138 | func (c ClientModel) GetBytesInMetrics() (metrics.Counter, metrics.Histogram) { 139 | return c.metrics.bytesInCount, c.metrics.bytesIn 140 | } 141 | 142 | func (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) { 143 | return c.metrics.bytesOutCount, c.metrics.bytesOut 144 | } 145 | func (c ClientModel) SetUpdateStatus(updateStatus mvc.UpdateStatus) { 146 | c.updateStatus = updateStatus 147 | c.update() 148 | } 149 | 150 | // mvc.Model interface 151 | func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) { 152 | var localConn conn.Conn 153 | localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) 154 | if err != nil { 155 | c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err) 156 | return 157 | } 158 | 159 | defer localConn.Close() 160 | localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"}) 161 | localConn.Write(payload) 162 | ioutil.ReadAll(localConn) 163 | } 164 | 165 | func (c *ClientModel) Shutdown() { 166 | } 167 | 168 | func (c *ClientModel) update() { 169 | c.ctl.Update(c) 170 | } 171 | 172 | func (c *ClientModel) Run() { 173 | // how long we should wait before we reconnect 174 | maxWait := 30 * time.Second 175 | wait := 1 * time.Second 176 | 177 | for { 178 | // run the control channel 179 | c.control() 180 | 181 | // control only returns when a failure has occurred, so we're going to try to reconnect 182 | if c.connStatus == mvc.ConnOnline { 183 | wait = 1 * time.Second 184 | } 185 | 186 | log.Info("Waiting %d seconds before reconnecting", int(wait.Seconds())) 187 | time.Sleep(wait) 188 | // exponentially increase wait time 189 | wait = 2 * wait 190 | wait = time.Duration(math.Min(float64(wait), float64(maxWait))) 191 | c.connStatus = mvc.ConnReconnecting 192 | c.update() 193 | } 194 | } 195 | 196 | // Establishes and manages a tunnel control connection with the server 197 | func (c *ClientModel) control() { 198 | defer func() { 199 | if r := recover(); r != nil { 200 | log.Error("control recovering from failure %v", r) 201 | } 202 | }() 203 | 204 | // establish control channel 205 | var ( 206 | ctlConn conn.Conn 207 | err error 208 | ) 209 | if c.proxyUrl == "" { 210 | // simple non-proxied case, just connect to the server 211 | ctlConn, err = conn.Dial(c.serverAddr, "ctl", c.tlsConfig) 212 | } else { 213 | ctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "ctl", c.tlsConfig) 214 | } 215 | if err != nil { 216 | panic(err) 217 | } 218 | defer ctlConn.Close() 219 | 220 | // authenticate with the server 221 | auth := &msg.Auth{ 222 | ClientId: c.id, 223 | OS: runtime.GOOS, 224 | Arch: runtime.GOARCH, 225 | Version: version.Proto, 226 | MmVersion: version.MajorMinor(), 227 | User: c.authToken, 228 | } 229 | 230 | if err = msg.WriteMsg(ctlConn, auth); err != nil { 231 | panic(err) 232 | } 233 | 234 | // wait for the server to authenticate us 235 | var authResp msg.AuthResp 236 | if err = msg.ReadMsgInto(ctlConn, &authResp); err != nil { 237 | panic(err) 238 | } 239 | 240 | if authResp.Error != "" { 241 | emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error) 242 | c.ctl.Shutdown(emsg) 243 | return 244 | } 245 | 246 | c.id = authResp.ClientId 247 | c.serverVersion = authResp.MmVersion 248 | c.Info("Authenticated with server, client id: %v", c.id) 249 | c.update() 250 | if err = SaveAuthToken(c.configPath, c.authToken); err != nil { 251 | c.Error("Failed to save auth token: %v", err) 252 | } 253 | 254 | // request tunnels 255 | reqIdToTunnelConfig := make(map[string]*TunnelConfiguration) 256 | for _, config := range c.tunnelConfig { 257 | // create the protocol list to ask for 258 | var protocols []string 259 | for proto, _ := range config.Protocols { 260 | protocols = append(protocols, proto) 261 | } 262 | 263 | reqTunnel := &msg.ReqTunnel{ 264 | ReqId: util.RandId(8), 265 | Protocol: strings.Join(protocols, "+"), 266 | Hostname: config.Hostname, 267 | Subdomain: config.Subdomain, 268 | HttpAuth: config.HttpAuth, 269 | RemotePort: config.RemotePort, 270 | } 271 | 272 | // send the tunnel request 273 | if err = msg.WriteMsg(ctlConn, reqTunnel); err != nil { 274 | panic(err) 275 | } 276 | 277 | // save request id association so we know which local address 278 | // to proxy to later 279 | reqIdToTunnelConfig[reqTunnel.ReqId] = config 280 | } 281 | 282 | // start the heartbeat 283 | lastPong := time.Now().UnixNano() 284 | c.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) }) 285 | 286 | // main control loop 287 | for { 288 | var rawMsg msg.Message 289 | if rawMsg, err = msg.ReadMsg(ctlConn); err != nil { 290 | panic(err) 291 | } 292 | 293 | switch m := rawMsg.(type) { 294 | case *msg.ReqProxy: 295 | c.ctl.Go(c.proxy) 296 | 297 | case *msg.Pong: 298 | atomic.StoreInt64(&lastPong, time.Now().UnixNano()) 299 | 300 | case *msg.NewTunnel: 301 | if m.Error != "" { 302 | emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error) 303 | c.Error(emsg) 304 | c.ctl.Shutdown(emsg) 305 | continue 306 | } 307 | 308 | tunnel := mvc.Tunnel{ 309 | PublicUrl: m.Url, 310 | LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol], 311 | Protocol: c.protoMap[m.Protocol], 312 | } 313 | 314 | c.tunnels[tunnel.PublicUrl] = tunnel 315 | c.connStatus = mvc.ConnOnline 316 | c.Info("Tunnel established at %v", tunnel.PublicUrl) 317 | c.update() 318 | 319 | default: 320 | ctlConn.Warn("Ignoring unknown control message %v ", m) 321 | } 322 | } 323 | } 324 | 325 | // Establishes and manages a tunnel proxy connection with the server 326 | func (c *ClientModel) proxy() { 327 | var ( 328 | remoteConn conn.Conn 329 | err error 330 | ) 331 | 332 | if c.proxyUrl == "" { 333 | remoteConn, err = conn.Dial(c.serverAddr, "pxy", c.tlsConfig) 334 | } else { 335 | remoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "pxy", c.tlsConfig) 336 | } 337 | 338 | if err != nil { 339 | log.Error("Failed to establish proxy connection: %v", err) 340 | return 341 | } 342 | defer remoteConn.Close() 343 | 344 | err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id}) 345 | if err != nil { 346 | remoteConn.Error("Failed to write RegProxy: %v", err) 347 | return 348 | } 349 | 350 | // wait for the server to ack our register 351 | var startPxy msg.StartProxy 352 | if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil { 353 | remoteConn.Error("Server failed to write StartProxy: %v", err) 354 | return 355 | } 356 | 357 | tunnel, ok := c.tunnels[startPxy.Url] 358 | if !ok { 359 | remoteConn.Error("Couldn't find tunnel for proxy: %s", startPxy.Url) 360 | return 361 | } 362 | 363 | // start up the private connection 364 | start := time.Now() 365 | localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) 366 | if err != nil { 367 | remoteConn.Warn("Failed to open private leg %s: %v", tunnel.LocalAddr, err) 368 | 369 | if tunnel.Protocol.GetName() == "http" { 370 | // try to be helpful when you're in HTTP mode and a human might see the output 371 | badGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr) 372 | remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway 373 | Content-Type: text/html 374 | Content-Length: %d 375 | 376 | %s`, len(badGatewayBody), badGatewayBody))) 377 | } 378 | return 379 | } 380 | defer localConn.Close() 381 | 382 | m := c.metrics 383 | m.proxySetupTimer.Update(time.Since(start)) 384 | m.connMeter.Mark(1) 385 | c.update() 386 | m.connTimer.Time(func() { 387 | localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr}) 388 | bytesIn, bytesOut := conn.Join(localConn, remoteConn) 389 | m.bytesIn.Update(bytesIn) 390 | m.bytesOut.Update(bytesOut) 391 | m.bytesInCount.Inc(bytesIn) 392 | m.bytesOutCount.Inc(bytesOut) 393 | }) 394 | c.update() 395 | } 396 | 397 | // Hearbeating to ensure our connection ngrokd is still live 398 | func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) { 399 | lastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0) 400 | ping := time.NewTicker(pingInterval) 401 | pongCheck := time.NewTicker(time.Second) 402 | 403 | defer func() { 404 | conn.Close() 405 | ping.Stop() 406 | pongCheck.Stop() 407 | }() 408 | 409 | for { 410 | select { 411 | case <-pongCheck.C: 412 | lastPong := time.Unix(0, atomic.LoadInt64(lastPongAddr)) 413 | needPong := lastPong.Sub(lastPing) < 0 414 | pongLatency := time.Since(lastPing) 415 | 416 | if needPong && pongLatency > maxPongLatency { 417 | c.Info("Last ping: %v, Last pong: %v", lastPing, lastPong) 418 | c.Info("Connection stale, haven't gotten PongMsg in %d seconds", int(pongLatency.Seconds())) 419 | return 420 | } 421 | 422 | case <-ping.C: 423 | err := msg.WriteMsg(conn, &msg.Ping{}) 424 | if err != nil { 425 | conn.Debug("Got error %v when writing PingMsg", err) 426 | return 427 | } 428 | lastPing = time.Now() 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/ngrok/client/mvc/controller.go: -------------------------------------------------------------------------------- 1 | package mvc 2 | 3 | import ( 4 | "ngrok/util" 5 | ) 6 | 7 | type Controller interface { 8 | // how the model communicates that it has changed state 9 | Update(State) 10 | 11 | // instructs the controller to shut the app down 12 | Shutdown(message string) 13 | 14 | // PlayRequest instructs the model to play requests 15 | PlayRequest(tunnel Tunnel, payload []byte) 16 | 17 | // A channel of updates 18 | Updates() *util.Broadcast 19 | 20 | // returns the current state 21 | State() State 22 | 23 | // safe wrapper for running go-routines 24 | Go(fn func()) 25 | 26 | // the address where the web inspection interface is running 27 | GetWebInspectAddr() string 28 | } 29 | -------------------------------------------------------------------------------- /src/ngrok/client/mvc/model.go: -------------------------------------------------------------------------------- 1 | package mvc 2 | 3 | type Model interface { 4 | Run() 5 | 6 | Shutdown() 7 | 8 | PlayRequest(tunnel Tunnel, payload []byte) 9 | } 10 | -------------------------------------------------------------------------------- /src/ngrok/client/mvc/state.go: -------------------------------------------------------------------------------- 1 | package mvc 2 | 3 | import ( 4 | metrics "github.com/rcrowley/go-metrics" 5 | "ngrok/proto" 6 | ) 7 | 8 | type UpdateStatus int 9 | 10 | const ( 11 | UpdateNone = -1 * iota 12 | UpdateInstalling 13 | UpdateReady 14 | UpdateAvailable 15 | ) 16 | 17 | type ConnStatus int 18 | 19 | const ( 20 | ConnConnecting = iota 21 | ConnReconnecting 22 | ConnOnline 23 | ) 24 | 25 | type Tunnel struct { 26 | PublicUrl string 27 | Protocol proto.Protocol 28 | LocalAddr string 29 | } 30 | 31 | type ConnectionContext struct { 32 | Tunnel Tunnel 33 | ClientAddr string 34 | } 35 | 36 | type State interface { 37 | GetClientVersion() string 38 | GetServerVersion() string 39 | GetTunnels() []Tunnel 40 | GetProtocols() []proto.Protocol 41 | GetUpdateStatus() UpdateStatus 42 | GetConnStatus() ConnStatus 43 | GetConnectionMetrics() (metrics.Meter, metrics.Timer) 44 | GetBytesInMetrics() (metrics.Counter, metrics.Histogram) 45 | GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) 46 | SetUpdateStatus(UpdateStatus) 47 | } 48 | -------------------------------------------------------------------------------- /src/ngrok/client/mvc/view.go: -------------------------------------------------------------------------------- 1 | package mvc 2 | 3 | type View interface { 4 | Shutdown() 5 | } 6 | -------------------------------------------------------------------------------- /src/ngrok/client/release.go: -------------------------------------------------------------------------------- 1 | // +build release 2 | 3 | package client 4 | 5 | import "net" 6 | 7 | var ( 8 | rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt"} 9 | ) 10 | 11 | // server name in release builds is the host part of the server address 12 | func serverName(addr string) string { 13 | host, _, err := net.SplitHostPort(addr) 14 | 15 | // should never panic because the config parser calls SplitHostPort first 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | return host 21 | } 22 | -------------------------------------------------------------------------------- /src/ngrok/client/tls.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | _ "crypto/sha512" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "fmt" 9 | "ngrok/client/assets" 10 | ) 11 | 12 | func LoadTLSConfig(rootCertPaths []string) (*tls.Config, error) { 13 | pool := x509.NewCertPool() 14 | 15 | for _, certPath := range rootCertPaths { 16 | rootCrt, err := assets.Asset(certPath) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | pemBlock, _ := pem.Decode(rootCrt) 22 | if pemBlock == nil { 23 | return nil, fmt.Errorf("Bad PEM data") 24 | } 25 | 26 | certs, err := x509.ParseCertificates(pemBlock.Bytes) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | pool.AddCert(certs[0]) 32 | } 33 | 34 | return &tls.Config{RootCAs: pool}, nil 35 | } 36 | -------------------------------------------------------------------------------- /src/ngrok/client/update_debug.go: -------------------------------------------------------------------------------- 1 | // +build !release,!autoupdate 2 | 3 | package client 4 | 5 | import ( 6 | "ngrok/client/mvc" 7 | ) 8 | 9 | // no auto-updating in debug mode 10 | func autoUpdate(state mvc.State, token string) { 11 | } 12 | -------------------------------------------------------------------------------- /src/ngrok/client/update_release.go: -------------------------------------------------------------------------------- 1 | // +build release autoupdate 2 | 3 | package client 4 | 5 | import ( 6 | "ngrok/client/mvc" 7 | "ngrok/log" 8 | "ngrok/version" 9 | "time" 10 | 11 | "gopkg.in/inconshreveable/go-update.v0" 12 | "gopkg.in/inconshreveable/go-update.v0/check" 13 | ) 14 | 15 | const ( 16 | appId = "ap_pJSFC5wQYkAyI0FIVwKYs9h1hW" 17 | updateEndpoint = "https://api.equinox.io/1/Updates" 18 | ) 19 | 20 | const publicKey = `-----BEGIN PUBLIC KEY----- 21 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Gx8r9no1QBtCruJW2tu 22 | 082MJJ5ZA7k803GisR2c6WglPOD1b/+kUg+dx5Y0TKXz+uNlR3GrCxLh8WkoA95M 23 | T38CQldIjoVN/bWP6jzFxL+6BRoKy5L1TcaIf3xb9B8OhwEq60cvFy7BBrLKEHJN 24 | ua/D1S5axgNOAJ8tQ2w8gISICd84ng+U9tNMqIcEjUN89h3Z4zablfNIfVkbqbSR 25 | fnkR9boUaMr6S1w8OeInjWdiab9sUr87GmEo/3tVxrHVCzHB8pzzoZceCkjgI551 26 | d/hHfAl567YhlkQMNz8dawxBjQwCHHekgC8gAvTO7kmXkAm6YAbpa9kjwgnorPEP 27 | ywIDAQAB 28 | -----END PUBLIC KEY-----` 29 | 30 | func autoUpdate(s mvc.State, token string) { 31 | up, err := update.New().VerifySignatureWithPEM([]byte(publicKey)) 32 | if err != nil { 33 | log.Error("Failed to create update with signature: %v", err) 34 | return 35 | } 36 | 37 | update := func() (tryAgain bool) { 38 | log.Info("Checking for update") 39 | params := check.Params{ 40 | AppId: appId, 41 | AppVersion: version.MajorMinor(), 42 | UserId: token, 43 | } 44 | 45 | result, err := params.CheckForUpdate(updateEndpoint, up) 46 | if err == check.NoUpdateAvailable { 47 | log.Info("No update available") 48 | return true 49 | } else if err != nil { 50 | log.Error("Error while checking for update: %v", err) 51 | return true 52 | } 53 | 54 | if result.Initiative == check.INITIATIVE_AUTO { 55 | if err := up.CanUpdate(); err != nil { 56 | log.Error("Can't update: insufficient permissions: %v", err) 57 | // tell the user to update manually 58 | s.SetUpdateStatus(mvc.UpdateAvailable) 59 | } else { 60 | applyUpdate(s, result) 61 | } 62 | } else if result.Initiative == check.INITIATIVE_MANUAL { 63 | // this is the way the server tells us to update manually 64 | log.Info("Server wants us to update manually") 65 | s.SetUpdateStatus(mvc.UpdateAvailable) 66 | } else { 67 | log.Info("Update available, but ignoring") 68 | } 69 | 70 | // stop trying after a single download attempt 71 | // XXX: improve this so the we can: 72 | // 1. safely update multiple times 73 | // 2. only retry after temporary errors 74 | return false 75 | } 76 | 77 | // try to update immediately and then at a set interval 78 | for { 79 | if tryAgain := update(); !tryAgain { 80 | break 81 | } 82 | 83 | time.Sleep(updateCheckInterval) 84 | } 85 | } 86 | 87 | func applyUpdate(s mvc.State, result *check.Result) { 88 | err, errRecover := result.Update() 89 | if err == nil { 90 | log.Info("Update ready!") 91 | s.SetUpdateStatus(mvc.UpdateReady) 92 | return 93 | } 94 | 95 | log.Error("Error while updating ngrok: %v", err) 96 | if errRecover != nil { 97 | log.Error("Error while recovering from failed ngrok update, your binary may be missing: %v", errRecover.Error()) 98 | } 99 | 100 | // tell the user to update manually 101 | s.SetUpdateStatus(mvc.UpdateAvailable) 102 | } 103 | -------------------------------------------------------------------------------- /src/ngrok/client/views/term/area.go: -------------------------------------------------------------------------------- 1 | // shared internal functions for handling output to the terminal 2 | package term 3 | 4 | import ( 5 | "fmt" 6 | termbox "github.com/nsf/termbox-go" 7 | ) 8 | 9 | const ( 10 | fgColor = termbox.ColorWhite 11 | bgColor = termbox.ColorDefault 12 | ) 13 | 14 | type area struct { 15 | // top-left corner 16 | x, y int 17 | 18 | // size of the area 19 | w, h int 20 | 21 | // default colors 22 | fgColor, bgColor termbox.Attribute 23 | } 24 | 25 | func NewArea(x, y, w, h int) *area { 26 | return &area{x, y, w, h, fgColor, bgColor} 27 | } 28 | 29 | func (a *area) Clear() { 30 | for i := 0; i < a.w; i++ { 31 | for j := 0; j < a.h; j++ { 32 | termbox.SetCell(a.x+i, a.y+j, ' ', a.fgColor, a.bgColor) 33 | } 34 | } 35 | } 36 | 37 | func (a *area) APrintf(fg termbox.Attribute, x, y int, arg0 string, args ...interface{}) { 38 | s := fmt.Sprintf(arg0, args...) 39 | for i, ch := range s { 40 | termbox.SetCell(a.x+x+i, a.y+y, ch, fg, bgColor) 41 | } 42 | } 43 | 44 | func (a *area) Printf(x, y int, arg0 string, args ...interface{}) { 45 | a.APrintf(a.fgColor, x, y, arg0, args...) 46 | } 47 | -------------------------------------------------------------------------------- /src/ngrok/client/views/term/http.go: -------------------------------------------------------------------------------- 1 | package term 2 | 3 | import ( 4 | termbox "github.com/nsf/termbox-go" 5 | "ngrok/client/mvc" 6 | "ngrok/log" 7 | "ngrok/proto" 8 | "ngrok/util" 9 | "unicode/utf8" 10 | ) 11 | 12 | const ( 13 | size = 10 14 | pathMaxLength = 25 15 | ) 16 | 17 | type HttpView struct { 18 | log.Logger 19 | *area 20 | 21 | httpProto *proto.Http 22 | HttpRequests *util.Ring 23 | shutdown chan int 24 | termView *TermView 25 | } 26 | 27 | func colorFor(status string) termbox.Attribute { 28 | switch status[0] { 29 | case '3': 30 | return termbox.ColorCyan 31 | case '4': 32 | return termbox.ColorYellow 33 | case '5': 34 | return termbox.ColorRed 35 | default: 36 | } 37 | return termbox.ColorWhite 38 | } 39 | 40 | func newTermHttpView(ctl mvc.Controller, termView *TermView, proto *proto.Http, x, y int) *HttpView { 41 | v := &HttpView{ 42 | httpProto: proto, 43 | HttpRequests: util.NewRing(size), 44 | area: NewArea(x, y, 70, size+5), 45 | shutdown: make(chan int), 46 | termView: termView, 47 | Logger: log.NewPrefixLogger("view", "term", "http"), 48 | } 49 | ctl.Go(v.Run) 50 | return v 51 | } 52 | 53 | func (v *HttpView) Run() { 54 | updates := v.httpProto.Txns.Reg() 55 | 56 | for { 57 | select { 58 | case txn := <-updates: 59 | v.Debug("Got HTTP update") 60 | if txn.(*proto.HttpTxn).Resp == nil { 61 | v.HttpRequests.Add(txn) 62 | } 63 | v.Render() 64 | } 65 | } 66 | } 67 | 68 | func (v *HttpView) Render() { 69 | v.Clear() 70 | v.Printf(0, 0, "HTTP Requests") 71 | v.Printf(0, 1, "-------------") 72 | for i, obj := range v.HttpRequests.Slice() { 73 | txn := obj.(*proto.HttpTxn) 74 | path := truncatePath(txn.Req.URL.Path) 75 | v.Printf(0, 3+i, "%s %v", txn.Req.Method, path) 76 | if txn.Resp != nil { 77 | v.APrintf(colorFor(txn.Resp.Status), 30, 3+i, "%s", txn.Resp.Status) 78 | } 79 | } 80 | v.termView.Flush() 81 | } 82 | 83 | func (v *HttpView) Shutdown() { 84 | close(v.shutdown) 85 | } 86 | 87 | func truncatePath(path string) string { 88 | // Truncate all long strings based on rune count 89 | if utf8.RuneCountInString(path) > pathMaxLength { 90 | path = string([]rune(path)[:pathMaxLength]) 91 | } 92 | 93 | // By this point, len(path) should be < pathMaxLength if we're dealing with single-byte runes. 94 | // Otherwise, we have a multi-byte string and need to calculate the size of each rune and 95 | // truncate manually. 96 | // 97 | // This is a workaround for a bug in termbox-go. Remove it when this issue is fixed: 98 | // https://github.com/nsf/termbox-go/pull/21 99 | if len(path) > pathMaxLength { 100 | out := make([]byte, pathMaxLength, pathMaxLength) 101 | length := 0 102 | for { 103 | r, size := utf8.DecodeRuneInString(path[length:]) 104 | if r == utf8.RuneError && size == 1 { 105 | break 106 | } 107 | 108 | // utf8.EncodeRune expects there to be enough room to store the full size of the rune 109 | if length+size <= pathMaxLength { 110 | utf8.EncodeRune(out[length:], r) 111 | length += size 112 | } else { 113 | break 114 | } 115 | } 116 | path = string(out[:length]) 117 | } 118 | return path 119 | } 120 | -------------------------------------------------------------------------------- /src/ngrok/client/views/term/view.go: -------------------------------------------------------------------------------- 1 | // interactive terminal interface for local clients 2 | package term 3 | 4 | import ( 5 | termbox "github.com/nsf/termbox-go" 6 | "ngrok/client/mvc" 7 | "ngrok/log" 8 | "ngrok/proto" 9 | "ngrok/util" 10 | "time" 11 | ) 12 | 13 | type TermView struct { 14 | ctl mvc.Controller 15 | updates chan interface{} 16 | flush chan int 17 | shutdown chan int 18 | redraw *util.Broadcast 19 | subviews []mvc.View 20 | log.Logger 21 | *area 22 | } 23 | 24 | func NewTermView(ctl mvc.Controller) *TermView { 25 | // initialize terminal display 26 | termbox.Init() 27 | 28 | w, _ := termbox.Size() 29 | 30 | v := &TermView{ 31 | ctl: ctl, 32 | updates: ctl.Updates().Reg(), 33 | redraw: util.NewBroadcast(), 34 | flush: make(chan int), 35 | shutdown: make(chan int), 36 | Logger: log.NewPrefixLogger("view", "term"), 37 | area: NewArea(0, 0, w, 10), 38 | } 39 | 40 | ctl.Go(v.run) 41 | ctl.Go(v.input) 42 | 43 | return v 44 | } 45 | 46 | func connStatusRepr(status mvc.ConnStatus) (string, termbox.Attribute) { 47 | switch status { 48 | case mvc.ConnConnecting: 49 | return "connecting", termbox.ColorCyan 50 | case mvc.ConnReconnecting: 51 | return "reconnecting", termbox.ColorRed 52 | case mvc.ConnOnline: 53 | return "online", termbox.ColorGreen 54 | } 55 | return "unknown", termbox.ColorWhite 56 | } 57 | 58 | func (v *TermView) draw() { 59 | state := v.ctl.State() 60 | 61 | v.Clear() 62 | 63 | // quit instructions 64 | quitMsg := "(Ctrl+C to quit)" 65 | v.Printf(v.w-len(quitMsg), 0, quitMsg) 66 | 67 | // new version message 68 | updateStatus := state.GetUpdateStatus() 69 | var updateMsg string 70 | switch updateStatus { 71 | case mvc.UpdateNone: 72 | updateMsg = "" 73 | case mvc.UpdateInstalling: 74 | updateMsg = "ngrok is updating" 75 | case mvc.UpdateReady: 76 | updateMsg = "ngrok has updated: restart ngrok for the new version" 77 | case mvc.UpdateAvailable: 78 | updateMsg = "new version available at https://ngrok.com" 79 | default: 80 | pct := float64(updateStatus) / 100.0 81 | const barLength = 25 82 | full := int(barLength * pct) 83 | bar := make([]byte, barLength+2) 84 | bar[0] = '[' 85 | bar[barLength+1] = ']' 86 | for i := 0; i < 25; i++ { 87 | if i <= full { 88 | bar[i+1] = '#' 89 | } else { 90 | bar[i+1] = ' ' 91 | } 92 | } 93 | updateMsg = "Downloading update: " + string(bar) 94 | } 95 | 96 | if updateMsg != "" { 97 | v.APrintf(termbox.ColorYellow, 30, 0, updateMsg) 98 | } 99 | 100 | v.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, "ngrok") 101 | statusStr, statusColor := connStatusRepr(state.GetConnStatus()) 102 | v.APrintf(statusColor, 0, 2, "%-30s%s", "Tunnel Status", statusStr) 103 | 104 | v.Printf(0, 3, "%-30s%s/%s", "Version", state.GetClientVersion(), state.GetServerVersion()) 105 | var i int = 4 106 | for _, t := range state.GetTunnels() { 107 | v.Printf(0, i, "%-30s%s -> %s", "Forwarding", t.PublicUrl, t.LocalAddr) 108 | i++ 109 | } 110 | v.Printf(0, i+0, "%-30s%s", "Web Interface", v.ctl.GetWebInspectAddr()) 111 | 112 | connMeter, connTimer := state.GetConnectionMetrics() 113 | v.Printf(0, i+1, "%-30s%d", "# Conn", connMeter.Count()) 114 | 115 | msec := float64(time.Millisecond) 116 | v.Printf(0, i+2, "%-30s%.2fms", "Avg Conn Time", connTimer.Mean()/msec) 117 | 118 | termbox.Flush() 119 | } 120 | 121 | func (v *TermView) run() { 122 | defer close(v.shutdown) 123 | defer termbox.Close() 124 | 125 | redraw := v.redraw.Reg() 126 | defer v.redraw.UnReg(redraw) 127 | 128 | v.draw() 129 | for { 130 | v.Debug("Waiting for update") 131 | select { 132 | case <-v.flush: 133 | termbox.Flush() 134 | 135 | case <-v.updates: 136 | v.draw() 137 | 138 | case <-redraw: 139 | v.draw() 140 | 141 | case <-v.shutdown: 142 | return 143 | } 144 | } 145 | } 146 | 147 | func (v *TermView) Shutdown() { 148 | v.shutdown <- 1 149 | <-v.shutdown 150 | } 151 | 152 | func (v *TermView) Flush() { 153 | v.flush <- 1 154 | } 155 | 156 | func (v *TermView) NewHttpView(p *proto.Http) *HttpView { 157 | return newTermHttpView(v.ctl, v, p, 0, 12) 158 | } 159 | 160 | func (v *TermView) input() { 161 | for { 162 | ev := termbox.PollEvent() 163 | switch ev.Type { 164 | case termbox.EventKey: 165 | switch ev.Key { 166 | case termbox.KeyCtrlC: 167 | v.Info("Got quit command") 168 | v.ctl.Shutdown("") 169 | } 170 | 171 | case termbox.EventResize: 172 | v.Info("Resize event, redrawing") 173 | v.redraw.In() <- 1 174 | 175 | case termbox.EventError: 176 | panic(ev.Err) 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/ngrok/client/views/web/http.go: -------------------------------------------------------------------------------- 1 | // interactive web user interface 2 | package web 3 | 4 | import ( 5 | "encoding/base64" 6 | "encoding/json" 7 | "encoding/xml" 8 | "html/template" 9 | "net/http" 10 | "net/http/httputil" 11 | "net/url" 12 | "ngrok/client/assets" 13 | "ngrok/client/mvc" 14 | "ngrok/log" 15 | "ngrok/proto" 16 | "ngrok/util" 17 | "strings" 18 | "unicode/utf8" 19 | ) 20 | 21 | type SerializedTxn struct { 22 | Id string 23 | Duration int64 24 | Start int64 25 | ConnCtx mvc.ConnectionContext 26 | *proto.HttpTxn `json:"-"` 27 | Req SerializedRequest 28 | Resp SerializedResponse 29 | } 30 | 31 | type SerializedBody struct { 32 | RawContentType string 33 | ContentType string 34 | Text string 35 | Length int 36 | Error string 37 | ErrorOffset int 38 | Form url.Values 39 | } 40 | 41 | type SerializedRequest struct { 42 | Raw string 43 | MethodPath string 44 | Params url.Values 45 | Header http.Header 46 | Body SerializedBody 47 | Binary bool 48 | } 49 | 50 | type SerializedResponse struct { 51 | Raw string 52 | Status string 53 | Header http.Header 54 | Body SerializedBody 55 | Binary bool 56 | } 57 | 58 | type WebHttpView struct { 59 | log.Logger 60 | 61 | webview *WebView 62 | ctl mvc.Controller 63 | httpProto *proto.Http 64 | state chan SerializedUiState 65 | HttpRequests *util.Ring 66 | idToTxn map[string]*SerializedTxn 67 | } 68 | 69 | type SerializedUiState struct { 70 | Tunnels []mvc.Tunnel 71 | } 72 | 73 | type SerializedPayload struct { 74 | Txns []interface{} 75 | UiState SerializedUiState 76 | } 77 | 78 | func newWebHttpView(ctl mvc.Controller, wv *WebView, proto *proto.Http) *WebHttpView { 79 | whv := &WebHttpView{ 80 | Logger: log.NewPrefixLogger("view", "web", "http"), 81 | webview: wv, 82 | ctl: ctl, 83 | httpProto: proto, 84 | idToTxn: make(map[string]*SerializedTxn), 85 | HttpRequests: util.NewRing(20), 86 | } 87 | ctl.Go(whv.updateHttp) 88 | whv.register() 89 | return whv 90 | } 91 | 92 | type XMLDoc struct { 93 | data []byte `xml:",innerxml"` 94 | } 95 | 96 | func makeBody(h http.Header, body []byte) SerializedBody { 97 | b := SerializedBody{ 98 | Length: len(body), 99 | Text: base64.StdEncoding.EncodeToString(body), 100 | ErrorOffset: -1, 101 | } 102 | 103 | // some errors like XML errors only give a line number 104 | // and not an exact offset 105 | offsetForLine := func(line int) int { 106 | lines := strings.SplitAfterN(b.Text, "\n", line) 107 | return b.Length - len(lines[len(lines)-1]) 108 | } 109 | 110 | var err error 111 | b.RawContentType = h.Get("Content-Type") 112 | if b.RawContentType != "" { 113 | b.ContentType = strings.TrimSpace(strings.Split(b.RawContentType, ";")[0]) 114 | switch b.ContentType { 115 | case "application/xml", "text/xml": 116 | err = xml.Unmarshal(body, new(XMLDoc)) 117 | if err != nil { 118 | if syntaxError, ok := err.(*xml.SyntaxError); ok { 119 | // xml syntax errors only give us a line number, so we 120 | // count to find an offset 121 | b.ErrorOffset = offsetForLine(syntaxError.Line) 122 | } 123 | } 124 | 125 | case "application/json": 126 | err = json.Unmarshal(body, new(json.RawMessage)) 127 | if err != nil { 128 | if syntaxError, ok := err.(*json.SyntaxError); ok { 129 | b.ErrorOffset = int(syntaxError.Offset) 130 | } 131 | } 132 | 133 | case "application/x-www-form-urlencoded": 134 | b.Form, err = url.ParseQuery(string(body)) 135 | } 136 | } 137 | 138 | if err != nil { 139 | b.Error = err.Error() 140 | } 141 | 142 | return b 143 | } 144 | 145 | func (whv *WebHttpView) updateHttp() { 146 | // open channels for incoming http state changes 147 | // and broadcasts 148 | txnUpdates := whv.httpProto.Txns.Reg() 149 | for txn := range txnUpdates { 150 | // XXX: it's not safe for proto.Http and this code 151 | // to be accessing txn and txn.(req/resp) without synchronization 152 | htxn := txn.(*proto.HttpTxn) 153 | 154 | // we haven't processed this transaction yet if we haven't set the 155 | // user data 156 | if htxn.UserCtx == nil { 157 | rawReq, err := proto.DumpRequestOut(htxn.Req.Request, true) 158 | if err != nil { 159 | whv.Error("Failed to dump request: %v", err) 160 | continue 161 | } 162 | 163 | body := makeBody(htxn.Req.Header, htxn.Req.BodyBytes) 164 | whtxn := &SerializedTxn{ 165 | Id: util.RandId(8), 166 | HttpTxn: htxn, 167 | Req: SerializedRequest{ 168 | MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path, 169 | Raw: base64.StdEncoding.EncodeToString(rawReq), 170 | Params: htxn.Req.URL.Query(), 171 | Header: htxn.Req.Header, 172 | Body: body, 173 | Binary: !utf8.Valid(rawReq), 174 | }, 175 | Start: htxn.Start.Unix(), 176 | ConnCtx: htxn.ConnUserCtx.(mvc.ConnectionContext), 177 | } 178 | 179 | htxn.UserCtx = whtxn 180 | // XXX: unsafe map access from multiple go routines 181 | whv.idToTxn[whtxn.Id] = whtxn 182 | // XXX: use return value to delete from map so we don't leak memory 183 | whv.HttpRequests.Add(whtxn) 184 | } else { 185 | rawResp, err := httputil.DumpResponse(htxn.Resp.Response, true) 186 | if err != nil { 187 | whv.Error("Failed to dump response: %v", err) 188 | continue 189 | } 190 | 191 | txn := htxn.UserCtx.(*SerializedTxn) 192 | body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes) 193 | txn.Duration = htxn.Duration.Nanoseconds() 194 | txn.Resp = SerializedResponse{ 195 | Status: htxn.Resp.Status, 196 | Raw: base64.StdEncoding.EncodeToString(rawResp), 197 | Header: htxn.Resp.Header, 198 | Body: body, 199 | Binary: !utf8.Valid(rawResp), 200 | } 201 | 202 | payload, err := json.Marshal(txn) 203 | if err != nil { 204 | whv.Error("Failed to serialized txn payload for websocket: %v", err) 205 | } 206 | whv.webview.wsMessages.In() <- payload 207 | } 208 | } 209 | } 210 | 211 | func (whv *WebHttpView) register() { 212 | http.HandleFunc("/http/in/replay", func(w http.ResponseWriter, r *http.Request) { 213 | defer func() { 214 | if r := recover(); r != nil { 215 | err := util.MakePanicTrace(r) 216 | whv.Error("Replay failed: %v", err) 217 | http.Error(w, err, 500) 218 | } 219 | }() 220 | 221 | r.ParseForm() 222 | txnid := r.Form.Get("txnid") 223 | if txn, ok := whv.idToTxn[txnid]; ok { 224 | reqBytes, err := base64.StdEncoding.DecodeString(txn.Req.Raw) 225 | if err != nil { 226 | panic(err) 227 | } 228 | whv.ctl.PlayRequest(txn.ConnCtx.Tunnel, reqBytes) 229 | w.Write([]byte(http.StatusText(200))) 230 | } else { 231 | http.Error(w, http.StatusText(400), 400) 232 | } 233 | }) 234 | 235 | http.HandleFunc("/http/in", func(w http.ResponseWriter, r *http.Request) { 236 | defer func() { 237 | if r := recover(); r != nil { 238 | err := util.MakePanicTrace(r) 239 | whv.Error("HTTP web view failed: %v", err) 240 | http.Error(w, err, 500) 241 | } 242 | }() 243 | 244 | pageTmpl, err := assets.Asset("assets/client/page.html") 245 | if err != nil { 246 | panic(err) 247 | } 248 | 249 | tmpl := template.Must(template.New("page.html").Delims("{%", "%}").Parse(string(pageTmpl))) 250 | 251 | payloadData := SerializedPayload{ 252 | Txns: whv.HttpRequests.Slice(), 253 | UiState: SerializedUiState{Tunnels: whv.ctl.State().GetTunnels()}, 254 | } 255 | 256 | payload, err := json.Marshal(payloadData) 257 | if err != nil { 258 | panic(err) 259 | } 260 | 261 | // write the response 262 | if err := tmpl.Execute(w, string(payload)); err != nil { 263 | panic(err) 264 | } 265 | }) 266 | } 267 | 268 | func (whv *WebHttpView) Shutdown() { 269 | } 270 | -------------------------------------------------------------------------------- /src/ngrok/client/views/web/view.go: -------------------------------------------------------------------------------- 1 | // interactive web user interface 2 | package web 3 | 4 | import ( 5 | "github.com/gorilla/websocket" 6 | "net/http" 7 | "ngrok/client/assets" 8 | "ngrok/client/mvc" 9 | "ngrok/log" 10 | "ngrok/proto" 11 | "ngrok/util" 12 | "path" 13 | ) 14 | 15 | type WebView struct { 16 | log.Logger 17 | 18 | ctl mvc.Controller 19 | 20 | // messages sent over this broadcast are sent to all websocket connections 21 | wsMessages *util.Broadcast 22 | } 23 | 24 | func NewWebView(ctl mvc.Controller, addr string) *WebView { 25 | wv := &WebView{ 26 | Logger: log.NewPrefixLogger("view", "web"), 27 | wsMessages: util.NewBroadcast(), 28 | ctl: ctl, 29 | } 30 | 31 | // for now, always redirect to the http view 32 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 33 | http.Redirect(w, r, "/http/in", 302) 34 | }) 35 | 36 | // handle web socket connections 37 | http.HandleFunc("/_ws", func(w http.ResponseWriter, r *http.Request) { 38 | conn, err := websocket.Upgrade(w, r, nil, 1024, 1024) 39 | 40 | if err != nil { 41 | http.Error(w, "Failed websocket upgrade", 400) 42 | wv.Warn("Failed websocket upgrade: %v", err) 43 | return 44 | } 45 | 46 | msgs := wv.wsMessages.Reg() 47 | defer wv.wsMessages.UnReg(msgs) 48 | for m := range msgs { 49 | err := conn.WriteMessage(websocket.TextMessage, m.([]byte)) 50 | if err != nil { 51 | // connection is closed 52 | break 53 | } 54 | } 55 | }) 56 | 57 | // serve static assets 58 | http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { 59 | buf, err := assets.Asset(path.Join("assets", "client", r.URL.Path[1:])) 60 | if err != nil { 61 | wv.Warn("Error serving static file: %s", err.Error()) 62 | http.NotFound(w, r) 63 | return 64 | } 65 | w.Write(buf) 66 | }) 67 | 68 | wv.Info("Serving web interface on %s", addr) 69 | wv.ctl.Go(func() { http.ListenAndServe(addr, nil) }) 70 | return wv 71 | } 72 | 73 | func (wv *WebView) NewHttpView(proto *proto.Http) *WebHttpView { 74 | return newWebHttpView(wv.ctl, wv, proto) 75 | } 76 | 77 | func (wv *WebView) Shutdown() { 78 | } 79 | -------------------------------------------------------------------------------- /src/ngrok/conn/conn.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "encoding/base64" 7 | "fmt" 8 | vhost "github.com/inconshreveable/go-vhost" 9 | "io" 10 | "math/rand" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | "ngrok/log" 15 | "sync" 16 | ) 17 | 18 | type Conn interface { 19 | net.Conn 20 | log.Logger 21 | Id() string 22 | SetType(string) 23 | CloseRead() error 24 | } 25 | 26 | type loggedConn struct { 27 | tcp *net.TCPConn 28 | net.Conn 29 | log.Logger 30 | id int32 31 | typ string 32 | } 33 | 34 | type Listener struct { 35 | net.Addr 36 | Conns chan *loggedConn 37 | } 38 | 39 | func wrapConn(conn net.Conn, typ string) *loggedConn { 40 | switch c := conn.(type) { 41 | case *vhost.HTTPConn: 42 | wrapped := c.Conn.(*loggedConn) 43 | return &loggedConn{wrapped.tcp, conn, wrapped.Logger, wrapped.id, wrapped.typ} 44 | case *loggedConn: 45 | return c 46 | case *net.TCPConn: 47 | wrapped := &loggedConn{c, conn, log.NewPrefixLogger(), rand.Int31(), typ} 48 | wrapped.AddLogPrefix(wrapped.Id()) 49 | return wrapped 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func Listen(addr, typ string, tlsCfg *tls.Config) (l *Listener, err error) { 56 | // listen for incoming connections 57 | listener, err := net.Listen("tcp", addr) 58 | if err != nil { 59 | return 60 | } 61 | 62 | l = &Listener{ 63 | Addr: listener.Addr(), 64 | Conns: make(chan *loggedConn), 65 | } 66 | 67 | go func() { 68 | for { 69 | rawConn, err := listener.Accept() 70 | if err != nil { 71 | log.Error("Failed to accept new TCP connection of type %s: %v", typ, err) 72 | continue 73 | } 74 | 75 | c := wrapConn(rawConn, typ) 76 | if tlsCfg != nil { 77 | c.Conn = tls.Server(c.Conn, tlsCfg) 78 | } 79 | c.Info("New connection from %v", c.RemoteAddr()) 80 | l.Conns <- c 81 | } 82 | }() 83 | return 84 | } 85 | 86 | func Wrap(conn net.Conn, typ string) *loggedConn { 87 | return wrapConn(conn, typ) 88 | } 89 | 90 | func Dial(addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) { 91 | var rawConn net.Conn 92 | if rawConn, err = net.Dial("tcp", addr); err != nil { 93 | return 94 | } 95 | 96 | conn = wrapConn(rawConn, typ) 97 | conn.Debug("New connection to: %v", rawConn.RemoteAddr()) 98 | 99 | if tlsCfg != nil { 100 | conn.StartTLS(tlsCfg) 101 | } 102 | 103 | return 104 | } 105 | 106 | func DialHttpProxy(proxyUrl, addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) { 107 | // parse the proxy address 108 | var parsedUrl *url.URL 109 | if parsedUrl, err = url.Parse(proxyUrl); err != nil { 110 | return 111 | } 112 | 113 | var proxyAuth string 114 | if parsedUrl.User != nil { 115 | proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(parsedUrl.User.String())) 116 | } 117 | 118 | var proxyTlsConfig *tls.Config 119 | switch parsedUrl.Scheme { 120 | case "http": 121 | proxyTlsConfig = nil 122 | case "https": 123 | proxyTlsConfig = new(tls.Config) 124 | default: 125 | err = fmt.Errorf("Proxy URL scheme must be http or https, got: %s", parsedUrl.Scheme) 126 | return 127 | } 128 | 129 | // dial the proxy 130 | if conn, err = Dial(parsedUrl.Host, typ, proxyTlsConfig); err != nil { 131 | return 132 | } 133 | 134 | // send an HTTP proxy CONNECT message 135 | req, err := http.NewRequest("CONNECT", "https://"+addr, nil) 136 | if err != nil { 137 | return 138 | } 139 | 140 | if proxyAuth != "" { 141 | req.Header.Set("Proxy-Authorization", proxyAuth) 142 | } 143 | req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; ngrok)") 144 | req.Write(conn) 145 | 146 | // read the proxy's response 147 | resp, err := http.ReadResponse(bufio.NewReader(conn), req) 148 | if err != nil { 149 | return 150 | } 151 | resp.Body.Close() 152 | 153 | if resp.StatusCode != 200 { 154 | err = fmt.Errorf("Non-200 response from proxy server: %s", resp.Status) 155 | return 156 | } 157 | 158 | // upgrade to TLS 159 | conn.StartTLS(tlsCfg) 160 | 161 | return 162 | } 163 | 164 | func (c *loggedConn) StartTLS(tlsCfg *tls.Config) { 165 | c.Conn = tls.Client(c.Conn, tlsCfg) 166 | } 167 | 168 | func (c *loggedConn) Close() (err error) { 169 | if err := c.Conn.Close(); err == nil { 170 | c.Debug("Closing") 171 | } 172 | return 173 | } 174 | 175 | func (c *loggedConn) Id() string { 176 | return fmt.Sprintf("%s:%x", c.typ, c.id) 177 | } 178 | 179 | func (c *loggedConn) SetType(typ string) { 180 | oldId := c.Id() 181 | c.typ = typ 182 | c.ClearLogPrefixes() 183 | c.AddLogPrefix(c.Id()) 184 | c.Info("Renamed connection %s", oldId) 185 | } 186 | 187 | func (c *loggedConn) CloseRead() error { 188 | // XXX: use CloseRead() in Conn.Join() and in Control.shutdown() for cleaner 189 | // connection termination. Unfortunately, when I've tried that, I've observed 190 | // failures where the connection was closed *before* flushing its write buffer, 191 | // set with SetLinger() set properly (which it is by default). 192 | return c.tcp.CloseRead() 193 | } 194 | 195 | func Join(c Conn, c2 Conn) (int64, int64) { 196 | var wait sync.WaitGroup 197 | 198 | pipe := func(to Conn, from Conn, bytesCopied *int64) { 199 | defer to.Close() 200 | defer from.Close() 201 | defer wait.Done() 202 | 203 | var err error 204 | *bytesCopied, err = io.Copy(to, from) 205 | if err != nil { 206 | from.Warn("Copied %d bytes to %s before failing with error %v", *bytesCopied, to.Id(), err) 207 | } else { 208 | from.Debug("Copied %d bytes to %s", *bytesCopied, to.Id()) 209 | } 210 | } 211 | 212 | wait.Add(2) 213 | var fromBytes, toBytes int64 214 | go pipe(c, c2, &fromBytes) 215 | go pipe(c2, c, &toBytes) 216 | c.Info("Joined with connection %s", c2.Id()) 217 | wait.Wait() 218 | return fromBytes, toBytes 219 | } 220 | -------------------------------------------------------------------------------- /src/ngrok/conn/tee.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | ) 7 | 8 | // conn.Tee is a wraps a conn.Conn 9 | // causing all writes/reads to be tee'd just 10 | // like the unix command such that all data that 11 | // is read and written to the connection through its 12 | // interfaces will also be copied into two dedicated pipes 13 | // used for consuming a copy of the data stream 14 | // 15 | // this is useful for introspecting the traffic flowing 16 | // over a connection without having to tamper with the actual 17 | // code that reads and writes over the connection 18 | // 19 | // NB: the data is Tee'd into a shared-memory io.Pipe which 20 | // has a limited (and small) buffer. If you are not consuming from 21 | // the ReadBuffer() and WriteBuffer(), you are going to block 22 | // your application's real traffic from flowing over the connection 23 | 24 | type Tee struct { 25 | rd io.Reader 26 | wr io.Writer 27 | readPipe struct { 28 | rd *io.PipeReader 29 | wr *io.PipeWriter 30 | } 31 | writePipe struct { 32 | rd *io.PipeReader 33 | wr *io.PipeWriter 34 | } 35 | Conn 36 | } 37 | 38 | func NewTee(conn Conn) *Tee { 39 | c := &Tee{ 40 | rd: nil, 41 | wr: nil, 42 | Conn: conn, 43 | } 44 | 45 | c.readPipe.rd, c.readPipe.wr = io.Pipe() 46 | c.writePipe.rd, c.writePipe.wr = io.Pipe() 47 | 48 | c.rd = io.TeeReader(c.Conn, c.readPipe.wr) 49 | c.wr = io.MultiWriter(c.Conn, c.writePipe.wr) 50 | return c 51 | } 52 | 53 | func (c *Tee) ReadBuffer() *bufio.Reader { 54 | return bufio.NewReader(c.readPipe.rd) 55 | } 56 | 57 | func (c *Tee) WriteBuffer() *bufio.Reader { 58 | return bufio.NewReader(c.writePipe.rd) 59 | } 60 | 61 | func (c *Tee) Read(b []byte) (n int, err error) { 62 | n, err = c.rd.Read(b) 63 | if err != nil { 64 | c.readPipe.wr.Close() 65 | } 66 | return 67 | } 68 | 69 | func (c *Tee) ReadFrom(r io.Reader) (n int64, err error) { 70 | n, err = io.Copy(c.wr, r) 71 | if err != nil { 72 | c.writePipe.wr.Close() 73 | } 74 | return 75 | } 76 | 77 | func (c *Tee) Write(b []byte) (n int, err error) { 78 | n, err = c.wr.Write(b) 79 | if err != nil { 80 | c.writePipe.wr.Close() 81 | } 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /src/ngrok/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | log "github.com/alecthomas/log4go" 5 | "fmt" 6 | ) 7 | 8 | var root log.Logger = make(log.Logger) 9 | 10 | func LogTo(target string, level_name string) { 11 | var writer log.LogWriter = nil 12 | 13 | switch target { 14 | case "stdout": 15 | writer = log.NewConsoleLogWriter() 16 | case "none": 17 | // no logging 18 | default: 19 | writer = log.NewFileLogWriter(target, true) 20 | } 21 | 22 | if writer != nil { 23 | var level = log.DEBUG 24 | 25 | switch level_name { 26 | case "FINEST": 27 | level = log.FINEST 28 | case "FINE": 29 | level = log.FINE 30 | case "DEBUG": 31 | level = log.DEBUG 32 | case "TRACE": 33 | level = log.TRACE 34 | case "INFO": 35 | level = log.INFO 36 | case "WARNING": 37 | level = log.WARNING 38 | case "ERROR": 39 | level = log.ERROR 40 | case "CRITICAL": 41 | level = log.CRITICAL 42 | default: 43 | level = log.DEBUG 44 | } 45 | 46 | root.AddFilter("log", level, writer) 47 | } 48 | } 49 | 50 | type Logger interface { 51 | AddLogPrefix(string) 52 | ClearLogPrefixes() 53 | Debug(string, ...interface{}) 54 | Info(string, ...interface{}) 55 | Warn(string, ...interface{}) error 56 | Error(string, ...interface{}) error 57 | } 58 | 59 | type PrefixLogger struct { 60 | *log.Logger 61 | prefix string 62 | } 63 | 64 | func NewPrefixLogger(prefixes ...string) Logger { 65 | logger := &PrefixLogger{Logger: &root} 66 | 67 | for _, p := range prefixes { 68 | logger.AddLogPrefix(p) 69 | } 70 | 71 | return logger 72 | } 73 | 74 | func (pl *PrefixLogger) pfx(fmtstr string) interface{} { 75 | return fmt.Sprintf("%s %s", pl.prefix, fmtstr) 76 | } 77 | 78 | func (pl *PrefixLogger) Debug(arg0 string, args ...interface{}) { 79 | pl.Logger.Debug(pl.pfx(arg0), args...) 80 | } 81 | 82 | func (pl *PrefixLogger) Info(arg0 string, args ...interface{}) { 83 | pl.Logger.Info(pl.pfx(arg0), args...) 84 | } 85 | 86 | func (pl *PrefixLogger) Warn(arg0 string, args ...interface{}) error { 87 | return pl.Logger.Warn(pl.pfx(arg0), args...) 88 | } 89 | 90 | func (pl *PrefixLogger) Error(arg0 string, args ...interface{}) error { 91 | return pl.Logger.Error(pl.pfx(arg0), args...) 92 | } 93 | 94 | func (pl *PrefixLogger) AddLogPrefix(prefix string) { 95 | if len(pl.prefix) > 0 { 96 | pl.prefix += " " 97 | } 98 | 99 | pl.prefix += "[" + prefix + "]" 100 | } 101 | 102 | func (pl *PrefixLogger) ClearLogPrefixes() { 103 | pl.prefix = "" 104 | } 105 | 106 | // we should never really use these . . . always prefer logging through a prefix logger 107 | func Debug(arg0 string, args ...interface{}) { 108 | root.Debug(arg0, args...) 109 | } 110 | 111 | func Info(arg0 string, args ...interface{}) { 112 | root.Info(arg0, args...) 113 | } 114 | 115 | func Warn(arg0 string, args ...interface{}) error { 116 | return root.Warn(arg0, args...) 117 | } 118 | 119 | func Error(arg0 string, args ...interface{}) error { 120 | return root.Error(arg0, args...) 121 | } 122 | -------------------------------------------------------------------------------- /src/ngrok/main/ngrok/ngrok.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ngrok/client" 5 | ) 6 | 7 | func main() { 8 | client.Main() 9 | } 10 | -------------------------------------------------------------------------------- /src/ngrok/main/ngrokd/ngrokd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ngrok/server" 5 | ) 6 | 7 | func main() { 8 | server.Main() 9 | } 10 | -------------------------------------------------------------------------------- /src/ngrok/msg/conn.go: -------------------------------------------------------------------------------- 1 | package msg 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "ngrok/conn" 8 | ) 9 | 10 | func readMsgShared(c conn.Conn) (buffer []byte, err error) { 11 | c.Debug("Waiting to read message") 12 | 13 | var sz int64 14 | err = binary.Read(c, binary.LittleEndian, &sz) 15 | if err != nil { 16 | return 17 | } 18 | c.Debug("Reading message with length: %d", sz) 19 | 20 | buffer = make([]byte, sz) 21 | n, err := c.Read(buffer) 22 | c.Debug("Read message %s", buffer) 23 | 24 | if err != nil { 25 | return 26 | } 27 | 28 | if int64(n) != sz { 29 | err = errors.New(fmt.Sprintf("Expected to read %d bytes, but only read %d", sz, n)) 30 | return 31 | } 32 | 33 | return 34 | } 35 | 36 | func ReadMsg(c conn.Conn) (msg Message, err error) { 37 | buffer, err := readMsgShared(c) 38 | if err != nil { 39 | return 40 | } 41 | 42 | return Unpack(buffer) 43 | } 44 | 45 | func ReadMsgInto(c conn.Conn, msg Message) (err error) { 46 | buffer, err := readMsgShared(c) 47 | if err != nil { 48 | return 49 | } 50 | return UnpackInto(buffer, msg) 51 | } 52 | 53 | func WriteMsg(c conn.Conn, msg interface{}) (err error) { 54 | buffer, err := Pack(msg) 55 | if err != nil { 56 | return 57 | } 58 | 59 | c.Debug("Writing message: %s", string(buffer)) 60 | err = binary.Write(c, binary.LittleEndian, int64(len(buffer))) 61 | 62 | if err != nil { 63 | return 64 | } 65 | 66 | if _, err = c.Write(buffer); err != nil { 67 | return 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /src/ngrok/msg/msg.go: -------------------------------------------------------------------------------- 1 | package msg 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | ) 7 | 8 | var TypeMap map[string]reflect.Type 9 | 10 | func init() { 11 | TypeMap = make(map[string]reflect.Type) 12 | 13 | t := func(obj interface{}) reflect.Type { return reflect.TypeOf(obj).Elem() } 14 | TypeMap["Auth"] = t((*Auth)(nil)) 15 | TypeMap["AuthResp"] = t((*AuthResp)(nil)) 16 | TypeMap["ReqTunnel"] = t((*ReqTunnel)(nil)) 17 | TypeMap["NewTunnel"] = t((*NewTunnel)(nil)) 18 | TypeMap["RegProxy"] = t((*RegProxy)(nil)) 19 | TypeMap["ReqProxy"] = t((*ReqProxy)(nil)) 20 | TypeMap["StartProxy"] = t((*StartProxy)(nil)) 21 | TypeMap["Ping"] = t((*Ping)(nil)) 22 | TypeMap["Pong"] = t((*Pong)(nil)) 23 | } 24 | 25 | type Message interface{} 26 | 27 | type Envelope struct { 28 | Type string 29 | Payload json.RawMessage 30 | } 31 | 32 | // When a client opens a new control channel to the server 33 | // it must start by sending an Auth message. 34 | type Auth struct { 35 | Version string // protocol version 36 | MmVersion string // major/minor software version (informational only) 37 | User string 38 | Password string 39 | OS string 40 | Arch string 41 | ClientId string // empty for new sessions 42 | } 43 | 44 | // A server responds to an Auth message with an 45 | // AuthResp message over the control channel. 46 | // 47 | // If Error is not the empty string 48 | // the server has indicated it will not accept 49 | // the new session and will close the connection. 50 | // 51 | // The server response includes a unique ClientId 52 | // that is used to associate and authenticate future 53 | // proxy connections via the same field in RegProxy messages. 54 | type AuthResp struct { 55 | Version string 56 | MmVersion string 57 | ClientId string 58 | Error string 59 | } 60 | 61 | // A client sends this message to the server over the control channel 62 | // to request a new tunnel be opened on the client's behalf. 63 | // ReqId is a random number set by the client that it can pull 64 | // from future NewTunnel's to correlate then to the requesting ReqTunnel. 65 | type ReqTunnel struct { 66 | ReqId string 67 | Protocol string 68 | 69 | // http only 70 | Hostname string 71 | Subdomain string 72 | HttpAuth string 73 | 74 | // tcp only 75 | RemotePort uint16 76 | } 77 | 78 | // When the server opens a new tunnel on behalf of 79 | // a client, it sends a NewTunnel message to notify the client. 80 | // ReqId is the ReqId from the corresponding ReqTunnel message. 81 | // 82 | // A client may receive *multiple* NewTunnel messages from a single 83 | // ReqTunnel. (ex. A client opens an https tunnel and the server 84 | // chooses to open an http tunnel of the same name as well) 85 | type NewTunnel struct { 86 | ReqId string 87 | Url string 88 | Protocol string 89 | Error string 90 | } 91 | 92 | // When the server wants to initiate a new tunneled connection, it sends 93 | // this message over the control channel to the client. When a client receives 94 | // this message, it must initiate a new proxy connection to the server. 95 | type ReqProxy struct { 96 | } 97 | 98 | // After a client receives a ReqProxy message, it opens a new 99 | // connection to the server and sends a RegProxy message. 100 | type RegProxy struct { 101 | ClientId string 102 | } 103 | 104 | // This message is sent by the server to the client over a *proxy* connection before it 105 | // begins to send the bytes of the proxied request. 106 | type StartProxy struct { 107 | Url string // URL of the tunnel this connection connection is being proxied for 108 | ClientAddr string // Network address of the client initiating the connection to the tunnel 109 | } 110 | 111 | // A client or server may send this message periodically over 112 | // the control channel to request that the remote side acknowledge 113 | // its connection is still alive. The remote side must respond with a Pong. 114 | type Ping struct { 115 | } 116 | 117 | // Sent by a client or server over the control channel to indicate 118 | // it received a Ping. 119 | type Pong struct { 120 | } 121 | -------------------------------------------------------------------------------- /src/ngrok/msg/pack.go: -------------------------------------------------------------------------------- 1 | package msg 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | ) 9 | 10 | func unpack(buffer []byte, msgIn Message) (msg Message, err error) { 11 | var env Envelope 12 | if err = json.Unmarshal(buffer, &env); err != nil { 13 | return 14 | } 15 | 16 | if msgIn == nil { 17 | t, ok := TypeMap[env.Type] 18 | 19 | if !ok { 20 | err = errors.New(fmt.Sprintf("Unsupported message type %s", env.Type)) 21 | return 22 | } 23 | 24 | // guess type 25 | msg = reflect.New(t).Interface().(Message) 26 | } else { 27 | msg = msgIn 28 | } 29 | 30 | err = json.Unmarshal(env.Payload, &msg) 31 | return 32 | } 33 | 34 | func UnpackInto(buffer []byte, msg Message) (err error) { 35 | _, err = unpack(buffer, msg) 36 | return 37 | } 38 | 39 | func Unpack(buffer []byte) (msg Message, err error) { 40 | return unpack(buffer, nil) 41 | } 42 | 43 | func Pack(payload interface{}) ([]byte, error) { 44 | return json.Marshal(struct { 45 | Type string 46 | Payload interface{} 47 | }{ 48 | Type: reflect.TypeOf(payload).Elem().Name(), 49 | Payload: payload, 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /src/ngrok/proto/http.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "net/http/httputil" 11 | "net/url" 12 | "ngrok/conn" 13 | "ngrok/util" 14 | "strings" 15 | "sync" 16 | "time" 17 | 18 | metrics "github.com/rcrowley/go-metrics" 19 | ) 20 | 21 | type HttpRequest struct { 22 | *http.Request 23 | BodyBytes []byte 24 | } 25 | 26 | type HttpResponse struct { 27 | *http.Response 28 | BodyBytes []byte 29 | } 30 | 31 | type HttpTxn struct { 32 | Req *HttpRequest 33 | Resp *HttpResponse 34 | Start time.Time 35 | Duration time.Duration 36 | UserCtx interface{} 37 | ConnUserCtx interface{} 38 | } 39 | 40 | type Http struct { 41 | Txns *util.Broadcast 42 | reqGauge metrics.Gauge 43 | reqMeter metrics.Meter 44 | reqTimer metrics.Timer 45 | } 46 | 47 | func NewHttp() *Http { 48 | return &Http{ 49 | Txns: util.NewBroadcast(), 50 | reqGauge: metrics.NewGauge(), 51 | reqMeter: metrics.NewMeter(), 52 | reqTimer: metrics.NewTimer(), 53 | } 54 | } 55 | 56 | func extractBody(r io.Reader) ([]byte, io.ReadCloser, error) { 57 | buf := new(bytes.Buffer) 58 | _, err := buf.ReadFrom(r) 59 | return buf.Bytes(), ioutil.NopCloser(buf), err 60 | } 61 | 62 | func (h *Http) GetName() string { return "http" } 63 | 64 | func (h *Http) WrapConn(c conn.Conn, ctx interface{}) conn.Conn { 65 | tee := conn.NewTee(c) 66 | lastTxn := make(chan *HttpTxn) 67 | go h.readRequests(tee, lastTxn, ctx) 68 | go h.readResponses(tee, lastTxn) 69 | return tee 70 | } 71 | 72 | func (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn, connCtx interface{}) { 73 | defer close(lastTxn) 74 | 75 | for { 76 | req, err := http.ReadRequest(tee.WriteBuffer()) 77 | if err != nil { 78 | // no more requests to be read, we're done 79 | break 80 | } 81 | 82 | // make sure we read the body of the request so that 83 | // we don't block the writer 84 | _, err = httputil.DumpRequest(req, true) 85 | 86 | h.reqMeter.Mark(1) 87 | if err != nil { 88 | tee.Warn("Failed to extract request body: %v", err) 89 | } 90 | 91 | // golang's ReadRequest/DumpRequestOut is broken. Fix up the request so it works later 92 | req.URL.Scheme = "http" 93 | req.URL.Host = req.Host 94 | 95 | txn := &HttpTxn{Start: time.Now(), ConnUserCtx: connCtx} 96 | txn.Req = &HttpRequest{Request: req} 97 | if req.Body != nil { 98 | txn.Req.BodyBytes, txn.Req.Body, err = extractBody(req.Body) 99 | if err != nil { 100 | tee.Warn("Failed to extract request body: %v", err) 101 | } 102 | } 103 | 104 | lastTxn <- txn 105 | h.Txns.In() <- txn 106 | } 107 | } 108 | 109 | func (h *Http) readResponses(tee *conn.Tee, lastTxn chan *HttpTxn) { 110 | for txn := range lastTxn { 111 | resp, err := http.ReadResponse(tee.ReadBuffer(), txn.Req.Request) 112 | txn.Duration = time.Since(txn.Start) 113 | h.reqTimer.Update(txn.Duration) 114 | if err != nil { 115 | tee.Warn("Error reading response from server: %v", err) 116 | // no more responses to be read, we're done 117 | break 118 | } 119 | // make sure we read the body of the response so that 120 | // we don't block the reader 121 | _, _ = httputil.DumpResponse(resp, true) 122 | 123 | txn.Resp = &HttpResponse{Response: resp} 124 | // apparently, Body can be nil in some cases 125 | if resp.Body != nil { 126 | txn.Resp.BodyBytes, txn.Resp.Body, err = extractBody(resp.Body) 127 | if err != nil { 128 | tee.Warn("Failed to extract response body: %v", err) 129 | } 130 | } 131 | 132 | h.Txns.In() <- txn 133 | 134 | // XXX: remove web socket shim in favor of a real websocket protocol analyzer 135 | if txn.Req.Header.Get("Upgrade") == "websocket" { 136 | tee.Info("Upgrading to websocket") 137 | var wg sync.WaitGroup 138 | 139 | // shim for websockets 140 | // in order for websockets to work, we need to continue reading all of the 141 | // the bytes in the analyzer so that the joined connections will continue 142 | // sending bytes to each other 143 | wg.Add(2) 144 | go func() { 145 | ioutil.ReadAll(tee.WriteBuffer()) 146 | wg.Done() 147 | }() 148 | 149 | go func() { 150 | ioutil.ReadAll(tee.ReadBuffer()) 151 | wg.Done() 152 | }() 153 | 154 | wg.Wait() 155 | break 156 | } 157 | } 158 | } 159 | 160 | // we have to vendor DumpRequestOut because it's broken and the fix won't be in until at least 1.4 161 | // XXX: remove this all in favor of actually parsing the HTTP traffic ourselves for more transparent 162 | // replay and inspection, regardless of when it gets fixed in stdlib 163 | 164 | // Copyright 2009 The Go Authors. All rights reserved. 165 | // Use of this source code is governed by a BSD-style 166 | // license that can be found in the LICENSE file. 167 | 168 | // One of the copies, say from b to r2, could be avoided by using a more 169 | // elaborate trick where the other copy is made during Request/Response.Write. 170 | // This would complicate things too much, given that these functions are for 171 | // debugging only. 172 | func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { 173 | var buf bytes.Buffer 174 | if _, err = buf.ReadFrom(b); err != nil { 175 | return nil, nil, err 176 | } 177 | if err = b.Close(); err != nil { 178 | return nil, nil, err 179 | } 180 | return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil 181 | } 182 | 183 | // dumpConn is a net.Conn which writes to Writer and reads from Reader 184 | type dumpConn struct { 185 | io.Writer 186 | io.Reader 187 | } 188 | 189 | func (c *dumpConn) Close() error { return nil } 190 | func (c *dumpConn) LocalAddr() net.Addr { return nil } 191 | func (c *dumpConn) RemoteAddr() net.Addr { return nil } 192 | func (c *dumpConn) SetDeadline(t time.Time) error { return nil } 193 | func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil } 194 | func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil } 195 | 196 | type neverEnding byte 197 | 198 | func (b neverEnding) Read(p []byte) (n int, err error) { 199 | for i := range p { 200 | p[i] = byte(b) 201 | } 202 | return len(p), nil 203 | } 204 | 205 | // DumpRequestOut is like DumpRequest but includes 206 | // headers that the standard http.Transport adds, 207 | // such as User-Agent. 208 | func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { 209 | save := req.Body 210 | dummyBody := false 211 | if !body || req.Body == nil { 212 | req.Body = nil 213 | if req.ContentLength != 0 { 214 | req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength)) 215 | dummyBody = true 216 | } 217 | } else { 218 | var err error 219 | save, req.Body, err = drainBody(req.Body) 220 | if err != nil { 221 | return nil, err 222 | } 223 | } 224 | 225 | // Since we're using the actual Transport code to write the request, 226 | // switch to http so the Transport doesn't try to do an SSL 227 | // negotiation with our dumpConn and its bytes.Buffer & pipe. 228 | // The wire format for https and http are the same, anyway. 229 | reqSend := req 230 | if req.URL.Scheme == "https" { 231 | reqSend = new(http.Request) 232 | *reqSend = *req 233 | reqSend.URL = new(url.URL) 234 | *reqSend.URL = *req.URL 235 | reqSend.URL.Scheme = "http" 236 | } 237 | 238 | // Use the actual Transport code to record what we would send 239 | // on the wire, but not using TCP. Use a Transport with a 240 | // custom dialer that returns a fake net.Conn that waits 241 | // for the full input (and recording it), and then responds 242 | // with a dummy response. 243 | var buf bytes.Buffer // records the output 244 | pr, pw := io.Pipe() 245 | dr := &delegateReader{c: make(chan io.Reader)} 246 | // Wait for the request before replying with a dummy response: 247 | go func() { 248 | req, _ := http.ReadRequest(bufio.NewReader(pr)) 249 | // THIS IS THE PART THAT'S BROKEN IN THE STDLIB (as of Go 1.3) 250 | if req != nil && req.Body != nil { 251 | ioutil.ReadAll(req.Body) 252 | } 253 | dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n") 254 | }() 255 | 256 | t := &http.Transport{ 257 | Dial: func(net, addr string) (net.Conn, error) { 258 | return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil 259 | }, 260 | } 261 | defer t.CloseIdleConnections() 262 | 263 | _, err := t.RoundTrip(reqSend) 264 | 265 | req.Body = save 266 | if err != nil { 267 | return nil, err 268 | } 269 | dump := buf.Bytes() 270 | 271 | // If we used a dummy body above, remove it now. 272 | // TODO: if the req.ContentLength is large, we allocate memory 273 | // unnecessarily just to slice it off here. But this is just 274 | // a debug function, so this is acceptable for now. We could 275 | // discard the body earlier if this matters. 276 | if dummyBody { 277 | if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 { 278 | dump = dump[:i+4] 279 | } 280 | } 281 | return dump, nil 282 | } 283 | 284 | // delegateReader is a reader that delegates to another reader, 285 | // once it arrives on a channel. 286 | type delegateReader struct { 287 | c chan io.Reader 288 | r io.Reader // nil until received from c 289 | } 290 | 291 | func (r *delegateReader) Read(p []byte) (int, error) { 292 | if r.r == nil { 293 | r.r = <-r.c 294 | } 295 | return r.r.Read(p) 296 | } 297 | 298 | // Return value if nonempty, def otherwise. 299 | func valueOrDefault(value, def string) string { 300 | if value != "" { 301 | return value 302 | } 303 | return def 304 | } 305 | 306 | var reqWriteExcludeHeaderDump = map[string]bool{ 307 | "Host": true, // not in Header map anyway 308 | "Content-Length": true, 309 | "Transfer-Encoding": true, 310 | "Trailer": true, 311 | } 312 | -------------------------------------------------------------------------------- /src/ngrok/proto/interface.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "ngrok/conn" 5 | ) 6 | 7 | type Protocol interface { 8 | GetName() string 9 | WrapConn(conn.Conn, interface{}) conn.Conn 10 | } 11 | -------------------------------------------------------------------------------- /src/ngrok/proto/tcp.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "ngrok/conn" 5 | ) 6 | 7 | type Tcp struct{} 8 | 9 | func NewTcp() *Tcp { 10 | return new(Tcp) 11 | } 12 | 13 | func (h *Tcp) GetName() string { return "tcp" } 14 | 15 | func (h *Tcp) WrapConn(c conn.Conn, ctx interface{}) conn.Conn { 16 | return c 17 | } 18 | -------------------------------------------------------------------------------- /src/ngrok/server/cli.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type Options struct { 8 | httpAddr string 9 | httpsAddr string 10 | tunnelAddr string 11 | domain string 12 | tlsCrt string 13 | tlsKey string 14 | logto string 15 | loglevel string 16 | } 17 | 18 | func parseArgs() *Options { 19 | httpAddr := flag.String("httpAddr", ":80", "Public address for HTTP connections, empty string to disable") 20 | httpsAddr := flag.String("httpsAddr", ":443", "Public address listening for HTTPS connections, emptry string to disable") 21 | tunnelAddr := flag.String("tunnelAddr", ":4443", "Public address listening for ngrok client") 22 | domain := flag.String("domain", "ngrok.com", "Domain where the tunnels are hosted") 23 | tlsCrt := flag.String("tlsCrt", "", "Path to a TLS certificate file") 24 | tlsKey := flag.String("tlsKey", "", "Path to a TLS key file") 25 | logto := flag.String("log", "stdout", "Write log messages to this file. 'stdout' and 'none' have special meanings") 26 | loglevel := flag.String("log-level", "DEBUG", "The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR") 27 | flag.Parse() 28 | 29 | return &Options{ 30 | httpAddr: *httpAddr, 31 | httpsAddr: *httpsAddr, 32 | tunnelAddr: *tunnelAddr, 33 | domain: *domain, 34 | tlsCrt: *tlsCrt, 35 | tlsKey: *tlsKey, 36 | logto: *logto, 37 | loglevel: *loglevel, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ngrok/server/control.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "ngrok/conn" 7 | "ngrok/msg" 8 | "ngrok/util" 9 | "ngrok/version" 10 | "runtime/debug" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | const ( 16 | pingTimeoutInterval = 30 * time.Second 17 | connReapInterval = 10 * time.Second 18 | controlWriteTimeout = 10 * time.Second 19 | proxyStaleDuration = 60 * time.Second 20 | proxyMaxPoolSize = 10 21 | ) 22 | 23 | type Control struct { 24 | // auth message 25 | auth *msg.Auth 26 | 27 | // actual connection 28 | conn conn.Conn 29 | 30 | // put a message in this channel to send it over 31 | // conn to the client 32 | out chan (msg.Message) 33 | 34 | // read from this channel to get the next message sent 35 | // to us over conn by the client 36 | in chan (msg.Message) 37 | 38 | // the last time we received a ping from the client - for heartbeats 39 | lastPing time.Time 40 | 41 | // all of the tunnels this control connection handles 42 | tunnels []*Tunnel 43 | 44 | // proxy connections 45 | proxies chan conn.Conn 46 | 47 | // identifier 48 | id string 49 | 50 | // synchronizer for controlled shutdown of writer() 51 | writerShutdown *util.Shutdown 52 | 53 | // synchronizer for controlled shutdown of reader() 54 | readerShutdown *util.Shutdown 55 | 56 | // synchronizer for controlled shutdown of manager() 57 | managerShutdown *util.Shutdown 58 | 59 | // synchronizer for controller shutdown of entire Control 60 | shutdown *util.Shutdown 61 | } 62 | 63 | func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) { 64 | var err error 65 | 66 | // create the object 67 | c := &Control{ 68 | auth: authMsg, 69 | conn: ctlConn, 70 | out: make(chan msg.Message), 71 | in: make(chan msg.Message), 72 | proxies: make(chan conn.Conn, 10), 73 | lastPing: time.Now(), 74 | writerShutdown: util.NewShutdown(), 75 | readerShutdown: util.NewShutdown(), 76 | managerShutdown: util.NewShutdown(), 77 | shutdown: util.NewShutdown(), 78 | } 79 | 80 | failAuth := func(e error) { 81 | _ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()}) 82 | ctlConn.Close() 83 | } 84 | 85 | // register the clientid 86 | c.id = authMsg.ClientId 87 | if c.id == "" { 88 | // it's a new session, assign an ID 89 | if c.id, err = util.SecureRandId(16); err != nil { 90 | failAuth(err) 91 | return 92 | } 93 | } 94 | 95 | // set logging prefix 96 | ctlConn.SetType("ctl") 97 | ctlConn.AddLogPrefix(c.id) 98 | 99 | if authMsg.Version != version.Proto { 100 | failAuth(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), authMsg.Version)) 101 | return 102 | } 103 | 104 | // register the control 105 | if replaced := controlRegistry.Add(c.id, c); replaced != nil { 106 | replaced.shutdown.WaitComplete() 107 | } 108 | 109 | // start the writer first so that the following messages get sent 110 | go c.writer() 111 | 112 | // Respond to authentication 113 | c.out <- &msg.AuthResp{ 114 | Version: version.Proto, 115 | MmVersion: version.MajorMinor(), 116 | ClientId: c.id, 117 | } 118 | 119 | // As a performance optimization, ask for a proxy connection up front 120 | c.out <- &msg.ReqProxy{} 121 | 122 | // manage the connection 123 | go c.manager() 124 | go c.reader() 125 | go c.stopper() 126 | } 127 | 128 | // Register a new tunnel on this control connection 129 | func (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) { 130 | for _, proto := range strings.Split(rawTunnelReq.Protocol, "+") { 131 | tunnelReq := *rawTunnelReq 132 | tunnelReq.Protocol = proto 133 | 134 | c.conn.Debug("Registering new tunnel") 135 | t, err := NewTunnel(&tunnelReq, c) 136 | if err != nil { 137 | c.out <- &msg.NewTunnel{Error: err.Error()} 138 | if len(c.tunnels) == 0 { 139 | c.shutdown.Begin() 140 | } 141 | 142 | // we're done 143 | return 144 | } 145 | 146 | // add it to the list of tunnels 147 | c.tunnels = append(c.tunnels, t) 148 | 149 | // acknowledge success 150 | c.out <- &msg.NewTunnel{ 151 | Url: t.url, 152 | Protocol: proto, 153 | ReqId: rawTunnelReq.ReqId, 154 | } 155 | 156 | rawTunnelReq.Hostname = strings.Replace(t.url, proto+"://", "", 1) 157 | } 158 | } 159 | 160 | func (c *Control) manager() { 161 | // don't crash on panics 162 | defer func() { 163 | if err := recover(); err != nil { 164 | c.conn.Info("Control::manager failed with error %v: %s", err, debug.Stack()) 165 | } 166 | }() 167 | 168 | // kill everything if the control manager stops 169 | defer c.shutdown.Begin() 170 | 171 | // notify that manager() has shutdown 172 | defer c.managerShutdown.Complete() 173 | 174 | // reaping timer for detecting heartbeat failure 175 | reap := time.NewTicker(connReapInterval) 176 | defer reap.Stop() 177 | 178 | for { 179 | select { 180 | case <-reap.C: 181 | if time.Since(c.lastPing) > pingTimeoutInterval { 182 | c.conn.Info("Lost heartbeat") 183 | c.shutdown.Begin() 184 | } 185 | 186 | case mRaw, ok := <-c.in: 187 | // c.in closes to indicate shutdown 188 | if !ok { 189 | return 190 | } 191 | 192 | switch m := mRaw.(type) { 193 | case *msg.ReqTunnel: 194 | c.registerTunnel(m) 195 | 196 | case *msg.Ping: 197 | c.lastPing = time.Now() 198 | c.out <- &msg.Pong{} 199 | } 200 | } 201 | } 202 | } 203 | 204 | func (c *Control) writer() { 205 | defer func() { 206 | if err := recover(); err != nil { 207 | c.conn.Info("Control::writer failed with error %v: %s", err, debug.Stack()) 208 | } 209 | }() 210 | 211 | // kill everything if the writer() stops 212 | defer c.shutdown.Begin() 213 | 214 | // notify that we've flushed all messages 215 | defer c.writerShutdown.Complete() 216 | 217 | // write messages to the control channel 218 | for m := range c.out { 219 | c.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout)) 220 | if err := msg.WriteMsg(c.conn, m); err != nil { 221 | panic(err) 222 | } 223 | } 224 | } 225 | 226 | func (c *Control) reader() { 227 | defer func() { 228 | if err := recover(); err != nil { 229 | c.conn.Warn("Control::reader failed with error %v: %s", err, debug.Stack()) 230 | } 231 | }() 232 | 233 | // kill everything if the reader stops 234 | defer c.shutdown.Begin() 235 | 236 | // notify that we're done 237 | defer c.readerShutdown.Complete() 238 | 239 | // read messages from the control channel 240 | for { 241 | if msg, err := msg.ReadMsg(c.conn); err != nil { 242 | if err == io.EOF { 243 | c.conn.Info("EOF") 244 | return 245 | } else { 246 | panic(err) 247 | } 248 | } else { 249 | // this can also panic during shutdown 250 | c.in <- msg 251 | } 252 | } 253 | } 254 | 255 | func (c *Control) stopper() { 256 | defer func() { 257 | if r := recover(); r != nil { 258 | c.conn.Error("Failed to shut down control: %v", r) 259 | } 260 | }() 261 | 262 | // wait until we're instructed to shutdown 263 | c.shutdown.WaitBegin() 264 | 265 | // remove ourself from the control registry 266 | controlRegistry.Del(c.id) 267 | 268 | // shutdown manager() so that we have no more work to do 269 | close(c.in) 270 | c.managerShutdown.WaitComplete() 271 | 272 | // shutdown writer() 273 | close(c.out) 274 | c.writerShutdown.WaitComplete() 275 | 276 | // close connection fully 277 | c.conn.Close() 278 | 279 | // shutdown all of the tunnels 280 | for _, t := range c.tunnels { 281 | t.Shutdown() 282 | } 283 | 284 | // shutdown all of the proxy connections 285 | close(c.proxies) 286 | for p := range c.proxies { 287 | p.Close() 288 | } 289 | 290 | c.shutdown.Complete() 291 | c.conn.Info("Shutdown complete") 292 | } 293 | 294 | func (c *Control) RegisterProxy(conn conn.Conn) { 295 | conn.AddLogPrefix(c.id) 296 | 297 | conn.SetDeadline(time.Now().Add(proxyStaleDuration)) 298 | select { 299 | case c.proxies <- conn: 300 | conn.Info("Registered") 301 | default: 302 | conn.Info("Proxies buffer is full, discarding.") 303 | conn.Close() 304 | } 305 | } 306 | 307 | // Remove a proxy connection from the pool and return it 308 | // If not proxy connections are in the pool, request one 309 | // and wait until it is available 310 | // Returns an error if we couldn't get a proxy because it took too long 311 | // or the tunnel is closing 312 | func (c *Control) GetProxy() (proxyConn conn.Conn, err error) { 313 | var ok bool 314 | 315 | // get a proxy connection from the pool 316 | select { 317 | case proxyConn, ok = <-c.proxies: 318 | if !ok { 319 | err = fmt.Errorf("No proxy connections available, control is closing") 320 | return 321 | } 322 | default: 323 | // no proxy available in the pool, ask for one over the control channel 324 | c.conn.Debug("No proxy in pool, requesting proxy from control . . .") 325 | if err = util.PanicToError(func() { c.out <- &msg.ReqProxy{} }); err != nil { 326 | return 327 | } 328 | 329 | select { 330 | case proxyConn, ok = <-c.proxies: 331 | if !ok { 332 | err = fmt.Errorf("No proxy connections available, control is closing") 333 | return 334 | } 335 | 336 | case <-time.After(pingTimeoutInterval): 337 | err = fmt.Errorf("Timeout trying to get proxy connection") 338 | return 339 | } 340 | } 341 | return 342 | } 343 | 344 | // Called when this control is replaced by another control 345 | // this can happen if the network drops out and the client reconnects 346 | // before the old tunnel has lost its heartbeat 347 | func (c *Control) Replaced(replacement *Control) { 348 | c.conn.Info("Replaced by control: %s", replacement.conn.Id()) 349 | 350 | // set the control id to empty string so that when stopper() 351 | // calls registry.Del it won't delete the replacement 352 | c.id = "" 353 | 354 | // tell the old one to shutdown 355 | c.shutdown.Begin() 356 | } 357 | -------------------------------------------------------------------------------- /src/ngrok/server/http.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | vhost "github.com/inconshreveable/go-vhost" 7 | //"net" 8 | "ngrok/conn" 9 | "ngrok/log" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | const ( 15 | NotAuthorized = `HTTP/1.0 401 Not Authorized 16 | WWW-Authenticate: Basic realm="ngrok" 17 | Content-Length: 23 18 | 19 | Authorization required 20 | ` 21 | 22 | NotFound = `HTTP/1.0 404 Not Found 23 | Content-Length: %d 24 | 25 | Tunnel %s not found 26 | ` 27 | 28 | BadRequest = `HTTP/1.0 400 Bad Request 29 | Content-Length: 12 30 | 31 | Bad Request 32 | ` 33 | ) 34 | 35 | // Listens for new http(s) connections from the public internet 36 | func startHttpListener(addr string, tlsCfg *tls.Config) (listener *conn.Listener) { 37 | // bind/listen for incoming connections 38 | var err error 39 | if listener, err = conn.Listen(addr, "pub", tlsCfg); err != nil { 40 | panic(err) 41 | } 42 | 43 | proto := "http" 44 | if tlsCfg != nil { 45 | proto = "https" 46 | } 47 | 48 | log.Info("Listening for public %s connections on %v", proto, listener.Addr.String()) 49 | go func() { 50 | for conn := range listener.Conns { 51 | go httpHandler(conn, proto) 52 | } 53 | }() 54 | 55 | return 56 | } 57 | 58 | // Handles a new http connection from the public internet 59 | func httpHandler(c conn.Conn, proto string) { 60 | defer c.Close() 61 | defer func() { 62 | // recover from failures 63 | if r := recover(); r != nil { 64 | c.Warn("httpHandler failed with error %v", r) 65 | } 66 | }() 67 | 68 | // Make sure we detect dead connections while we decide how to multiplex 69 | c.SetDeadline(time.Now().Add(connReadTimeout)) 70 | 71 | // multiplex by extracting the Host header, the vhost library 72 | vhostConn, err := vhost.HTTP(c) 73 | if err != nil { 74 | c.Warn("Failed to read valid %s request: %v", proto, err) 75 | c.Write([]byte(BadRequest)) 76 | return 77 | } 78 | 79 | // read out the Host header and auth from the request 80 | host := strings.ToLower(vhostConn.Host()) 81 | auth := vhostConn.Request.Header.Get("Authorization") 82 | 83 | // done reading mux data, free up the request memory 84 | vhostConn.Free() 85 | 86 | // We need to read from the vhost conn now since it mucked around reading the stream 87 | c = conn.Wrap(vhostConn, "pub") 88 | 89 | // multiplex to find the right backend host 90 | c.Debug("Found hostname %s in request", host) 91 | tunnel := tunnelRegistry.Get(fmt.Sprintf("%s://%s", proto, host)) 92 | if tunnel == nil { 93 | c.Info("No tunnel found for hostname %s", host) 94 | c.Write([]byte(fmt.Sprintf(NotFound, len(host)+18, host))) 95 | return 96 | } 97 | 98 | // If the client specified http auth and it doesn't match this request's auth 99 | // then fail the request with 401 Not Authorized and request the client reissue the 100 | // request with basic authdeny the request 101 | if tunnel.req.HttpAuth != "" && auth != tunnel.req.HttpAuth { 102 | c.Info("Authentication failed: %s", auth) 103 | c.Write([]byte(NotAuthorized)) 104 | return 105 | } 106 | 107 | // dead connections will now be handled by tunnel heartbeating and the client 108 | c.SetDeadline(time.Time{}) 109 | 110 | // let the tunnel handle the connection now 111 | tunnel.HandlePublicConnection(c) 112 | } 113 | -------------------------------------------------------------------------------- /src/ngrok/server/main.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "math/rand" 6 | "ngrok/conn" 7 | log "ngrok/log" 8 | "ngrok/msg" 9 | "ngrok/util" 10 | "os" 11 | "runtime/debug" 12 | "time" 13 | ) 14 | 15 | const ( 16 | registryCacheSize uint64 = 1024 * 1024 // 1 MB 17 | connReadTimeout time.Duration = 10 * time.Second 18 | ) 19 | 20 | // GLOBALS 21 | var ( 22 | tunnelRegistry *TunnelRegistry 23 | controlRegistry *ControlRegistry 24 | 25 | // XXX: kill these global variables - they're only used in tunnel.go for constructing forwarding URLs 26 | opts *Options 27 | listeners map[string]*conn.Listener 28 | ) 29 | 30 | func NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxy) { 31 | // fail gracefully if the proxy connection fails to register 32 | defer func() { 33 | if r := recover(); r != nil { 34 | pxyConn.Warn("Failed with error: %v", r) 35 | pxyConn.Close() 36 | } 37 | }() 38 | 39 | // set logging prefix 40 | pxyConn.SetType("pxy") 41 | 42 | // look up the control connection for this proxy 43 | pxyConn.Info("Registering new proxy for %s", regPxy.ClientId) 44 | ctl := controlRegistry.Get(regPxy.ClientId) 45 | 46 | if ctl == nil { 47 | panic("No client found for identifier: " + regPxy.ClientId) 48 | } 49 | 50 | ctl.RegisterProxy(pxyConn) 51 | } 52 | 53 | // Listen for incoming control and proxy connections 54 | // We listen for incoming control and proxy connections on the same port 55 | // for ease of deployment. The hope is that by running on port 443, using 56 | // TLS and running all connections over the same port, we can bust through 57 | // restrictive firewalls. 58 | func tunnelListener(addr string, tlsConfig *tls.Config) { 59 | // listen for incoming connections 60 | listener, err := conn.Listen(addr, "tun", tlsConfig) 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | log.Info("Listening for control and proxy connections on %s", listener.Addr.String()) 66 | for c := range listener.Conns { 67 | go func(tunnelConn conn.Conn) { 68 | // don't crash on panics 69 | defer func() { 70 | if r := recover(); r != nil { 71 | tunnelConn.Info("tunnelListener failed with error %v: %s", r, debug.Stack()) 72 | } 73 | }() 74 | 75 | tunnelConn.SetReadDeadline(time.Now().Add(connReadTimeout)) 76 | var rawMsg msg.Message 77 | if rawMsg, err = msg.ReadMsg(tunnelConn); err != nil { 78 | tunnelConn.Warn("Failed to read message: %v", err) 79 | tunnelConn.Close() 80 | return 81 | } 82 | 83 | // don't timeout after the initial read, tunnel heartbeating will kill 84 | // dead connections 85 | tunnelConn.SetReadDeadline(time.Time{}) 86 | 87 | switch m := rawMsg.(type) { 88 | case *msg.Auth: 89 | NewControl(tunnelConn, m) 90 | 91 | case *msg.RegProxy: 92 | NewProxy(tunnelConn, m) 93 | 94 | default: 95 | tunnelConn.Close() 96 | } 97 | }(c) 98 | } 99 | } 100 | 101 | func Main() { 102 | // parse options 103 | opts = parseArgs() 104 | 105 | // init logging 106 | log.LogTo(opts.logto, opts.loglevel) 107 | 108 | // seed random number generator 109 | seed, err := util.RandomSeed() 110 | if err != nil { 111 | panic(err) 112 | } 113 | rand.Seed(seed) 114 | 115 | // init tunnel/control registry 116 | registryCacheFile := os.Getenv("REGISTRY_CACHE_FILE") 117 | tunnelRegistry = NewTunnelRegistry(registryCacheSize, registryCacheFile) 118 | controlRegistry = NewControlRegistry() 119 | 120 | // start listeners 121 | listeners = make(map[string]*conn.Listener) 122 | 123 | // load tls configuration 124 | tlsConfig, err := LoadTLSConfig(opts.tlsCrt, opts.tlsKey) 125 | if err != nil { 126 | panic(err) 127 | } 128 | 129 | // listen for http 130 | if opts.httpAddr != "" { 131 | listeners["http"] = startHttpListener(opts.httpAddr, nil) 132 | } 133 | 134 | // listen for https 135 | if opts.httpsAddr != "" { 136 | listeners["https"] = startHttpListener(opts.httpsAddr, tlsConfig) 137 | } 138 | 139 | // ngrok clients 140 | tunnelListener(opts.tunnelAddr, tlsConfig) 141 | } 142 | -------------------------------------------------------------------------------- /src/ngrok/server/metrics.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | gometrics "github.com/rcrowley/go-metrics" 8 | "io/ioutil" 9 | "net/http" 10 | "ngrok/conn" 11 | "ngrok/log" 12 | "os" 13 | "time" 14 | ) 15 | 16 | var metrics Metrics 17 | 18 | func init() { 19 | keenApiKey := os.Getenv("KEEN_API_KEY") 20 | 21 | if keenApiKey != "" { 22 | metrics = NewKeenIoMetrics(60 * time.Second) 23 | } else { 24 | metrics = NewLocalMetrics(30 * time.Second) 25 | } 26 | } 27 | 28 | type Metrics interface { 29 | log.Logger 30 | OpenConnection(*Tunnel, conn.Conn) 31 | CloseConnection(*Tunnel, conn.Conn, time.Time, int64, int64) 32 | OpenTunnel(*Tunnel) 33 | CloseTunnel(*Tunnel) 34 | } 35 | 36 | type LocalMetrics struct { 37 | log.Logger 38 | reportInterval time.Duration 39 | windowsCounter gometrics.Counter 40 | linuxCounter gometrics.Counter 41 | osxCounter gometrics.Counter 42 | otherCounter gometrics.Counter 43 | 44 | tunnelMeter gometrics.Meter 45 | tcpTunnelMeter gometrics.Meter 46 | httpTunnelMeter gometrics.Meter 47 | connMeter gometrics.Meter 48 | lostHeartbeatMeter gometrics.Meter 49 | 50 | connTimer gometrics.Timer 51 | 52 | bytesInCount gometrics.Counter 53 | bytesOutCount gometrics.Counter 54 | 55 | /* 56 | tunnelGauge gometrics.Gauge 57 | tcpTunnelGauge gometrics.Gauge 58 | connGauge gometrics.Gauge 59 | */ 60 | } 61 | 62 | func NewLocalMetrics(reportInterval time.Duration) *LocalMetrics { 63 | metrics := LocalMetrics{ 64 | Logger: log.NewPrefixLogger("metrics"), 65 | reportInterval: reportInterval, 66 | windowsCounter: gometrics.NewCounter(), 67 | linuxCounter: gometrics.NewCounter(), 68 | osxCounter: gometrics.NewCounter(), 69 | otherCounter: gometrics.NewCounter(), 70 | 71 | tunnelMeter: gometrics.NewMeter(), 72 | tcpTunnelMeter: gometrics.NewMeter(), 73 | httpTunnelMeter: gometrics.NewMeter(), 74 | connMeter: gometrics.NewMeter(), 75 | lostHeartbeatMeter: gometrics.NewMeter(), 76 | 77 | connTimer: gometrics.NewTimer(), 78 | 79 | bytesInCount: gometrics.NewCounter(), 80 | bytesOutCount: gometrics.NewCounter(), 81 | 82 | /* 83 | metrics.tunnelGauge = gometrics.NewGauge(), 84 | metrics.tcpTunnelGauge = gometrics.NewGauge(), 85 | metrics.connGauge = gometrics.NewGauge(), 86 | */ 87 | } 88 | 89 | go metrics.Report() 90 | 91 | return &metrics 92 | } 93 | 94 | func (m *LocalMetrics) OpenTunnel(t *Tunnel) { 95 | m.tunnelMeter.Mark(1) 96 | 97 | switch t.ctl.auth.OS { 98 | case "windows": 99 | m.windowsCounter.Inc(1) 100 | case "linux": 101 | m.linuxCounter.Inc(1) 102 | case "darwin": 103 | m.osxCounter.Inc(1) 104 | default: 105 | m.otherCounter.Inc(1) 106 | } 107 | 108 | switch t.req.Protocol { 109 | case "tcp": 110 | m.tcpTunnelMeter.Mark(1) 111 | case "http": 112 | m.httpTunnelMeter.Mark(1) 113 | } 114 | } 115 | 116 | func (m *LocalMetrics) CloseTunnel(t *Tunnel) { 117 | } 118 | 119 | func (m *LocalMetrics) OpenConnection(t *Tunnel, c conn.Conn) { 120 | m.connMeter.Mark(1) 121 | } 122 | 123 | func (m *LocalMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time, bytesIn, bytesOut int64) { 124 | m.bytesInCount.Inc(bytesIn) 125 | m.bytesOutCount.Inc(bytesOut) 126 | } 127 | 128 | func (m *LocalMetrics) Report() { 129 | m.Info("Reporting every %d seconds", int(m.reportInterval.Seconds())) 130 | 131 | for { 132 | time.Sleep(m.reportInterval) 133 | buffer, err := json.Marshal(map[string]interface{}{ 134 | "windows": m.windowsCounter.Count(), 135 | "linux": m.linuxCounter.Count(), 136 | "osx": m.osxCounter.Count(), 137 | "other": m.otherCounter.Count(), 138 | "httpTunnelMeter.count": m.httpTunnelMeter.Count(), 139 | "tcpTunnelMeter.count": m.tcpTunnelMeter.Count(), 140 | "tunnelMeter.count": m.tunnelMeter.Count(), 141 | "tunnelMeter.m1": m.tunnelMeter.Rate1(), 142 | "connMeter.count": m.connMeter.Count(), 143 | "connMeter.m1": m.connMeter.Rate1(), 144 | "bytesIn.count": m.bytesInCount.Count(), 145 | "bytesOut.count": m.bytesOutCount.Count(), 146 | }) 147 | 148 | if err != nil { 149 | m.Error("Failed to serialize metrics: %v", err) 150 | continue 151 | } 152 | 153 | m.Info("Reporting: %s", buffer) 154 | } 155 | } 156 | 157 | type KeenIoMetric struct { 158 | Collection string 159 | Event interface{} 160 | } 161 | 162 | type KeenIoMetrics struct { 163 | log.Logger 164 | ApiKey string 165 | ProjectToken string 166 | HttpClient http.Client 167 | Metrics chan *KeenIoMetric 168 | } 169 | 170 | func NewKeenIoMetrics(batchInterval time.Duration) *KeenIoMetrics { 171 | k := &KeenIoMetrics{ 172 | Logger: log.NewPrefixLogger("metrics"), 173 | ApiKey: os.Getenv("KEEN_API_KEY"), 174 | ProjectToken: os.Getenv("KEEN_PROJECT_TOKEN"), 175 | Metrics: make(chan *KeenIoMetric, 1000), 176 | } 177 | 178 | go func() { 179 | defer func() { 180 | if r := recover(); r != nil { 181 | k.Error("KeenIoMetrics failed: %v", r) 182 | } 183 | }() 184 | 185 | batch := make(map[string][]interface{}) 186 | batchTimer := time.Tick(batchInterval) 187 | 188 | for { 189 | select { 190 | case m := <-k.Metrics: 191 | list, ok := batch[m.Collection] 192 | if !ok { 193 | list = make([]interface{}, 0) 194 | } 195 | batch[m.Collection] = append(list, m.Event) 196 | 197 | case <-batchTimer: 198 | // no metrics to report 199 | if len(batch) == 0 { 200 | continue 201 | } 202 | 203 | payload, err := json.Marshal(batch) 204 | if err != nil { 205 | k.Error("Failed to serialize metrics payload: %v, %v", batch, err) 206 | } else { 207 | for key, val := range batch { 208 | k.Debug("Reporting %d metrics for %s", len(val), key) 209 | } 210 | 211 | k.AuthedRequest("POST", "/events", bytes.NewReader(payload)) 212 | } 213 | batch = make(map[string][]interface{}) 214 | } 215 | } 216 | }() 217 | 218 | return k 219 | } 220 | 221 | func (k *KeenIoMetrics) AuthedRequest(method, path string, body *bytes.Reader) (resp *http.Response, err error) { 222 | path = fmt.Sprintf("https://api.keen.io/3.0/projects/%s%s", k.ProjectToken, path) 223 | req, err := http.NewRequest(method, path, body) 224 | if err != nil { 225 | return 226 | } 227 | 228 | req.Header.Add("Authorization", k.ApiKey) 229 | 230 | if body != nil { 231 | req.Header.Add("Content-Type", "application/json") 232 | req.ContentLength = int64(body.Len()) 233 | } 234 | 235 | requestStartAt := time.Now() 236 | resp, err = k.HttpClient.Do(req) 237 | 238 | if err != nil { 239 | k.Error("Failed to send metric event to keen.io %v", err) 240 | } else { 241 | k.Info("keen.io processed request in %f sec", time.Since(requestStartAt).Seconds()) 242 | defer resp.Body.Close() 243 | if resp.StatusCode != 200 { 244 | bytes, _ := ioutil.ReadAll(resp.Body) 245 | k.Error("Got %v response from keen.io: %s", resp.StatusCode, bytes) 246 | } 247 | } 248 | 249 | return 250 | } 251 | 252 | func (k *KeenIoMetrics) OpenConnection(t *Tunnel, c conn.Conn) { 253 | } 254 | 255 | func (k *KeenIoMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time, in, out int64) { 256 | event := struct { 257 | Keen KeenStruct `json:"keen"` 258 | OS string 259 | ClientId string 260 | Protocol string 261 | Url string 262 | User string 263 | Version string 264 | Reason string 265 | HttpAuth bool 266 | Subdomain bool 267 | TunnelDuration float64 268 | ConnectionDuration float64 269 | BytesIn int64 270 | BytesOut int64 271 | }{ 272 | Keen: KeenStruct{ 273 | Timestamp: start.UTC().Format("2006-01-02T15:04:05.000Z"), 274 | }, 275 | OS: t.ctl.auth.OS, 276 | ClientId: t.ctl.id, 277 | Protocol: t.req.Protocol, 278 | Url: t.url, 279 | User: t.ctl.auth.User, 280 | Version: t.ctl.auth.MmVersion, 281 | HttpAuth: t.req.HttpAuth != "", 282 | Subdomain: t.req.Subdomain != "", 283 | TunnelDuration: time.Since(t.start).Seconds(), 284 | ConnectionDuration: time.Since(start).Seconds(), 285 | BytesIn: in, 286 | BytesOut: out, 287 | } 288 | 289 | k.Metrics <- &KeenIoMetric{Collection: "CloseConnection", Event: event} 290 | } 291 | 292 | func (k *KeenIoMetrics) OpenTunnel(t *Tunnel) { 293 | } 294 | 295 | type KeenStruct struct { 296 | Timestamp string `json:"timestamp"` 297 | } 298 | 299 | func (k *KeenIoMetrics) CloseTunnel(t *Tunnel) { 300 | event := struct { 301 | Keen KeenStruct `json:"keen"` 302 | OS string 303 | ClientId string 304 | Protocol string 305 | Url string 306 | User string 307 | Version string 308 | Reason string 309 | Duration float64 310 | HttpAuth bool 311 | Subdomain bool 312 | }{ 313 | Keen: KeenStruct{ 314 | Timestamp: t.start.UTC().Format("2006-01-02T15:04:05.000Z"), 315 | }, 316 | OS: t.ctl.auth.OS, 317 | ClientId: t.ctl.id, 318 | Protocol: t.req.Protocol, 319 | Url: t.url, 320 | User: t.ctl.auth.User, 321 | Version: t.ctl.auth.MmVersion, 322 | //Reason: reason, 323 | Duration: time.Since(t.start).Seconds(), 324 | HttpAuth: t.req.HttpAuth != "", 325 | Subdomain: t.req.Subdomain != "", 326 | } 327 | 328 | k.Metrics <- &KeenIoMetric{Collection: "CloseTunnel", Event: event} 329 | } 330 | -------------------------------------------------------------------------------- /src/ngrok/server/registry.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "net" 7 | "ngrok/cache" 8 | "ngrok/log" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | const ( 14 | cacheSaveInterval time.Duration = 10 * time.Minute 15 | ) 16 | 17 | type cacheUrl string 18 | 19 | func (url cacheUrl) Size() int { 20 | return len(url) 21 | } 22 | 23 | // TunnelRegistry maps a tunnel URL to Tunnel structures 24 | type TunnelRegistry struct { 25 | tunnels map[string]*Tunnel 26 | affinity *cache.LRUCache 27 | log.Logger 28 | sync.RWMutex 29 | } 30 | 31 | func NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegistry { 32 | registry := &TunnelRegistry{ 33 | tunnels: make(map[string]*Tunnel), 34 | affinity: cache.NewLRUCache(cacheSize), 35 | Logger: log.NewPrefixLogger("registry", "tun"), 36 | } 37 | 38 | // LRUCache uses Gob encoding. Unfortunately, Gob is fickle and will fail 39 | // to encode or decode any non-primitive types that haven't been "registered" 40 | // with it. Since we store cacheUrl objects, we need to register them here first 41 | // for the encoding/decoding to work 42 | var urlobj cacheUrl 43 | gob.Register(urlobj) 44 | 45 | // try to load and then periodically save the affinity cache to file, if specified 46 | if cacheFile != "" { 47 | err := registry.affinity.LoadItemsFromFile(cacheFile) 48 | if err != nil { 49 | registry.Error("Failed to load affinity cache %s: %v", cacheFile, err) 50 | } 51 | 52 | registry.SaveCacheThread(cacheFile, cacheSaveInterval) 53 | } else { 54 | registry.Info("No affinity cache specified") 55 | } 56 | 57 | return registry 58 | } 59 | 60 | // Spawns a goroutine the periodically saves the cache to a file. 61 | func (r *TunnelRegistry) SaveCacheThread(path string, interval time.Duration) { 62 | go func() { 63 | r.Info("Saving affinity cache to %s every %s", path, interval.String()) 64 | for { 65 | time.Sleep(interval) 66 | 67 | r.Debug("Saving affinity cache") 68 | err := r.affinity.SaveItemsToFile(path) 69 | if err != nil { 70 | r.Error("Failed to save affinity cache: %v", err) 71 | } else { 72 | r.Info("Saved affinity cache") 73 | } 74 | } 75 | }() 76 | } 77 | 78 | // Register a tunnel with a specific url, returns an error 79 | // if a tunnel is already registered at that url 80 | func (r *TunnelRegistry) Register(url string, t *Tunnel) error { 81 | r.Lock() 82 | defer r.Unlock() 83 | 84 | if r.tunnels[url] != nil { 85 | return fmt.Errorf("The tunnel %s is already registered.", url) 86 | } 87 | 88 | r.tunnels[url] = t 89 | 90 | return nil 91 | } 92 | 93 | func (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) { 94 | clientIp := t.ctl.conn.RemoteAddr().(*net.TCPAddr).IP.String() 95 | clientId := t.ctl.id 96 | 97 | ipKey := fmt.Sprintf("client-ip-%s:%s", t.req.Protocol, clientIp) 98 | idKey := fmt.Sprintf("client-id-%s:%s", t.req.Protocol, clientId) 99 | return ipKey, idKey 100 | } 101 | 102 | func (r *TunnelRegistry) GetCachedRegistration(t *Tunnel) (url string) { 103 | ipCacheKey, idCacheKey := r.cacheKeys(t) 104 | 105 | // check cache for ID first, because we prefer that over IP which might 106 | // not be specific to a user because of NATs 107 | if v, ok := r.affinity.Get(idCacheKey); ok { 108 | url = string(v.(cacheUrl)) 109 | t.Debug("Found registry affinity %s for %s", url, idCacheKey) 110 | } else if v, ok := r.affinity.Get(ipCacheKey); ok { 111 | url = string(v.(cacheUrl)) 112 | t.Debug("Found registry affinity %s for %s", url, ipCacheKey) 113 | } 114 | return 115 | } 116 | 117 | func (r *TunnelRegistry) RegisterAndCache(url string, t *Tunnel) (err error) { 118 | if err = r.Register(url, t); err == nil { 119 | // we successfully assigned a url, cache it 120 | ipCacheKey, idCacheKey := r.cacheKeys(t) 121 | r.affinity.Set(ipCacheKey, cacheUrl(url)) 122 | r.affinity.Set(idCacheKey, cacheUrl(url)) 123 | } 124 | return 125 | 126 | } 127 | 128 | // Register a tunnel with the following process: 129 | // Consult the affinity cache to try to assign a previously used tunnel url if possible 130 | // Generate new urls repeatedly with the urlFn and register until one is available. 131 | func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) (string, error) { 132 | url := r.GetCachedRegistration(t) 133 | if url == "" { 134 | url = urlFn() 135 | } 136 | 137 | maxAttempts := 5 138 | for i := 0; i < maxAttempts; i++ { 139 | if err := r.RegisterAndCache(url, t); err != nil { 140 | // pick a new url and try again 141 | url = urlFn() 142 | } else { 143 | // we successfully assigned a url, we're done 144 | return url, nil 145 | } 146 | } 147 | 148 | return "", fmt.Errorf("Failed to assign a URL after %d attempts!", maxAttempts) 149 | } 150 | 151 | func (r *TunnelRegistry) Del(url string) { 152 | r.Lock() 153 | defer r.Unlock() 154 | delete(r.tunnels, url) 155 | } 156 | 157 | func (r *TunnelRegistry) Get(url string) *Tunnel { 158 | r.RLock() 159 | defer r.RUnlock() 160 | return r.tunnels[url] 161 | } 162 | 163 | // ControlRegistry maps a client ID to Control structures 164 | type ControlRegistry struct { 165 | controls map[string]*Control 166 | log.Logger 167 | sync.RWMutex 168 | } 169 | 170 | func NewControlRegistry() *ControlRegistry { 171 | return &ControlRegistry{ 172 | controls: make(map[string]*Control), 173 | Logger: log.NewPrefixLogger("registry", "ctl"), 174 | } 175 | } 176 | 177 | func (r *ControlRegistry) Get(clientId string) *Control { 178 | r.RLock() 179 | defer r.RUnlock() 180 | return r.controls[clientId] 181 | } 182 | 183 | func (r *ControlRegistry) Add(clientId string, ctl *Control) (oldCtl *Control) { 184 | r.Lock() 185 | defer r.Unlock() 186 | 187 | oldCtl = r.controls[clientId] 188 | if oldCtl != nil { 189 | oldCtl.Replaced(ctl) 190 | } 191 | 192 | r.controls[clientId] = ctl 193 | r.Info("Registered control with id %s", clientId) 194 | return 195 | } 196 | 197 | func (r *ControlRegistry) Del(clientId string) error { 198 | r.Lock() 199 | defer r.Unlock() 200 | if r.controls[clientId] == nil { 201 | return fmt.Errorf("No control found for client id: %s", clientId) 202 | } else { 203 | r.Info("Removed control registry id %s", clientId) 204 | delete(r.controls, clientId) 205 | return nil 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/ngrok/server/tls.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "io/ioutil" 6 | "ngrok/server/assets" 7 | ) 8 | 9 | func LoadTLSConfig(crtPath string, keyPath string) (tlsConfig *tls.Config, err error) { 10 | fileOrAsset := func(path string, default_path string) ([]byte, error) { 11 | loadFn := ioutil.ReadFile 12 | if path == "" { 13 | loadFn = assets.Asset 14 | path = default_path 15 | } 16 | 17 | return loadFn(path) 18 | } 19 | 20 | var ( 21 | crt []byte 22 | key []byte 23 | cert tls.Certificate 24 | ) 25 | 26 | if crt, err = fileOrAsset(crtPath, "assets/server/tls/snakeoil.crt"); err != nil { 27 | return 28 | } 29 | 30 | if key, err = fileOrAsset(keyPath, "assets/server/tls/snakeoil.key"); err != nil { 31 | return 32 | } 33 | 34 | if cert, err = tls.X509KeyPair(crt, key); err != nil { 35 | return 36 | } 37 | 38 | tlsConfig = &tls.Config{ 39 | Certificates: []tls.Certificate{cert}, 40 | } 41 | 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /src/ngrok/server/tunnel.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "ngrok/conn" 9 | "ngrok/log" 10 | "ngrok/msg" 11 | "ngrok/util" 12 | "os" 13 | "strconv" 14 | "strings" 15 | "sync/atomic" 16 | "time" 17 | ) 18 | 19 | var defaultPortMap = map[string]int{ 20 | "http": 80, 21 | "https": 443, 22 | "smtp": 25, 23 | } 24 | 25 | /** 26 | * Tunnel: A control connection, metadata and proxy connections which 27 | * route public traffic to a firewalled endpoint. 28 | */ 29 | type Tunnel struct { 30 | // request that opened the tunnel 31 | req *msg.ReqTunnel 32 | 33 | // time when the tunnel was opened 34 | start time.Time 35 | 36 | // public url 37 | url string 38 | 39 | // tcp listener 40 | listener *net.TCPListener 41 | 42 | // control connection 43 | ctl *Control 44 | 45 | // logger 46 | log.Logger 47 | 48 | // closing 49 | closing int32 50 | } 51 | 52 | // Common functionality for registering virtually hosted protocols 53 | func registerVhost(t *Tunnel, protocol string, servingPort int) (err error) { 54 | vhost := os.Getenv("VHOST") 55 | if vhost == "" { 56 | vhost = fmt.Sprintf("%s:%d", opts.domain, servingPort) 57 | } 58 | 59 | // Canonicalize virtual host by removing default port (e.g. :80 on HTTP) 60 | defaultPort, ok := defaultPortMap[protocol] 61 | if !ok { 62 | return fmt.Errorf("Couldn't find default port for protocol %s", protocol) 63 | } 64 | 65 | defaultPortSuffix := fmt.Sprintf(":%d", defaultPort) 66 | if strings.HasSuffix(vhost, defaultPortSuffix) { 67 | vhost = vhost[0 : len(vhost)-len(defaultPortSuffix)] 68 | } 69 | 70 | // Canonicalize by always using lower-case 71 | vhost = strings.ToLower(vhost) 72 | 73 | // Register for specific hostname 74 | hostname := strings.ToLower(strings.TrimSpace(t.req.Hostname)) 75 | if hostname != "" { 76 | t.url = fmt.Sprintf("%s://%s", protocol, hostname) 77 | return tunnelRegistry.Register(t.url, t) 78 | } 79 | 80 | // Register for specific subdomain 81 | subdomain := strings.ToLower(strings.TrimSpace(t.req.Subdomain)) 82 | if subdomain != "" { 83 | t.url = fmt.Sprintf("%s://%s.%s", protocol, subdomain, vhost) 84 | return tunnelRegistry.Register(t.url, t) 85 | } 86 | 87 | // Register for random URL 88 | t.url, err = tunnelRegistry.RegisterRepeat(func() string { 89 | return fmt.Sprintf("%s://%x.%s", protocol, rand.Int31(), vhost) 90 | }, t) 91 | 92 | return 93 | } 94 | 95 | // Create a new tunnel from a registration message received 96 | // on a control channel 97 | func NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) { 98 | t = &Tunnel{ 99 | req: m, 100 | start: time.Now(), 101 | ctl: ctl, 102 | Logger: log.NewPrefixLogger(), 103 | } 104 | 105 | proto := t.req.Protocol 106 | switch proto { 107 | case "tcp": 108 | bindTcp := func(port int) error { 109 | if t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: port}); err != nil { 110 | err = t.ctl.conn.Error("Error binding TCP listener: %v", err) 111 | return err 112 | } 113 | 114 | // create the url 115 | addr := t.listener.Addr().(*net.TCPAddr) 116 | t.url = fmt.Sprintf("tcp://%s:%d", opts.domain, addr.Port) 117 | 118 | // register it 119 | if err = tunnelRegistry.RegisterAndCache(t.url, t); err != nil { 120 | // This should never be possible because the OS will 121 | // only assign available ports to us. 122 | t.listener.Close() 123 | err = fmt.Errorf("TCP listener bound, but failed to register %s", t.url) 124 | return err 125 | } 126 | 127 | go t.listenTcp(t.listener) 128 | return nil 129 | } 130 | 131 | // use the custom remote port you asked for 132 | if t.req.RemotePort != 0 { 133 | bindTcp(int(t.req.RemotePort)) 134 | return 135 | } 136 | 137 | // try to return to you the same port you had before 138 | cachedUrl := tunnelRegistry.GetCachedRegistration(t) 139 | if cachedUrl != "" { 140 | var port int 141 | parts := strings.Split(cachedUrl, ":") 142 | portPart := parts[len(parts)-1] 143 | port, err = strconv.Atoi(portPart) 144 | if err != nil { 145 | t.ctl.conn.Error("Failed to parse cached url port as integer: %s", portPart) 146 | } else { 147 | // we have a valid, cached port, let's try to bind with it 148 | if bindTcp(port) != nil { 149 | t.ctl.conn.Warn("Failed to get custom port %d: %v, trying a random one", port, err) 150 | } else { 151 | // success, we're done 152 | return 153 | } 154 | } 155 | } 156 | 157 | // Bind for TCP connections 158 | bindTcp(0) 159 | return 160 | 161 | case "http", "https": 162 | l, ok := listeners[proto] 163 | if !ok { 164 | err = fmt.Errorf("Not listening for %s connections", proto) 165 | return 166 | } 167 | 168 | if err = registerVhost(t, proto, l.Addr.(*net.TCPAddr).Port); err != nil { 169 | return 170 | } 171 | 172 | default: 173 | err = fmt.Errorf("Protocol %s is not supported", proto) 174 | return 175 | } 176 | 177 | // pre-encode the http basic auth for fast comparisons later 178 | if m.HttpAuth != "" { 179 | m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth)) 180 | } 181 | 182 | t.AddLogPrefix(t.Id()) 183 | t.Info("Registered new tunnel on: %s", t.ctl.conn.Id()) 184 | 185 | metrics.OpenTunnel(t) 186 | return 187 | } 188 | 189 | func (t *Tunnel) Shutdown() { 190 | t.Info("Shutting down") 191 | 192 | // mark that we're shutting down 193 | atomic.StoreInt32(&t.closing, 1) 194 | 195 | // if we have a public listener (this is a raw TCP tunnel), shut it down 196 | if t.listener != nil { 197 | t.listener.Close() 198 | } 199 | 200 | // remove ourselves from the tunnel registry 201 | tunnelRegistry.Del(t.url) 202 | 203 | // let the control connection know we're shutting down 204 | // currently, only the control connection shuts down tunnels, 205 | // so it doesn't need to know about it 206 | // t.ctl.stoptunnel <- t 207 | 208 | metrics.CloseTunnel(t) 209 | } 210 | 211 | func (t *Tunnel) Id() string { 212 | return t.url 213 | } 214 | 215 | // Listens for new public tcp connections from the internet. 216 | func (t *Tunnel) listenTcp(listener *net.TCPListener) { 217 | for { 218 | defer func() { 219 | if r := recover(); r != nil { 220 | log.Warn("listenTcp failed with error %v", r) 221 | } 222 | }() 223 | 224 | // accept public connections 225 | tcpConn, err := listener.AcceptTCP() 226 | 227 | if err != nil { 228 | // not an error, we're shutting down this tunnel 229 | if atomic.LoadInt32(&t.closing) == 1 { 230 | return 231 | } 232 | 233 | t.Error("Failed to accept new TCP connection: %v", err) 234 | continue 235 | } 236 | 237 | conn := conn.Wrap(tcpConn, "pub") 238 | conn.AddLogPrefix(t.Id()) 239 | conn.Info("New connection from %v", conn.RemoteAddr()) 240 | 241 | go t.HandlePublicConnection(conn) 242 | } 243 | } 244 | 245 | func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) { 246 | defer publicConn.Close() 247 | defer func() { 248 | if r := recover(); r != nil { 249 | publicConn.Warn("HandlePublicConnection failed with error %v", r) 250 | } 251 | }() 252 | 253 | startTime := time.Now() 254 | metrics.OpenConnection(t, publicConn) 255 | 256 | var proxyConn conn.Conn 257 | var err error 258 | for i := 0; i < (2 * proxyMaxPoolSize); i++ { 259 | // get a proxy connection 260 | if proxyConn, err = t.ctl.GetProxy(); err != nil { 261 | t.Warn("Failed to get proxy connection: %v", err) 262 | return 263 | } 264 | defer proxyConn.Close() 265 | t.Info("Got proxy connection %s", proxyConn.Id()) 266 | proxyConn.AddLogPrefix(t.Id()) 267 | 268 | // tell the client we're going to start using this proxy connection 269 | startPxyMsg := &msg.StartProxy{ 270 | Url: t.url, 271 | ClientAddr: publicConn.RemoteAddr().String(), 272 | } 273 | 274 | if err = msg.WriteMsg(proxyConn, startPxyMsg); err != nil { 275 | proxyConn.Warn("Failed to write StartProxyMessage: %v, attempt %d", err, i) 276 | proxyConn.Close() 277 | } else { 278 | // success 279 | break 280 | } 281 | } 282 | 283 | if err != nil { 284 | // give up 285 | publicConn.Error("Too many failures starting proxy connection") 286 | return 287 | } 288 | 289 | // To reduce latency handling tunnel connections, we employ the following curde heuristic: 290 | // Whenever we take a proxy connection from the pool, replace it with a new one 291 | util.PanicToError(func() { t.ctl.out <- &msg.ReqProxy{} }) 292 | 293 | // no timeouts while connections are joined 294 | proxyConn.SetDeadline(time.Time{}) 295 | 296 | // join the public and proxy connections 297 | bytesIn, bytesOut := conn.Join(publicConn, proxyConn) 298 | metrics.CloseConnection(t, publicConn, startTime, bytesIn, bytesOut) 299 | } 300 | -------------------------------------------------------------------------------- /src/ngrok/util/broadcast.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type Broadcast struct { 4 | listeners []chan interface{} 5 | reg chan (chan interface{}) 6 | unreg chan (chan interface{}) 7 | in chan interface{} 8 | } 9 | 10 | func NewBroadcast() *Broadcast { 11 | b := &Broadcast{ 12 | listeners: make([]chan interface{}, 0), 13 | reg: make(chan (chan interface{})), 14 | unreg: make(chan (chan interface{})), 15 | in: make(chan interface{}), 16 | } 17 | 18 | go func() { 19 | for { 20 | select { 21 | case l := <-b.unreg: 22 | // remove L from b.listeners 23 | // this operation is slow: O(n) but not used frequently 24 | // unlike iterating over listeners 25 | oldListeners := b.listeners 26 | b.listeners = make([]chan interface{}, 0, len(oldListeners)) 27 | for _, oldL := range oldListeners { 28 | if l != oldL { 29 | b.listeners = append(b.listeners, oldL) 30 | } 31 | } 32 | 33 | case l := <-b.reg: 34 | b.listeners = append(b.listeners, l) 35 | 36 | case item := <-b.in: 37 | for _, l := range b.listeners { 38 | l <- item 39 | } 40 | } 41 | } 42 | }() 43 | 44 | return b 45 | } 46 | 47 | func (b *Broadcast) In() chan interface{} { 48 | return b.in 49 | } 50 | 51 | func (b *Broadcast) Reg() chan interface{} { 52 | listener := make(chan interface{}) 53 | b.reg <- listener 54 | return listener 55 | } 56 | 57 | func (b *Broadcast) UnReg(listener chan interface{}) { 58 | b.unreg <- listener 59 | } 60 | -------------------------------------------------------------------------------- /src/ngrok/util/errors.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | const crashMessage = `panic: %v 9 | 10 | %s 11 | 12 | Oh noes! ngrok crashed! 13 | 14 | Please submit the stack trace and any relevant information to: 15 | github.com/inconshreveable/ngrok/issues` 16 | 17 | func MakePanicTrace(err interface{}) string { 18 | stackBuf := make([]byte, 4096) 19 | n := runtime.Stack(stackBuf, false) 20 | return fmt.Sprintf(crashMessage, err, stackBuf[:n]) 21 | } 22 | 23 | // Runs the given function and converts any panic encountered while doing so 24 | // into an error. Useful for sending to channels that will close 25 | func PanicToError(fn func()) (err error) { 26 | defer func() { 27 | if r := recover(); r != nil { 28 | err = fmt.Errorf("Panic: %v", r) 29 | } 30 | }() 31 | fn() 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /src/ngrok/util/id.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "fmt" 7 | mrand "math/rand" 8 | ) 9 | 10 | func RandomSeed() (seed int64, err error) { 11 | err = binary.Read(rand.Reader, binary.LittleEndian, &seed) 12 | return 13 | } 14 | 15 | // creates a random identifier of the specified length 16 | func RandId(idlen int) string { 17 | b := make([]byte, idlen) 18 | var randVal uint32 19 | for i := 0; i < idlen; i++ { 20 | byteIdx := i % 4 21 | if byteIdx == 0 { 22 | randVal = mrand.Uint32() 23 | } 24 | b[i] = byte((randVal >> (8 * uint(byteIdx))) & 0xFF) 25 | } 26 | return fmt.Sprintf("%x", b) 27 | } 28 | 29 | // like RandId, but uses a crypto/rand for secure random identifiers 30 | func SecureRandId(idlen int) (id string, err error) { 31 | b := make([]byte, idlen) 32 | n, err := rand.Read(b) 33 | 34 | if n != idlen { 35 | err = fmt.Errorf("Only generated %d random bytes, %d requested", n, idlen) 36 | return 37 | } 38 | 39 | if err != nil { 40 | return 41 | } 42 | 43 | id = fmt.Sprintf("%x", b) 44 | return 45 | } 46 | 47 | func SecureRandIdOrPanic(idlen int) string { 48 | id, err := SecureRandId(idlen) 49 | if err != nil { 50 | panic(err) 51 | } 52 | return id 53 | } 54 | -------------------------------------------------------------------------------- /src/ngrok/util/ring.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | ) 7 | 8 | type Ring struct { 9 | sync.Mutex 10 | *list.List 11 | capacity int 12 | } 13 | 14 | func NewRing(capacity int) *Ring { 15 | return &Ring{capacity: capacity, List: list.New()} 16 | } 17 | 18 | func (r *Ring) Add(item interface{}) interface{} { 19 | r.Lock() 20 | defer r.Unlock() 21 | 22 | // add new item 23 | r.PushFront(item) 24 | 25 | // remove old item if at capacity 26 | var old interface{} 27 | if r.Len() >= r.capacity { 28 | old = r.Remove(r.Back()) 29 | } 30 | 31 | return old 32 | } 33 | 34 | func (r *Ring) Slice() []interface{} { 35 | r.Lock() 36 | defer r.Unlock() 37 | 38 | i := 0 39 | items := make([]interface{}, r.Len()) 40 | for e := r.Front(); e != nil; e = e.Next() { 41 | items[i] = e.Value 42 | i++ 43 | } 44 | 45 | return items 46 | } 47 | -------------------------------------------------------------------------------- /src/ngrok/util/shutdown.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // A small utility class for managing controlled shutdowns 8 | type Shutdown struct { 9 | sync.Mutex 10 | inProgress bool 11 | begin chan int // closed when the shutdown begins 12 | complete chan int // closed when the shutdown completes 13 | } 14 | 15 | func NewShutdown() *Shutdown { 16 | return &Shutdown{ 17 | begin: make(chan int), 18 | complete: make(chan int), 19 | } 20 | } 21 | 22 | func (s *Shutdown) Begin() { 23 | s.Lock() 24 | defer s.Unlock() 25 | if s.inProgress == true { 26 | return 27 | } else { 28 | s.inProgress = true 29 | close(s.begin) 30 | } 31 | } 32 | 33 | func (s *Shutdown) WaitBegin() { 34 | <-s.begin 35 | } 36 | 37 | func (s *Shutdown) Complete() { 38 | close(s.complete) 39 | } 40 | 41 | func (s *Shutdown) WaitComplete() { 42 | <-s.complete 43 | } 44 | -------------------------------------------------------------------------------- /src/ngrok/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | Proto = "2" 9 | Major = "1" 10 | Minor = "7" 11 | ) 12 | 13 | func MajorMinor() string { 14 | return fmt.Sprintf("%s.%s", Major, Minor) 15 | } 16 | 17 | func Full() string { 18 | return fmt.Sprintf("%s-%s.%s", Proto, Major, Minor) 19 | } 20 | 21 | func Compat(client string, server string) bool { 22 | return client == server 23 | } 24 | --------------------------------------------------------------------------------