├── .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.4 7 | - 1.5 8 | - 1.6 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 | # ngrok - Unified Ingress for Developers 2 | 3 | [https://ngrok.com](https://ngrok.com) 4 | 5 | ## ngrok Community on GitHub 6 | 7 | If you are having an issue with the ngrok cloud service please open an issue on the [ngrok community on GitHub](https://github.com/ngrok/ngrok) 8 | 9 | ## This repository is archived 10 | 11 | This is the GitHub repository for the old v1 version of ngrok which was actively developed from 2013-2016. 12 | 13 | **This repository is archived: ngrok v1 is no longer developed, supported or maintained.** 14 | 15 | Thank you to everyone who contributed to ngrok v1 it in its early days with PRs, issues and feedback. If you wish to continue development on this codebase, please fork it. 16 | 17 | ngrok's cloud service continues to operate and you can sign up for it here: [https://ngrok.com/signup](https://ngrok.com/signup) 18 | 19 | ## What is ngrok? 20 | 21 | ngrok is a globally distributed reverse proxy that secures, protects and accelerates your applications and network services, no matter where you run them. You can think of ngrok as the front door to your applications. ngrok combines your reverse proxy, firewall, API gateway, and global load balancing into one. ngrok can capture and analyze all traffic to your web service for later inspection and replay. 22 | 23 | To use ngrok, sign up at [https://ngrok.com/signup](https://ngrok.com/signup) 24 | 25 | ## ngrok open-source development 26 | ngrok continues to contribute to the open source ecosystem at [https://github.com/ngrok](https://github.com/ngrok) with: 27 | - [The ngrok kubernetes operator](https://github.com/ngrok/kubernetes-ingress-controller) 28 | - [The ngrok agent SDKs](https://ngrok.com/docs/agent-sdks/) for [Python](https://github.com/ngrok/ngrok-python), [JavaScript](https://github.com/ngrok/ngrok-javascript), [Go](https://github.com/ngrok/ngrok-go), [Rust](https://github.com/ngrok/ngrok-rust) and [Java](https://github.com/ngrok/ngrok-java) 29 | 30 | 31 | ## What is ngrok for? 32 | 33 | [What can you do with ngrok?](https://ngrok.com/docs/what-is-ngrok/#what-can-you-do-with-ngrok) 34 | 35 | - Site-to-site Connectivity: Connect securely to APIs and databases in your customers' networks without complex network configuration. 36 | - Developer Previews: Demoing an app from your local machine without deploying it 37 | - Webhook Testing: Developing any services which consume webhooks (HTTP callbacks) by allowing you to replay those requests 38 | - API Gateway: An global gateway-as-a-service that works for API running anywhere with simple CEL-based traffic policy for rate limiting, jwt authentication and more. 39 | - Device Gateway: Run ngrok on your IoT devices to control device APIs from your cloud 40 | - Debug and understand any web service by inspecting the HTTP traffic to it 41 | -------------------------------------------------------------------------------- /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.
34 | ` 35 | ) 36 | 37 | type ClientModel struct { 38 | log.Logger 39 | 40 | id string 41 | tunnels map[string]mvc.Tunnel 42 | serverVersion string 43 | metrics *ClientMetrics 44 | updateStatus mvc.UpdateStatus 45 | connStatus mvc.ConnStatus 46 | protoMap map[string]proto.Protocol 47 | protocols []proto.Protocol 48 | ctl mvc.Controller 49 | serverAddr string 50 | proxyUrl string 51 | authToken string 52 | tlsConfig *tls.Config 53 | tunnelConfig map[string]*TunnelConfiguration 54 | configPath string 55 | } 56 | 57 | func newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel { 58 | protoMap := make(map[string]proto.Protocol) 59 | protoMap["http"] = proto.NewHttp() 60 | protoMap["https"] = protoMap["http"] 61 | protoMap["tcp"] = proto.NewTcp() 62 | protocols := []proto.Protocol{protoMap["http"], protoMap["tcp"]} 63 | 64 | m := &ClientModel{ 65 | Logger: log.NewPrefixLogger("client"), 66 | 67 | // server address 68 | serverAddr: config.ServerAddr, 69 | 70 | // proxy address 71 | proxyUrl: config.HttpProxy, 72 | 73 | // auth token 74 | authToken: config.AuthToken, 75 | 76 | // connection status 77 | connStatus: mvc.ConnConnecting, 78 | 79 | // update status 80 | updateStatus: mvc.UpdateNone, 81 | 82 | // metrics 83 | metrics: NewClientMetrics(), 84 | 85 | // protocols 86 | protoMap: protoMap, 87 | 88 | // protocol list 89 | protocols: protocols, 90 | 91 | // open tunnels 92 | tunnels: make(map[string]mvc.Tunnel), 93 | 94 | // controller 95 | ctl: ctl, 96 | 97 | // tunnel configuration 98 | tunnelConfig: config.Tunnels, 99 | 100 | // config path 101 | configPath: config.Path, 102 | } 103 | 104 | // configure TLS 105 | if config.TrustHostRootCerts { 106 | m.Info("Trusting host's root certificates") 107 | m.tlsConfig = &tls.Config{} 108 | } else { 109 | m.Info("Trusting root CAs: %v", rootCrtPaths) 110 | var err error 111 | if m.tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil { 112 | panic(err) 113 | } 114 | } 115 | 116 | // configure TLS SNI 117 | m.tlsConfig.ServerName = serverName(m.serverAddr) 118 | m.tlsConfig.InsecureSkipVerify = useInsecureSkipVerify() 119 | 120 | return m 121 | } 122 | 123 | // server name in release builds is the host part of the server address 124 | func serverName(addr string) string { 125 | host, _, err := net.SplitHostPort(addr) 126 | 127 | // should never panic because the config parser calls SplitHostPort first 128 | if err != nil { 129 | panic(err) 130 | } 131 | 132 | return host 133 | } 134 | 135 | // mvc.State interface 136 | func (c ClientModel) GetProtocols() []proto.Protocol { return c.protocols } 137 | func (c ClientModel) GetClientVersion() string { return version.MajorMinor() } 138 | func (c ClientModel) GetServerVersion() string { return c.serverVersion } 139 | func (c ClientModel) GetTunnels() []mvc.Tunnel { 140 | tunnels := make([]mvc.Tunnel, 0) 141 | for _, t := range c.tunnels { 142 | tunnels = append(tunnels, t) 143 | } 144 | return tunnels 145 | } 146 | func (c ClientModel) GetConnStatus() mvc.ConnStatus { return c.connStatus } 147 | func (c ClientModel) GetUpdateStatus() mvc.UpdateStatus { return c.updateStatus } 148 | 149 | func (c ClientModel) GetConnectionMetrics() (metrics.Meter, metrics.Timer) { 150 | return c.metrics.connMeter, c.metrics.connTimer 151 | } 152 | 153 | func (c ClientModel) GetBytesInMetrics() (metrics.Counter, metrics.Histogram) { 154 | return c.metrics.bytesInCount, c.metrics.bytesIn 155 | } 156 | 157 | func (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) { 158 | return c.metrics.bytesOutCount, c.metrics.bytesOut 159 | } 160 | func (c ClientModel) SetUpdateStatus(updateStatus mvc.UpdateStatus) { 161 | c.updateStatus = updateStatus 162 | c.update() 163 | } 164 | 165 | // mvc.Model interface 166 | func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) { 167 | var localConn conn.Conn 168 | localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) 169 | if err != nil { 170 | c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err) 171 | return 172 | } 173 | 174 | defer localConn.Close() 175 | localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"}) 176 | localConn.Write(payload) 177 | ioutil.ReadAll(localConn) 178 | } 179 | 180 | func (c *ClientModel) Shutdown() { 181 | } 182 | 183 | func (c *ClientModel) update() { 184 | c.ctl.Update(c) 185 | } 186 | 187 | func (c *ClientModel) Run() { 188 | // how long we should wait before we reconnect 189 | maxWait := 30 * time.Second 190 | wait := 1 * time.Second 191 | 192 | for { 193 | // run the control channel 194 | c.control() 195 | 196 | // control only returns when a failure has occurred, so we're going to try to reconnect 197 | if c.connStatus == mvc.ConnOnline { 198 | wait = 1 * time.Second 199 | } 200 | 201 | log.Info("Waiting %d seconds before reconnecting", int(wait.Seconds())) 202 | time.Sleep(wait) 203 | // exponentially increase wait time 204 | wait = 2 * wait 205 | wait = time.Duration(math.Min(float64(wait), float64(maxWait))) 206 | c.connStatus = mvc.ConnReconnecting 207 | c.update() 208 | } 209 | } 210 | 211 | // Establishes and manages a tunnel control connection with the server 212 | func (c *ClientModel) control() { 213 | defer func() { 214 | if r := recover(); r != nil { 215 | log.Error("control recovering from failure %v", r) 216 | } 217 | }() 218 | 219 | // establish control channel 220 | var ( 221 | ctlConn conn.Conn 222 | err error 223 | ) 224 | if c.proxyUrl == "" { 225 | // simple non-proxied case, just connect to the server 226 | ctlConn, err = conn.Dial(c.serverAddr, "ctl", c.tlsConfig) 227 | } else { 228 | ctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "ctl", c.tlsConfig) 229 | } 230 | if err != nil { 231 | panic(err) 232 | } 233 | defer ctlConn.Close() 234 | 235 | // authenticate with the server 236 | auth := &msg.Auth{ 237 | ClientId: c.id, 238 | OS: runtime.GOOS, 239 | Arch: runtime.GOARCH, 240 | Version: version.Proto, 241 | MmVersion: version.MajorMinor(), 242 | User: c.authToken, 243 | } 244 | 245 | if err = msg.WriteMsg(ctlConn, auth); err != nil { 246 | panic(err) 247 | } 248 | 249 | // wait for the server to authenticate us 250 | var authResp msg.AuthResp 251 | if err = msg.ReadMsgInto(ctlConn, &authResp); err != nil { 252 | panic(err) 253 | } 254 | 255 | if authResp.Error != "" { 256 | emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error) 257 | c.ctl.Shutdown(emsg) 258 | return 259 | } 260 | 261 | c.id = authResp.ClientId 262 | c.serverVersion = authResp.MmVersion 263 | c.Info("Authenticated with server, client id: %v", c.id) 264 | c.update() 265 | if err = SaveAuthToken(c.configPath, c.authToken); err != nil { 266 | c.Error("Failed to save auth token: %v", err) 267 | } 268 | 269 | // request tunnels 270 | reqIdToTunnelConfig := make(map[string]*TunnelConfiguration) 271 | for _, config := range c.tunnelConfig { 272 | // create the protocol list to ask for 273 | var protocols []string 274 | for proto, _ := range config.Protocols { 275 | protocols = append(protocols, proto) 276 | } 277 | 278 | reqTunnel := &msg.ReqTunnel{ 279 | ReqId: util.RandId(8), 280 | Protocol: strings.Join(protocols, "+"), 281 | Hostname: config.Hostname, 282 | Subdomain: config.Subdomain, 283 | HttpAuth: config.HttpAuth, 284 | RemotePort: config.RemotePort, 285 | } 286 | 287 | // send the tunnel request 288 | if err = msg.WriteMsg(ctlConn, reqTunnel); err != nil { 289 | panic(err) 290 | } 291 | 292 | // save request id association so we know which local address 293 | // to proxy to later 294 | reqIdToTunnelConfig[reqTunnel.ReqId] = config 295 | } 296 | 297 | // start the heartbeat 298 | lastPong := time.Now().UnixNano() 299 | c.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) }) 300 | 301 | // main control loop 302 | for { 303 | var rawMsg msg.Message 304 | if rawMsg, err = msg.ReadMsg(ctlConn); err != nil { 305 | panic(err) 306 | } 307 | 308 | switch m := rawMsg.(type) { 309 | case *msg.ReqProxy: 310 | c.ctl.Go(c.proxy) 311 | 312 | case *msg.Pong: 313 | atomic.StoreInt64(&lastPong, time.Now().UnixNano()) 314 | 315 | case *msg.NewTunnel: 316 | if m.Error != "" { 317 | emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error) 318 | c.Error(emsg) 319 | c.ctl.Shutdown(emsg) 320 | continue 321 | } 322 | 323 | tunnel := mvc.Tunnel{ 324 | PublicUrl: m.Url, 325 | LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol], 326 | Protocol: c.protoMap[m.Protocol], 327 | } 328 | 329 | c.tunnels[tunnel.PublicUrl] = tunnel 330 | c.connStatus = mvc.ConnOnline 331 | c.Info("Tunnel established at %v", tunnel.PublicUrl) 332 | c.update() 333 | 334 | default: 335 | ctlConn.Warn("Ignoring unknown control message %v ", m) 336 | } 337 | } 338 | } 339 | 340 | // Establishes and manages a tunnel proxy connection with the server 341 | func (c *ClientModel) proxy() { 342 | var ( 343 | remoteConn conn.Conn 344 | err error 345 | ) 346 | 347 | if c.proxyUrl == "" { 348 | remoteConn, err = conn.Dial(c.serverAddr, "pxy", c.tlsConfig) 349 | } else { 350 | remoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "pxy", c.tlsConfig) 351 | } 352 | 353 | if err != nil { 354 | log.Error("Failed to establish proxy connection: %v", err) 355 | return 356 | } 357 | defer remoteConn.Close() 358 | 359 | err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id}) 360 | if err != nil { 361 | remoteConn.Error("Failed to write RegProxy: %v", err) 362 | return 363 | } 364 | 365 | // wait for the server to ack our register 366 | var startPxy msg.StartProxy 367 | if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil { 368 | remoteConn.Error("Server failed to write StartProxy: %v", err) 369 | return 370 | } 371 | 372 | tunnel, ok := c.tunnels[startPxy.Url] 373 | if !ok { 374 | remoteConn.Error("Couldn't find tunnel for proxy: %s", startPxy.Url) 375 | return 376 | } 377 | 378 | // start up the private connection 379 | start := time.Now() 380 | localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) 381 | if err != nil { 382 | remoteConn.Warn("Failed to open private leg %s: %v", tunnel.LocalAddr, err) 383 | 384 | if tunnel.Protocol.GetName() == "http" { 385 | // try to be helpful when you're in HTTP mode and a human might see the output 386 | badGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr) 387 | remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway 388 | Content-Type: text/html 389 | Content-Length: %d 390 | 391 | %s`, len(badGatewayBody), badGatewayBody))) 392 | } 393 | return 394 | } 395 | defer localConn.Close() 396 | 397 | m := c.metrics 398 | m.proxySetupTimer.Update(time.Since(start)) 399 | m.connMeter.Mark(1) 400 | c.update() 401 | m.connTimer.Time(func() { 402 | localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr}) 403 | bytesIn, bytesOut := conn.Join(localConn, remoteConn) 404 | m.bytesIn.Update(bytesIn) 405 | m.bytesOut.Update(bytesOut) 406 | m.bytesInCount.Inc(bytesIn) 407 | m.bytesOutCount.Inc(bytesOut) 408 | }) 409 | c.update() 410 | } 411 | 412 | // Hearbeating to ensure our connection ngrokd is still live 413 | func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) { 414 | lastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0) 415 | ping := time.NewTicker(pingInterval) 416 | pongCheck := time.NewTicker(time.Second) 417 | 418 | defer func() { 419 | conn.Close() 420 | ping.Stop() 421 | pongCheck.Stop() 422 | }() 423 | 424 | for { 425 | select { 426 | case <-pongCheck.C: 427 | lastPong := time.Unix(0, atomic.LoadInt64(lastPongAddr)) 428 | needPong := lastPong.Sub(lastPing) < 0 429 | pongLatency := time.Since(lastPing) 430 | 431 | if needPong && pongLatency > maxPongLatency { 432 | c.Info("Last ping: %v, Last pong: %v", lastPing, lastPong) 433 | c.Info("Connection stale, haven't gotten PongMsg in %d seconds", int(pongLatency.Seconds())) 434 | return 435 | } 436 | 437 | case <-ping.C: 438 | err := msg.WriteMsg(conn, &msg.Ping{}) 439 | if err != nil { 440 | conn.Debug("Got error %v when writing PingMsg", err) 441 | return 442 | } 443 | lastPing = time.Now() 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /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 | var ( 6 | rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt"} 7 | ) 8 | 9 | func useInsecureSkipVerify() bool { 10 | return false 11 | } 12 | -------------------------------------------------------------------------------- /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 | "fmt" 5 | log "github.com/alecthomas/log4go" 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 | --------------------------------------------------------------------------------