Esempio n. 1
0
func (whv *WebHttpView) update() {
	// open channels for incoming http state changes
	// and broadbasts
	txnUpdates := whv.httpProto.Txns.Reg()
	for {
		select {
		case txn := <-txnUpdates:
			// XXX: it's not safe for proto.Http and this code
			// to be accessing txn and txn.(req/resp) without synchronization
			htxn := txn.(*proto.HttpTxn)

			// XXX: golang, why do I have to do this to make DumpRequestOut work later?
			htxn.Req.URL.Scheme = "http"

			if htxn.Resp == nil {
				whtxn := &WebHttpTxn{Id: util.RandId(), HttpTxn: htxn}

				// 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
				whv.HttpRequests.Add(whtxn)
			}
		}
	}
}
Esempio n. 2
0
func (whv *WebHttpView) updateHttp() {
	// open channels for incoming http state changes
	// and broadbasts
	txnUpdates := whv.httpProto.Txns.Reg()
	for txn := range txnUpdates {
		// XXX: it's not safe for proto.Http and this code
		// to be accessing txn and txn.(req/resp) without synchronization
		htxn := txn.(*proto.HttpTxn)

		// we haven't processed this transaction yet if we haven't set the
		// user data
		if htxn.UserData == nil {
			id, err := util.RandId(8)
			if err != nil {
				log.Error("Failed to generate txn identifier for web storage: %v", err)
				continue
			}

			rawReq, err := httputil.DumpRequest(htxn.Req.Request, true)
			if err != nil {
				log.Error("Failed to dump request: %v", err)
				continue
			}

			body := makeBody(htxn.Req.Header, htxn.Req.BodyBytes)
			whtxn := &SerializedTxn{
				Id:      id,
				HttpTxn: htxn,
				Req: SerializedRequest{
					MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path,
					Raw:        base64.StdEncoding.EncodeToString(rawReq),
					Params:     htxn.Req.URL.Query(),
					Header:     htxn.Req.Header,
					Body:       body,
					Binary:     !utf8.Valid(rawReq),
				},
				Start: htxn.Start.Unix(),
			}

			htxn.UserData = 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
			whv.HttpRequests.Add(whtxn)
		} else {
			rawResp, err := httputil.DumpResponse(htxn.Resp.Response, true)
			if err != nil {
				log.Error("Failed to dump response: %v", err)
				continue
			}

			txn := htxn.UserData.(*SerializedTxn)
			body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes)
			txn.Duration = htxn.Duration.Nanoseconds()
			txn.Resp = SerializedResponse{
				Status: htxn.Resp.Status,
				Raw:    base64.StdEncoding.EncodeToString(rawResp),
				Header: htxn.Resp.Header,
				Body:   body,
				Binary: !utf8.Valid(rawResp),
			}

			payload, err := json.Marshal(txn)
			if err != nil {
				log.Error("Failed to serialized txn payload for websocket: %v", err)
			}
			whv.webview.wsMessages.In() <- payload
		}
	}
}
Esempio n. 3
0
// Establishes and manages a tunnel control connection with the server
func (c *ClientModel) control() {
	defer func() {
		if r := recover(); r != nil {
			log.Error("control recovering from failure %v", r)
		}
	}()

	// establish control channel
	var (
		ctlConn conn.Conn
		err     error
	)
	if c.proxyUrl == "" {
		// simple non-proxied case, just connect to the server
		ctlConn, err = conn.Dial(c.serverAddr, "ctl", c.tlsConfig)
	} else {
		ctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "ctl", c.tlsConfig)
	}
	if err != nil {
		panic(err)
	}
	defer ctlConn.Close()

	// 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(ctlConn, auth); err != nil {
		panic(err)
	}

	// wait for the server to authenticate us
	var authResp msg.AuthResp
	if err = msg.ReadMsgInto(ctlConn, &authResp); err != nil {
		panic(err)
	}

	if authResp.Error != "" {
		emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error)
		c.ctl.Shutdown(emsg)
		return
	}

	c.id = authResp.ClientId
	c.serverVersion = authResp.MmVersion
	c.Info("Authenticated with server, client id: %v", c.id)
	c.update()
	if err = SaveAuthToken(c.configPath, c.authToken); err != nil {
		c.Error("Failed to save auth token: %v", err)
	}

	// request tunnels
	reqIdToTunnelConfig := make(map[string]*TunnelConfiguration)
	for _, config := range c.tunnelConfig {
		// create the protocol list to ask for
		var protocols []string
		for proto, _ := range config.Protocols {
			protocols = append(protocols, proto)
		}

		reqTunnel := &msg.ReqTunnel{
			ReqId:      util.RandId(8),
			Protocol:   strings.Join(protocols, "+"),
			Hostname:   config.Hostname,
			Subdomain:  config.Subdomain,
			HttpAuth:   config.HttpAuth,
			RemotePort: config.RemotePort,
		}

		// send the tunnel request
		if err = msg.WriteMsg(ctlConn, reqTunnel); err != nil {
			panic(err)
		}

		// save request id association so we know which local address
		// to proxy to later
		reqIdToTunnelConfig[reqTunnel.ReqId] = config
	}

	// start the heartbeat
	lastPong := time.Now().UnixNano()
	c.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) })

	// main control loop
	for {
		var rawMsg msg.Message
		if rawMsg, err = msg.ReadMsg(ctlConn); err != nil {
			panic(err)
		}

		switch m := rawMsg.(type) {
		case *msg.ReqProxy:
			c.ctl.Go(c.proxy)

		case *msg.Pong:
			atomic.StoreInt64(&lastPong, time.Now().UnixNano())

		case *msg.NewTunnel:
			if m.Error != "" {
				emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error)
				c.Error(emsg)
				c.ctl.Shutdown(emsg)
				continue
			}

			tunnel := mvc.Tunnel{
				PublicUrl: m.Url,
				LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol],
				Protocol:  c.protoMap[m.Protocol],
			}

			c.tunnels[tunnel.PublicUrl] = tunnel
			c.connStatus = mvc.ConnOnline
			c.Info("Tunnel established at %v", tunnel.PublicUrl)
			c.update()

		default:
			ctlConn.Warn("Ignoring unknown control message %v ", m)
		}
	}
}
Esempio n. 4
0
func Main() {
	// parse options
	opts := parseArgs()

	// set up logging
	log.LogTo(opts.logto)

	// init client state
	s := &State{
		status: "connecting",

		// unique client id
		id: util.RandId(),

		// command-line options
		opts: opts,

		// metrics
		metrics: NewClientMetrics(),
	}

	switch opts.protocol {
	case "http":
		s.protocol = proto.NewHttp()
	case "tcp":
		s.protocol = proto.NewTcp()
	}

	// init ui
	ctl := ui.NewController()
	web.NewWebView(ctl, s, opts.webport)
	if opts.logto != "stdout" {
		term.New(ctl, s)
	}

	go reconnectingControl(s, ctl)
	go versionCheck(s, ctl)

	quitMessage := ""
	ctl.Wait.Add(1)
	go func() {
		defer ctl.Wait.Done()
		for {
			select {
			case obj := <-ctl.Cmds:
				switch cmd := obj.(type) {
				case ui.CmdQuit:
					quitMessage = cmd.Message
					ctl.DoShutdown()
					return
				case ui.CmdRequest:
					go func() {
						var localConn conn.Conn
						localConn, err := conn.Dial(s.opts.localaddr, "prv")
						if err != nil {
							log.Warn("Failed to open private leg %s: %v", s.opts.localaddr, err)
							return
						}
						//defer localConn.Close()
						localConn = s.protocol.WrapConn(localConn)
						localConn.Write(cmd.Payload)
						ioutil.ReadAll(localConn)
					}()
				}
			}
		}
	}()

	ctl.Wait.Wait()
	fmt.Println(quitMessage)
}