Browse Source

modernize the ngrok protocol to make more sense in the context of multiple tunnels. separate out authentication and tunnel registration

Alan Shreve 12 years ago
parent
commit
280901df87

+ 2 - 3
src/ngrok/client/controller.go

@@ -170,15 +170,14 @@ func (ctl *Controller) Run(opts *Options) {
 
 	ctl.Go(func() { autoUpdate(state, opts.authtoken) })
 
-	reg := &msg.RegMsg{
+	reqTunnel := &msg.ReqTunnel{
 		Protocol:  opts.protocol,
 		Hostname:  opts.hostname,
 		Subdomain: opts.subdomain,
 		HttpAuth:  opts.httpAuth,
-		User:      opts.authtoken,
 	}
 
-	ctl.Go(func() { ctl.model.Run(opts.server, opts.authtoken, ctl, reg, opts.localaddr) })
+	ctl.Go(func() { ctl.model.Run(opts.server, opts.authtoken, ctl, reqTunnel, opts.localaddr) })
 
 	quitMessage := ""
 	defer func() {

+ 54 - 47
src/ngrok/client/model.go

@@ -10,7 +10,6 @@ import (
 	"ngrok/log"
 	"ngrok/msg"
 	"ngrok/proto"
-	"ngrok/util"
 	"ngrok/version"
 	"runtime"
 	"sync/atomic"
@@ -55,9 +54,6 @@ func newClientModel(ctl mvc.Controller) *ClientModel {
 	return &ClientModel{
 		Logger: log.NewPrefixLogger("client"),
 
-		// unique client id
-		id: util.RandIdOrPanic(8),
-
 		// connection status
 		connStatus: mvc.ConnConnecting,
 
@@ -133,20 +129,20 @@ func (c *ClientModel) update() {
 	c.ctl.Update(c)
 }
 
-func (c *ClientModel) Run(serverAddr, authToken string, ctl mvc.Controller, reg *msg.RegMsg, localaddr string) {
+func (c *ClientModel) Run(serverAddr, authToken string, ctl mvc.Controller, reqTunnel *msg.ReqTunnel, localaddr string) {
 	c.serverAddr = serverAddr
 	c.authToken = authToken
 	c.ctl = ctl
-	c.reconnectingControl(reg, localaddr)
+	c.reconnectingControl(reqTunnel, localaddr)
 }
 
-func (c *ClientModel) reconnectingControl(reg *msg.RegMsg, localaddr string) {
+func (c *ClientModel) reconnectingControl(reqTunnel *msg.ReqTunnel, localaddr string) {
 	// how long we should wait before we reconnect
 	maxWait := 30 * time.Second
 	wait := 1 * time.Second
 
 	for {
-		c.control(reg, localaddr)
+		c.control(reqTunnel, localaddr)
 
 		if c.connStatus == mvc.ConnOnline {
 			wait = 1 * time.Second
@@ -163,7 +159,7 @@ func (c *ClientModel) reconnectingControl(reg *msg.RegMsg, localaddr string) {
 }
 
 // Establishes and manages a tunnel control connection with the server
-func (c *ClientModel) control(reg *msg.RegMsg, localaddr string) {
+func (c *ClientModel) control(reqTunnel *msg.ReqTunnel, localaddr string) {
 	defer func() {
 		if r := recover(); r != nil {
 			log.Error("control recovering from failure %v", r)
@@ -177,46 +173,55 @@ func (c *ClientModel) control(reg *msg.RegMsg, localaddr string) {
 	}
 	defer conn.Close()
 
-	// register with the server
-	reg.OS = runtime.GOOS
-	reg.ClientId = c.id
-	reg.Version = version.Proto
-	reg.MmVersion = version.MajorMinor()
-	reg.User = c.authToken
+	// authenticate with the server
+	auth := &msg.Auth{
+		ClientId:  c.id,
+		OS:        runtime.GOOS,
+		Arch:      runtime.GOARCH,
+		Version:   version.Proto,
+		MmVersion: version.MajorMinor(),
+		User:      c.authToken,
+	}
 
-	if err = msg.WriteMsg(conn, reg); err != nil {
+	if err = msg.WriteMsg(conn, auth); err != nil {
 		panic(err)
 	}
 
-	// wait for the server to ack our register
-	var regAck msg.RegAckMsg
-	if err = msg.ReadMsgInto(conn, &regAck); err != nil {
+	// wait for the server to authenticate us
+	var authResp msg.AuthResp
+	if err = msg.ReadMsgInto(conn, &authResp); err != nil {
 		panic(err)
 	}
 
-	if regAck.Error != "" {
-		emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", regAck.Error)
+	if authResp.Error != "" {
+		emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error)
 		c.ctl.Shutdown(emsg)
 		return
 	}
 
-	tunnel := mvc.Tunnel{
-		PublicUrl: regAck.Url,
-		LocalAddr: localaddr,
-		Protocol:  c.protoMap[reg.Protocol],
-	}
-
-	c.tunnels[tunnel.PublicUrl] = tunnel
-
-	// update UI state
-	c.id = regAck.ClientId
-	c.Info("Tunnel established at %v", tunnel.PublicUrl)
+	c.id = authResp.ClientId
 	c.connStatus = mvc.ConnOnline
-	c.serverVersion = regAck.MmVersion
+	c.serverVersion = authResp.MmVersion
+	c.Info("Authenticated with server, client id: %v", c.id)
 	c.update()
-
 	SaveAuthToken(c.authToken)
 
+	// register the tunnel
+	if err = msg.WriteMsg(conn, reqTunnel); err != nil {
+		panic(err)
+	}
+
+	// register an https tunnel as well for http tunnels
+	if reqTunnel.Protocol == "http" {
+		httpsReqTunnel := *reqTunnel
+		httpsReqTunnel.Protocol = "https"
+		// httpsReqTunnel.ReqId =
+
+		if err = msg.WriteMsg(conn, &httpsReqTunnel); err != nil {
+			panic(err)
+		}
+	}
+
 	// start the heartbeat
 	lastPong := time.Now().UnixNano()
 	c.ctl.Go(func() { c.heartbeat(&lastPong, conn) })
@@ -229,15 +234,17 @@ func (c *ClientModel) control(reg *msg.RegMsg, localaddr string) {
 		}
 
 		switch m := rawMsg.(type) {
-		case *msg.ReqProxyMsg:
+		case *msg.ReqProxy:
 			c.ctl.Go(c.proxy)
 
-		case *msg.PongMsg:
+		case *msg.Pong:
 			atomic.StoreInt64(&lastPong, time.Now().UnixNano())
 
-		case *msg.RegAckMsg:
+		case *msg.NewTunnel:
 			if m.Error != "" {
-				c.Error("Server failed to allocate tunnel: %s", regAck.Error)
+				emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error)
+				c.Error(emsg)
+				c.ctl.Shutdown(emsg)
 				continue
 			}
 
@@ -266,22 +273,22 @@ func (c *ClientModel) proxy() {
 	}
 
 	defer remoteConn.Close()
-	err = msg.WriteMsg(remoteConn, &msg.RegProxyMsg{ClientId: c.id})
+	err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id})
 	if err != nil {
-		log.Error("Failed to write RegProxyMsg: %v", err)
+		log.Error("Failed to write RegProxy: %v", err)
 		return
 	}
 
 	// wait for the server to ack our register
-	var startPxyMsg msg.StartProxyMsg
-	if err = msg.ReadMsgInto(remoteConn, &startPxyMsg); err != nil {
-		log.Error("Server failed to write StartProxyMsg: %v", err)
+	var startPxy msg.StartProxy
+	if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil {
+		log.Error("Server failed to write StartProxy: %v", err)
 		return
 	}
 
-	tunnel, ok := c.tunnels[startPxyMsg.Url]
+	tunnel, ok := c.tunnels[startPxy.Url]
 	if !ok {
-		c.Error("Couldn't find tunnel for proxy: %s", startPxyMsg.Url)
+		c.Error("Couldn't find tunnel for proxy: %s", startPxy.Url)
 		return
 	}
 
@@ -309,7 +316,7 @@ Content-Length: %d
 	m.connMeter.Mark(1)
 	c.update()
 	m.connTimer.Time(func() {
-		localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxyMsg.ClientAddr})
+		localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr})
 		bytesIn, bytesOut := conn.Join(localConn, remoteConn)
 		m.bytesIn.Update(bytesIn)
 		m.bytesOut.Update(bytesOut)
@@ -345,7 +352,7 @@ func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) {
 			}
 
 		case <-ping.C:
-			err := msg.WriteMsg(conn, &msg.PingMsg{})
+			err := msg.WriteMsg(conn, &msg.Ping{})
 			if err != nil {
 				conn.Debug("Got error %v when writing PingMsg", err)
 				return

+ 1 - 1
src/ngrok/client/mvc/model.go

@@ -5,7 +5,7 @@ import (
 )
 
 type Model interface {
-	Run(serverAddr, authToken string, ctl Controller, reg *msg.RegMsg, localaddr string)
+	Run(serverAddr, authToken string, ctl Controller, reqTunnel *msg.ReqTunnel, localaddr string)
 
 	Shutdown()
 

+ 1 - 7
src/ngrok/client/views/web/http.go

@@ -154,12 +154,6 @@ func (whv *WebHttpView) updateHttp() {
 		// we haven't processed this transaction yet if we haven't set the
 		// user data
 		if htxn.UserCtx == nil {
-			id, err := util.RandId(8)
-			if err != nil {
-				whv.Error("Failed to generate txn identifier for web storage: %v", err)
-				continue
-			}
-
 			rawReq, err := httputil.DumpRequestOut(htxn.Req.Request, true)
 			if err != nil {
 				whv.Error("Failed to dump request: %v", err)
@@ -168,7 +162,7 @@ func (whv *WebHttpView) updateHttp() {
 
 			body := makeBody(htxn.Req.Header, htxn.Req.BodyBytes)
 			whtxn := &SerializedTxn{
-				Id:      id,
+				Id:      util.RandId(8),
 				HttpTxn: htxn,
 				Req: SerializedRequest{
 					MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path,

+ 69 - 33
src/ngrok/msg/msg.go

@@ -11,15 +11,15 @@ func init() {
 	TypeMap = make(map[string]reflect.Type)
 
 	t := func(obj interface{}) reflect.Type { return reflect.TypeOf(obj).Elem() }
-	TypeMap["RegMsg"] = t((*RegMsg)(nil))
-	TypeMap["RegAckMsg"] = t((*RegAckMsg)(nil))
-	TypeMap["RegProxyMsg"] = t((*RegProxyMsg)(nil))
-	TypeMap["ReqProxyMsg"] = t((*ReqProxyMsg)(nil))
-	TypeMap["StartProxyMsg"] = t((*StartProxyMsg)(nil))
-	TypeMap["PingMsg"] = t((*PingMsg)(nil))
-	TypeMap["PongMsg"] = t((*PongMsg)(nil))
-	TypeMap["VerisonMsg"] = t((*VersionMsg)(nil))
-	TypeMap["VersionRespMsg"] = t((*VersionRespMsg)(nil))
+	TypeMap["Auth"] = t((*Auth)(nil))
+	TypeMap["AuthResp"] = t((*AuthResp)(nil))
+	TypeMap["ReqTunnel"] = t((*ReqTunnel)(nil))
+	TypeMap["NewTunnel"] = t((*NewTunnel)(nil))
+	TypeMap["RegProxy"] = t((*RegProxy)(nil))
+	TypeMap["ReqProxy"] = t((*ReqProxy)(nil))
+	TypeMap["StartProxy"] = t((*StartProxy)(nil))
+	TypeMap["Ping"] = t((*Ping)(nil))
+	TypeMap["Pong"] = t((*Pong)(nil))
 }
 
 type Message interface{}
@@ -29,51 +29,87 @@ type Envelope struct {
 	Payload json.RawMessage
 }
 
-type RegMsg struct {
-	Version   string
-	MmVersion string
-	Protocol  string
-	Hostname  string
-	Subdomain string
-	ClientId  string
-	HttpAuth  string
+// When a client opens a new control channel to the server
+// it must start by sending an Auth message.
+type Auth struct {
+	Version   string // protocol version
+	MmVersion string // major/minor software version (informational only)
 	User      string
 	Password  string
 	OS        string
 	Arch      string
+	ClientId  string // empty for new sessions
 }
 
-type RegAckMsg struct {
+// A server responds to an Auth message with an
+// AuthResp message over the control channel.
+//
+// If Error is not the empty string
+// the server has indicated it will not accept
+// the new session and will close the connection.
+//
+// The server response includes a unique ClientId
+// that is used to associate and authenticate future
+// proxy connections via the same field in RegProxy messages.
+type AuthResp struct {
 	Version   string
 	MmVersion string
-	Url       string
-	Protocol  string
-	Error     string
 	ClientId  string
+	Error     string
 }
 
-type ReqProxyMsg struct {
+// A client sends this message to the server over the control channel
+// to request a new tunnel be opened on the client's behalf.
+// ReqId is a random number set by the client that it can pull
+// from future NewTunnel's to correlate then to the requesting ReqTunnel.
+type ReqTunnel struct {
+	ReqId     string
+	Protocol  string
+	Hostname  string
+	Subdomain string
+	HttpAuth  string
 }
 
-type RegProxyMsg struct {
-	ClientId string
+// When the server opens a new tunnel on behalf of
+// a client, it sends a NewTunnel message to notify the client.
+// ReqId is the ReqId from the corresponding ReqTunnel message.
+//
+// A client may receive *multiple* NewTunnel messages from a single
+// ReqTunnel. (ex. A client opens an https tunnel and the server
+// chooses to open an http tunnel of the same name as well)
+type NewTunnel struct {
+	ReqId    string
+	Url      string
+	Protocol string
+	Error    string
 }
 
-type StartProxyMsg struct {
-	Url        string
-	ClientAddr string
+// When the server wants to initiate a new tunneled connection, it sends
+// this message over the control channel to the client. When a client receives
+// this message, it must initiate a new proxy connection to the server.
+type ReqProxy struct {
 }
 
-type PingMsg struct {
+// After a client receives a ReqProxy message, it opens a new
+// connection to the server and sends a RegProxy message.
+type RegProxy struct {
+	ClientId string
 }
 
-type PongMsg struct {
+// This message is sent by the server to the client over a *proxy* connection before it
+// begins to send the bytes of the proxied request.
+type StartProxy struct {
+	Url        string // URL of the tunnel this connection connection is being proxied for
+	ClientAddr string // Network address of the client initiating the connection to the tunnel
 }
 
-type VersionMsg struct {
+// A client or server may send this message periodically over
+// the control channel to request that the remote side acknowledge
+// its connection is still alive. The remote side must respond with a Pong.
+type Ping struct {
 }
 
-type VersionRespMsg struct {
-	Version   string
-	MmVersion string
+// Sent by a client or server over the control channel to indicate
+// it received a Ping.
+type Pong struct {
 }

+ 45 - 39
src/ngrok/server/control.go

@@ -18,6 +18,9 @@ const (
 )
 
 type Control struct {
+	// auth message
+	auth *msg.Auth
+
 	// actual connection
 	conn conn.Conn
 
@@ -50,11 +53,14 @@ type Control struct {
 	id string
 }
 
-func NewControl(ctlConn conn.Conn, regMsg *msg.RegMsg) {
+func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
+	var err error
+
 	// create the object
 	// channels are buffered because we read and write to them
 	// from the same goroutine in managerThread()
 	c := &Control{
+		auth:     authMsg,
 		conn:     ctlConn,
 		out:      make(chan msg.Message, 5),
 		in:       make(chan msg.Message, 5),
@@ -63,37 +69,52 @@ func NewControl(ctlConn conn.Conn, regMsg *msg.RegMsg) {
 		lastPing: time.Now(),
 	}
 
-	// assign the random id
-	serverId, err := util.RandId(8)
-	if err != nil {
-		c.stop <- &msg.RegAckMsg{Error: err.Error()}
+	failAuth := func(e error) {
+		_ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()})
+		ctlConn.Close()
+	}
+
+	// register the clientid
+	c.id = authMsg.ClientId
+	if c.id == "" {
+		// it's a new session, assign an ID
+		if c.id, err = util.SecureRandId(16); err != nil {
+			failAuth(err)
+			return
+		}
+	}
+
+	if authMsg.Version != version.Proto {
+		failAuth(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), authMsg.Version))
+		return
 	}
-	c.id = fmt.Sprintf("%s-%s", regMsg.ClientId, serverId)
 
 	// register the control
-	err = controlRegistry.Add(c.id, c)
-	if err != nil {
-		c.stop <- &msg.RegAckMsg{Error: err.Error()}
+	controlRegistry.Add(c.id, c)
+
+	c.out <- &msg.AuthResp{
+		Version:   version.Proto,
+		MmVersion: version.MajorMinor(),
+		ClientId:  c.id,
 	}
 
+	// As a performance optimization, ask for a proxy connection up front
+	c.out <- &msg.ReqProxy{}
+
 	// set logging prefix
 	ctlConn.SetType("ctl")
 
-	// register the first tunnel
-	c.in <- regMsg
-
 	// manage the connection
 	go c.managerThread()
 	go c.readThread()
-
 }
 
 // Register a new tunnel on this control connection
-func (c *Control) registerTunnel(regMsg *msg.RegMsg) {
+func (c *Control) registerTunnel(reqTunnel *msg.ReqTunnel) {
 	c.conn.Debug("Registering new tunnel")
-	t, err := NewTunnel(regMsg, c)
+	t, err := NewTunnel(reqTunnel, c)
 	if err != nil {
-		ack := &msg.RegAckMsg{Error: err.Error()}
+		ack := &msg.NewTunnel{Error: err.Error()}
 		if len(c.tunnels) == 0 {
 			// you can't fail your first tunnel registration
 			// terminate the control connection
@@ -111,18 +132,9 @@ func (c *Control) registerTunnel(regMsg *msg.RegMsg) {
 	c.tunnels = append(c.tunnels, t)
 
 	// acknowledge success
-	c.out <- &msg.RegAckMsg{
-		Url:       t.url,
-		Protocol:  regMsg.Protocol,
-		Version:   version.Proto,
-		MmVersion: version.MajorMinor(),
-		ClientId:  c.id,
-	}
-
-	if regMsg.Protocol == "http" {
-		httpsRegMsg := *regMsg
-		httpsRegMsg.Protocol = "https"
-		c.in <- &httpsRegMsg
+	c.out <- &msg.NewTunnel{
+		Url:      t.url,
+		Protocol: reqTunnel.Protocol,
 	}
 }
 
@@ -182,18 +194,12 @@ func (c *Control) managerThread() {
 
 		case mRaw := <-c.in:
 			switch m := mRaw.(type) {
-			case *msg.RegMsg:
+			case *msg.ReqTunnel:
 				c.registerTunnel(m)
 
-			case *msg.PingMsg:
+			case *msg.Ping:
 				c.lastPing = time.Now()
-				c.out <- &msg.PongMsg{}
-
-			case *msg.VersionMsg:
-				c.out <- &msg.VersionRespMsg{
-					Version:   version.Proto,
-					MmVersion: version.MajorMinor(),
-				}
+				c.out <- &msg.Pong{}
 			}
 		}
 	}
@@ -260,7 +266,7 @@ func (c *Control) GetProxy() (proxyConn conn.Conn, err error) {
 		case <-timeout.C:
 			c.conn.Debug("Requesting new proxy connection")
 			// request a proxy connection
-			c.out <- &msg.ReqProxyMsg{}
+			c.out <- &msg.ReqProxy{}
 			// timeout after 1 second if we don't get one
 			timeout.Reset(1 * time.Second)
 		}
@@ -273,7 +279,7 @@ func (c *Control) GetProxy() (proxyConn conn.Conn, err error) {
 	// There are two major issues with this strategy: it's not thread safe and it's not predictive.
 	// It should be a good start though.
 	if len(c.proxies) == 0 {
-		c.out <- &msg.ReqProxyMsg{}
+		c.out <- &msg.ReqProxy{}
 	}
 
 	return

+ 1 - 1
src/ngrok/server/http.go

@@ -89,7 +89,7 @@ func httpHandler(tcpConn net.Conn, proto string) {
 	// If the client specified http auth and it doesn't match this request's auth
 	// then fail the request with 401 Not Authorized and request the client reissue the
 	// request with basic authdeny the request
-	if tunnel.regMsg.HttpAuth != "" && req.Header.Get("Authorization") != tunnel.regMsg.HttpAuth {
+	if tunnel.req.HttpAuth != "" && req.Header.Get("Authorization") != tunnel.req.HttpAuth {
 		conn.Info("Authentication failed: %s", req.Header.Get("Authorization"))
 		conn.Write([]byte(NotAuthorized))
 		return

+ 9 - 9
src/ngrok/server/main.go

@@ -15,15 +15,15 @@ const (
 
 // GLOBALS
 var (
-	tunnelRegistry    *TunnelRegistry
-	controlRegistry   *ControlRegistry
+	tunnelRegistry  *TunnelRegistry
+	controlRegistry *ControlRegistry
 
 	// XXX: kill these global variables - they're only used in tunnel.go for constructing forwarding URLs
-	opts              *Options
-	listeners         map[string] *conn.Listener
+	opts      *Options
+	listeners map[string]*conn.Listener
 )
 
-func NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxyMsg) {
+func NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxy) {
 	// fail gracefully if the proxy connection fails to register
 	defer func() {
 		if r := recover(); r != nil {
@@ -58,7 +58,7 @@ func tunnelListener(addr string) {
 		panic(err)
 	}
 
-	log.Info("Listening for control and proxy connections on %d", listener.Addr.String())
+	log.Info("Listening for control and proxy connections on %s", listener.Addr.String())
 	for c := range listener.Conns {
 		var rawMsg msg.Message
 		if rawMsg, err = msg.ReadMsg(c); err != nil {
@@ -67,10 +67,10 @@ func tunnelListener(addr string) {
 		}
 
 		switch m := rawMsg.(type) {
-		case *msg.RegMsg:
+		case *msg.Auth:
 			go NewControl(c, m)
 
-		case *msg.RegProxyMsg:
+		case *msg.RegProxy:
 			go NewProxy(c, m)
 		}
 	}
@@ -96,7 +96,7 @@ func Main() {
 	controlRegistry = NewControlRegistry()
 
 	// start listeners
-	listeners = make(map[string] *conn.Listener)
+	listeners = make(map[string]*conn.Listener)
 
 	// listen for http
 	if opts.httpAddr != "" {

+ 16 - 16
src/ngrok/server/metrics.go

@@ -94,7 +94,7 @@ func NewLocalMetrics(reportInterval time.Duration) *LocalMetrics {
 func (m *LocalMetrics) OpenTunnel(t *Tunnel) {
 	m.tunnelMeter.Mark(1)
 
-	switch t.regMsg.OS {
+	switch t.ctl.auth.OS {
 	case "windows":
 		m.windowsCounter.Inc(1)
 	case "linux":
@@ -105,7 +105,7 @@ func (m *LocalMetrics) OpenTunnel(t *Tunnel) {
 		m.otherCounter.Inc(1)
 	}
 
-	switch t.regMsg.Protocol {
+	switch t.req.Protocol {
 	case "tcp":
 		m.tcpTunnelMeter.Mark(1)
 	case "http":
@@ -236,14 +236,14 @@ func (k *KeenIoMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time,
 		Keen: KeenStruct{
 			Timestamp: start.UTC().Format("2006-01-02T15:04:05.000Z"),
 		},
-		OS:                 t.regMsg.OS,
-		ClientId:           t.regMsg.ClientId,
-		Protocol:           t.regMsg.Protocol,
+		OS:                 t.ctl.auth.OS,
+		ClientId:           t.ctl.id,
+		Protocol:           t.req.Protocol,
 		Url:                t.url,
-		User:               t.regMsg.User,
-		Version:            t.regMsg.MmVersion,
-		HttpAuth:           t.regMsg.HttpAuth != "",
-		Subdomain:          t.regMsg.Subdomain != "",
+		User:               t.ctl.auth.User,
+		Version:            t.ctl.auth.MmVersion,
+		HttpAuth:           t.req.HttpAuth != "",
+		Subdomain:          t.req.Subdomain != "",
 		TunnelDuration:     time.Since(t.start).Seconds(),
 		ConnectionDuration: time.Since(start).Seconds(),
 		BytesIn:            in,
@@ -281,16 +281,16 @@ func (k *KeenIoMetrics) CloseTunnel(t *Tunnel) {
 		Keen: KeenStruct{
 			Timestamp: t.start.UTC().Format("2006-01-02T15:04:05.000Z"),
 		},
-		OS:       t.regMsg.OS,
-		ClientId: t.regMsg.ClientId,
-		Protocol: t.regMsg.Protocol,
+		OS:       t.ctl.auth.OS,
+		ClientId: t.ctl.id,
+		Protocol: t.req.Protocol,
 		Url:      t.url,
-		User:     t.regMsg.User,
-		Version:  t.regMsg.MmVersion,
+		User:     t.ctl.auth.User,
+		Version:  t.ctl.auth.MmVersion,
 		//Reason: reason,
 		Duration:  time.Since(t.start).Seconds(),
-		HttpAuth:  t.regMsg.HttpAuth != "",
-		Subdomain: t.regMsg.Subdomain != "",
+		HttpAuth:  t.req.HttpAuth != "",
+		Subdomain: t.req.Subdomain != "",
 	})
 
 	if err != nil {

+ 6 - 11
src/ngrok/server/registry.go

@@ -92,10 +92,10 @@ func (r *TunnelRegistry) Register(url string, t *Tunnel) error {
 
 func (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) {
 	clientIp := t.ctl.conn.RemoteAddr().(*net.TCPAddr).IP.String()
-	clientId := t.regMsg.ClientId
+	clientId := t.ctl.id
 
-	ipKey := fmt.Sprintf("client-ip-%s:%s", t.regMsg.Protocol, clientIp)
-	idKey := fmt.Sprintf("client-id-%s:%s", t.regMsg.Protocol, clientId)
+	ipKey := fmt.Sprintf("client-ip-%s:%s", t.req.Protocol, clientIp)
+	idKey := fmt.Sprintf("client-id-%s:%s", t.req.Protocol, clientId)
 	return ipKey, idKey
 }
 
@@ -180,16 +180,11 @@ func (r *ControlRegistry) Get(clientId string) *Control {
 	return r.controls[clientId]
 }
 
-func (r *ControlRegistry) Add(clientId string, ctl *Control) error {
+func (r *ControlRegistry) Add(clientId string, ctl *Control) {
 	r.Lock()
 	defer r.Unlock()
-	if r.controls[clientId] == nil {
-		r.Info("Registered control with id %s", clientId)
-		r.controls[clientId] = ctl
-		return nil
-	} else {
-		return fmt.Errorf("Client with id %s already registered!", clientId)
-	}
+	r.controls[clientId] = ctl
+	r.Info("Registered control with id %s", clientId)
 }
 
 func (r *ControlRegistry) Del(clientId string) error {

+ 8 - 13
src/ngrok/server/tunnel.go

@@ -8,7 +8,6 @@ import (
 	"ngrok/conn"
 	"ngrok/log"
 	"ngrok/msg"
-	"ngrok/version"
 	"os"
 	"strconv"
 	"strings"
@@ -27,7 +26,8 @@ var defaultPortMap = map[string]int{
  *         route public traffic to a firewalled endpoint.
  */
 type Tunnel struct {
-	regMsg *msg.RegMsg
+	// request that opened the tunnel
+	req *msg.ReqTunnel
 
 	// time when the tunnel was opened
 	start time.Time
@@ -71,14 +71,14 @@ func registerVhost(t *Tunnel, protocol string, servingPort int) (err error) {
 	t.url = strings.ToLower(t.url)
 
 	// Register for specific hostname
-	hostname := strings.TrimSpace(t.regMsg.Hostname)
+	hostname := strings.TrimSpace(t.req.Hostname)
 	if hostname != "" {
 		t.url = fmt.Sprintf("%s://%s", protocol, hostname)
 		return tunnelRegistry.Register(t.url, t)
 	}
 
 	// Register for specific subdomain
-	subdomain := strings.TrimSpace(t.regMsg.Subdomain)
+	subdomain := strings.TrimSpace(t.req.Subdomain)
 	if subdomain != "" {
 		t.url = fmt.Sprintf("%s://%s.%s", protocol, subdomain, vhost)
 		return tunnelRegistry.Register(t.url, t)
@@ -94,15 +94,15 @@ func registerVhost(t *Tunnel, protocol string, servingPort int) (err error) {
 
 // Create a new tunnel from a registration message received
 // on a control channel
-func NewTunnel(m *msg.RegMsg, ctl *Control) (t *Tunnel, err error) {
+func NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) {
 	t = &Tunnel{
-		regMsg: m,
+		req:    m,
 		start:  time.Now(),
 		ctl:    ctl,
 		Logger: log.NewPrefixLogger(),
 	}
 
-	proto := t.regMsg.Protocol
+	proto := t.req.Protocol
 	switch proto {
 	case "tcp":
 		var port int = 0
@@ -166,11 +166,6 @@ func NewTunnel(m *msg.RegMsg, ctl *Control) (t *Tunnel, err error) {
 		return
 	}
 
-	if m.Version != version.Proto {
-		err = fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), m.Version)
-		return
-	}
-
 	// pre-encode the http basic auth for fast comparisons later
 	if m.HttpAuth != "" {
 		m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth))
@@ -264,7 +259,7 @@ func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) {
 		proxyConn.AddLogPrefix(t.Id())
 
 		// tell the client we're going to start using this proxy connection
-		startPxyMsg := &msg.StartProxyMsg{
+		startPxyMsg := &msg.StartProxy{
 			Url:        t.url,
 			ClientAddr: publicConn.RemoteAddr().String(),
 		}

+ 21 - 20
src/ngrok/util/id.go

@@ -2,31 +2,32 @@ package util
 
 import (
 	"crypto/rand"
+	"encoding/binary"
 	"fmt"
+	mrand "math/rand"
 )
 
-func RandomSeed() (int64, error) {
-	b := make([]byte, 8)
-	n, err := rand.Read(b)
-	if n != 8 {
-		return 0, fmt.Errorf("Only generated %d random bytes, %d requested", n, 8)
-	}
-
-	if err != nil {
-		return 0, err
-	}
+func RandomSeed() (seed int64, err error) {
+	err = binary.Read(rand.Reader, binary.LittleEndian, &seed)
+	return
+}
 
-	var seed int64
-	var i uint
-	for i = 0; i < 8; i++ {
-		seed = seed | int64(b[i]<<(i*8))
+// creates a random identifier of the specified length
+func RandId(idlen int) string {
+	b := make([]byte, idlen)
+	var randVal uint32
+	for i := 0; i < idlen; i++ {
+		byteIdx := i % 4
+		if byteIdx == 0 {
+			randVal = mrand.Uint32()
+		}
+		b[i] = byte((randVal >> (8 * uint(byteIdx))) & 0xFF)
 	}
-
-	return seed, nil
+	return fmt.Sprintf("%x", b)
 }
 
-// create a random identifier for this client
-func RandId(idlen int) (id string, err error) {
+// like RandId, but uses a crypto/rand for secure random identifiers
+func SecureRandId(idlen int) (id string, err error) {
 	b := make([]byte, idlen)
 	n, err := rand.Read(b)
 
@@ -43,8 +44,8 @@ func RandId(idlen int) (id string, err error) {
 	return
 }
 
-func RandIdOrPanic(idlen int) string {
-	id, err := RandId(idlen)
+func SecureRandIdOrPanic(idlen int) string {
+	id, err := SecureRandId(idlen)
 	if err != nil {
 		panic(err)
 	}