Browse Source

properly display client ip addresses without relying on X-Real-Ip to be set by a proxy

Alan Shreve 12 years ago
parent
commit
b488fe6bd7

+ 1 - 1
assets/client/page.html

@@ -81,7 +81,7 @@
                         </div>
                         <div  class="span4">
                             <i class="icon-user"></i> IP
-                            <span style="margin-left: 8px;" class="muted">{{Txn.Req.Header['X-Real-Ip'][0]}}</span>
+                            <span style="margin-left: 8px;" class="muted">{{Txn.ConnCtx.ClientAddr.split(":")[0]}}</span>
                         </div>
                     </div>
                     <hr />

+ 6 - 9
src/ngrok/client/model.go

@@ -109,17 +109,15 @@ func (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) {
 
 // mvc.Model interface
 func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) {
-	t := c.tunnels[tunnel.PublicUrl]
-
 	var localConn conn.Conn
-	localConn, err := conn.Dial(t.LocalAddr, "prv", nil)
+	localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil)
 	if err != nil {
-		c.Warn("Failed to open private leg to %s: %v", t.LocalAddr, err)
+		c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err)
 		return
 	}
-	//defer localConn.Close()
-	// XXX: send user context that indicates it's a replayed connection
-	localConn = t.Protocol.WrapConn(localConn, nil)
+
+	defer localConn.Close()
+	localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"})
 	localConn.Write(payload)
 	ioutil.ReadAll(localConn)
 }
@@ -307,8 +305,7 @@ Content-Length: %d
 	m.connMeter.Mark(1)
 	c.update()
 	m.connTimer.Time(func() {
-		// XXX: wrap with connection context
-		localConn := tunnel.Protocol.WrapConn(localConn, nil)
+		localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxyMsg.ClientAddr})
 		bytesIn, bytesOut := conn.Join(localConn, remoteConn)
 		m.bytesIn.Update(bytesIn)
 		m.bytesOut.Update(bytesOut)

+ 5 - 0
src/ngrok/client/mvc/state.go

@@ -28,6 +28,11 @@ type Tunnel struct {
 	LocalAddr string
 }
 
+type ConnectionContext struct {
+	Tunnel     Tunnel
+	ClientAddr string
+}
+
 type State interface {
 	GetClientVersion() string
 	GetServerVersion() string

+ 6 - 6
src/ngrok/client/views/web/http.go

@@ -22,6 +22,7 @@ type SerializedTxn struct {
 	Id             string
 	Duration       int64
 	Start          int64
+	ConnCtx        mvc.ConnectionContext
 	*proto.HttpTxn `json:"-"`
 	Req            SerializedRequest
 	Resp           SerializedResponse
@@ -152,7 +153,7 @@ func (whv *WebHttpView) updateHttp() {
 
 		// we haven't processed this transaction yet if we haven't set the
 		// user data
-		if htxn.UserData == nil {
+		if htxn.UserCtx == nil {
 			id, err := util.RandId(8)
 			if err != nil {
 				whv.Error("Failed to generate txn identifier for web storage: %v", err)
@@ -178,9 +179,10 @@ func (whv *WebHttpView) updateHttp() {
 					Binary:     !utf8.Valid(rawReq),
 				},
 				Start: htxn.Start.Unix(),
+				ConnCtx: htxn.ConnUserCtx.(mvc.ConnectionContext),
 			}
 
-			htxn.UserData = whtxn
+			htxn.UserCtx = whtxn
 			// XXX: unsafe map access from multiple go routines
 			whv.idToTxn[whtxn.Id] = whtxn
 			// XXX: use return value to delete from map so we don't leak memory
@@ -192,7 +194,7 @@ func (whv *WebHttpView) updateHttp() {
 				continue
 			}
 
-			txn := htxn.UserData.(*SerializedTxn)
+			txn := htxn.UserCtx.(*SerializedTxn)
 			body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes)
 			txn.Duration = htxn.Duration.Nanoseconds()
 			txn.Resp = SerializedResponse{
@@ -230,9 +232,7 @@ func (whv *WebHttpView) register() {
 				panic(err)
 			}
 			// XXX: pull the tunnel out of the transaction's user context
-			//h.ctl.PlayRequest(tunnel, bodyBytes)
-			var t mvc.Tunnel
-			whv.ctl.PlayRequest(t, bodyBytes)
+			whv.ctl.PlayRequest(txn.ConnCtx.Tunnel, bodyBytes)
 			w.Write([]byte(http.StatusText(200)))
 		} else {
 			http.Error(w, http.StatusText(400), 400)

+ 2 - 1
src/ngrok/msg/msg.go

@@ -60,7 +60,8 @@ type RegProxyMsg struct {
 }
 
 type StartProxyMsg struct {
-	Url string
+	Url        string
+	ClientAddr string
 }
 
 type PingMsg struct {

+ 13 - 15
src/ngrok/proto/http.go

@@ -23,21 +23,19 @@ type HttpResponse struct {
 }
 
 type HttpTxn struct {
-	Req          *HttpRequest
-	Resp         *HttpResponse
-	Start        time.Time
-	Duration     time.Duration
-	UserData     interface{}
-	ConnUserData interface{}
+	Req         *HttpRequest
+	Resp        *HttpResponse
+	Start       time.Time
+	Duration    time.Duration
+	UserCtx     interface{}
+	ConnUserCtx interface{}
 }
 
 type Http struct {
-	Txns         *util.Broadcast
-	reqGauge     metrics.Gauge
-	reqMeter     metrics.Meter
-	reqTimer     metrics.Timer
-	UserData     interface{}
-	ConnUserData interface{}
+	Txns     *util.Broadcast
+	reqGauge metrics.Gauge
+	reqMeter metrics.Meter
+	reqTimer metrics.Timer
 }
 
 func NewHttp() *Http {
@@ -60,12 +58,12 @@ func (h *Http) GetName() string { return "http" }
 func (h *Http) WrapConn(c conn.Conn, ctx interface{}) conn.Conn {
 	tee := conn.NewTee(c)
 	lastTxn := make(chan *HttpTxn)
-	go h.readRequests(tee, lastTxn)
+	go h.readRequests(tee, lastTxn, ctx)
 	go h.readResponses(tee, lastTxn)
 	return tee
 }
 
-func (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn) {
+func (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn, connCtx interface{}) {
 	for {
 		req, err := http.ReadRequest(tee.WriteBuffer())
 		if err != nil {
@@ -82,7 +80,7 @@ func (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn) {
 			tee.Warn("Failed to extract request body: %v", err)
 		}
 
-		txn := &HttpTxn{Start: time.Now()}
+		txn := &HttpTxn{Start: time.Now(), ConnUserCtx: connCtx}
 		txn.Req = &HttpRequest{Request: req}
 		txn.Req.BodyBytes, txn.Req.Body, err = extractBody(req.Body)
 

+ 30 - 11
src/ngrok/server/tunnel.go

@@ -240,18 +240,37 @@ func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) {
 	startTime := time.Now()
 	metrics.OpenConnection(t, publicConn)
 
-	// get a proxy connection
-	proxyConn, err := t.ctl.GetProxy()
-	if err != nil {
-		t.Warn("Failed to get proxy connection: %v", err)
-		return
+	var proxyConn conn.Conn
+	var attempts int
+	var err error
+	for {
+		// get a proxy connection
+		if proxyConn, err = t.ctl.GetProxy(); err != nil {
+			t.Warn("Failed to get proxy connection: %v", err)
+			return
+		}
+		defer proxyConn.Close()
+		t.Info("Got proxy connection %s", proxyConn.Id())
+		proxyConn.AddLogPrefix(t.Id())
+
+		// tell the client we're going to start using this proxy connection
+		startPxyMsg := &msg.StartProxyMsg{
+			Url: t.url,
+			ClientAddr: publicConn.RemoteAddr().String(),
+		}
+		if err = msg.WriteMsg(proxyConn, startPxyMsg); err != nil {
+			attempts += 1
+			proxyConn.Warn("Failed to write StartProxyMessage: %v, attempt %d", err, attempts)
+			if attempts > 3 {
+				// give up
+				publicConn.Error("Too many failures starting proxy connection")
+				return
+			}
+		} else {
+			// success
+			break
+		}
 	}
-	defer proxyConn.Close()
-	t.Info("Got proxy connection %s", proxyConn.Id())
-	proxyConn.AddLogPrefix(t.Id())
-
-	// tell the client we're going to start using this proxy connection
-	msg.WriteMsg(proxyConn, &msg.StartProxyMsg{Url: t.url})
 
 	// join the public and proxy connections
 	bytesIn, bytesOut := conn.Join(publicConn, proxyConn)