123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package client
- import (
- log "code.google.com/p/log4go"
- "crypto/rand"
- "fmt"
- "net"
- "ngrok"
- "ngrok/client/ui"
- "ngrok/conn"
- "ngrok/proto"
- "runtime"
- "time"
- )
- /**
- * Connect to the ngrok server
- */
- func connect(addr string, typ string) (c conn.Conn, err error) {
- var (
- tcpAddr *net.TCPAddr
- tcpConn *net.TCPConn
- )
- if tcpAddr, err = net.ResolveTCPAddr("tcp", addr); err != nil {
- return
- }
- //log.Debug("Dialing %v", addr)
- if tcpConn, err = net.DialTCP("tcp", nil, tcpAddr); err != nil {
- return
- }
- c = conn.NewTCP(tcpConn, typ)
- c.Debug("Connected to: %v", tcpAddr)
- return c, nil
- }
- /**
- * Establishes and manages a tunnel proxy connection with the server
- */
- func proxy(proxyAddr string, s *State) {
- start := time.Now()
- remoteConn, err := connect(proxyAddr, "pxy")
- if err != nil {
- panic(err)
- }
- defer remoteConn.Close()
- err = proto.WriteMsg(remoteConn, &proto.RegProxyMsg{Url: s.publicUrl})
- if err != nil {
- panic(err)
- }
- localConn, err := connect(s.opts.localaddr, "prv")
- if err != nil {
- remoteConn.Warn("Failed to open private leg %s: %v", s.opts.localaddr, err)
- return
- }
- defer localConn.Close()
- m := s.metrics
- m.proxySetupTimer.Update(time.Since(start))
- m.connMeter.Mark(1)
- s.Update()
- m.connTimer.Time(func() {
- if s.opts.protocol == "http" {
- teeConn := conn.NewTee(remoteConn)
- remoteConn = teeConn
- go conn.ParseHttp(teeConn, s.history.reqs, s.history.resps)
- }
- bytesIn, bytesOut := conn.Join(localConn, remoteConn)
- m.bytesIn.Update(bytesIn)
- m.bytesOut.Update(bytesOut)
- m.bytesInCount.Inc(bytesIn)
- m.bytesOutCount.Inc(bytesOut)
- })
- s.Update()
- }
- /**
- * Establishes and manages a tunnel control connection with the server
- */
- func control(s *State) {
- defer func() {
- if r := recover(); r != nil {
- log.Error("Recovering from failure %v, attempting to reconnect to server after 10 seconds . . .", r)
- time.Sleep(10 * time.Second)
- s.status = "reconnecting"
- s.Update()
- go control(s)
- }
- }()
- // establish control channel
- conn, err := connect(s.opts.server, "ctl")
- if err != nil {
- panic(err)
- }
- defer conn.Close()
- // register with the server
- err = proto.WriteMsg(conn, &proto.RegMsg{
- Protocol: s.opts.protocol,
- OS: runtime.GOOS,
- Hostname: s.opts.hostname,
- Subdomain: s.opts.subdomain,
- ClientId: s.id,
- })
- if err != nil {
- panic(err)
- }
- // wait for the server to ack our register
- var regAck proto.RegAckMsg
- if err = proto.ReadMsgInto(conn, ®Ack); err != nil {
- panic(err)
- }
- // update UI state
- conn.Info("Tunnel established at %v", regAck.Url)
- //state.version = regAck.Version
- s.publicUrl = regAck.Url
- s.status = "online"
- s.Update()
- // main control loop
- for {
- var msg proto.Message
- if msg, err = proto.ReadMsg(conn); err != nil {
- panic(err)
- }
- switch msg.GetType() {
- case "ReqProxyMsg":
- go proxy(regAck.ProxyAddr, s)
- case "PingMsg":
- proto.WriteMsg(conn, &proto.PongMsg{})
- }
- }
- }
- // create a random identifier for this client
- func mkid() string {
- b := make([]byte, 8)
- _, err := rand.Read(b)
- if err != nil {
- panic(fmt.Sprintf("Couldn't create random client identifier, %v", err))
- }
- return fmt.Sprintf("%x", b)
- }
- func Main() {
- ngrok.LogToFile()
- // ngrok.LogToConsole()
- // parse options
- opts := parseArgs()
- // init terminal, http UI
- //termView := ui.NewTerm()
- httpView := ui.NewHttp(9999)
- // init client state
- s := &State{
- // unique client id
- id: mkid(),
- // ui communication channels
- // ui: ui.NewUi(termView, httpView),
- ui: ui.NewUi(httpView),
- // command-line options
- opts: opts,
- // metrics
- metrics: NewClientMetrics(),
- }
- // request history
- // XXX: don't use a callback, use a channel
- // and define it inline in the struct
- s.history = NewRequestHistory(opts.historySize, s.metrics, func(history []*RequestHistoryEntry) {
- s.historyEntries = history
- s.Update()
- })
- // set initial ui state
- s.status = "connecting"
- s.Update()
- go control(s)
- s.ui.Wait.Add(1)
- go func() {
- defer s.ui.Wait.Done()
- for {
- select {
- case cmd := <-s.ui.Cmds:
- switch cmd {
- case ui.QUIT:
- s.stopping = true
- s.Update()
- return
- }
- }
- }
- }()
- s.ui.Wait.Wait()
- }
|