/** * Establishes and manages a tunnel proxy connection with the server */ func proxy(proxyAddr string, s *State, ctl *ui.Controller) { start := time.Now() remoteConn, err := conn.Dial(proxyAddr, "pxy", tlsConfig) if err != nil { // XXX: What is the proper response here? // display something to the user? // retry? // reset control connection? log.Error("Failed to establish proxy connection: %v", err) return } defer remoteConn.Close() err = msg.WriteMsg(remoteConn, &msg.RegProxyMsg{Url: s.publicUrl}) if err != nil { // XXX: What is the proper response here? // display something to the user? // retry? // reset control connection? log.Error("Failed to write RegProxyMsg: %v", err) return } localConn, err := conn.Dial(s.opts.localaddr, "prv", nil) if err != nil { remoteConn.Warn("Failed to open private leg %s: %v", s.opts.localaddr, err) badGatewayBody := fmt.Sprintf(BadGateway, s.publicUrl, s.opts.localaddr, s.opts.localaddr) remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway Content-Type: text/html Content-Length: %d %s`, len(badGatewayBody), badGatewayBody))) return } defer localConn.Close() m := s.metrics m.proxySetupTimer.Update(time.Since(start)) m.connMeter.Mark(1) ctl.Update(s) m.connTimer.Time(func() { localConn := s.protocol.WrapConn(localConn) bytesIn, bytesOut := conn.Join(localConn, remoteConn) m.bytesIn.Update(bytesIn) m.bytesOut.Update(bytesOut) m.bytesInCount.Inc(bytesIn) m.bytesOutCount.Inc(bytesOut) }) ctl.Update(s) }
// mvc.Model interface func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) { var localConn conn.Conn localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) if err != nil { c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err) return } defer localConn.Close() localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"}) localConn.Write(payload) ioutil.ReadAll(localConn) }
// Establishes and manages a tunnel proxy connection with the server func (c *ClientModel) proxy() { var ( remoteConn conn.Conn err error ) if c.proxyUrl == "" { remoteConn, err = conn.Dial(c.serverAddr, "pxy", c.tlsConfig) } else { remoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "pxy", c.tlsConfig) } if err != nil { log.Error("Failed to establish proxy connection: %v", err) return } defer remoteConn.Close() err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id}) if err != nil { remoteConn.Error("Failed to write RegProxy: %v", err) return } // wait for the server to ack our register var startPxy msg.StartProxy if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil { remoteConn.Error("Server failed to write StartProxy: %v", err) return } tunnel, ok := c.tunnels[startPxy.Url] if !ok { remoteConn.Error("Couldn't find tunnel for proxy: %s", startPxy.Url) return } // start up the private connection start := time.Now() localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) if err != nil { remoteConn.Warn("Failed to open private leg %s: %v", tunnel.LocalAddr, err) if tunnel.Protocol.GetName() == "http" { // try to be helpful when you're in HTTP mode and a human might see the output badGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr) remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway Content-Type: text/html Content-Length: %d %s`, len(badGatewayBody), badGatewayBody))) } return } defer localConn.Close() m := c.metrics m.proxySetupTimer.Update(time.Since(start)) m.connMeter.Mark(1) c.update() m.connTimer.Time(func() { 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) m.bytesInCount.Inc(bytesIn) m.bytesOutCount.Inc(bytesOut) }) c.update() }
// 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) } } }
func Main() { // parse options opts := parseArgs() // set up logging log.LogTo(opts.logto) // set up auth token if opts.authtoken == "" { opts.authtoken = LoadAuthToken() } // init client state s := &State{ status: "connecting", // unique client id id: util.RandIdOrPanic(8), // 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() if opts.webport != -1 { web.NewWebView(ctl, s, opts.webport) } if opts.logto != "stdout" { term.New(ctl, s) } go reconnectingControl(s, ctl) go autoUpdate(s, ctl, opts.authtoken) 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", nil) 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) }
/** * Establishes and manages a tunnel control connection with the server */ func control(s *State, ctl *ui.Controller) { defer func() { if r := recover(); r != nil { log.Error("control recovering from failure %v", r) } }() // establish control channel conn, err := conn.Dial(s.opts.server, "ctl", tlsConfig) if err != nil { panic(err) } defer conn.Close() // register with the server err = msg.WriteMsg(conn, &msg.RegMsg{ Protocol: s.opts.protocol, OS: runtime.GOOS, HttpAuth: s.opts.httpAuth, Hostname: s.opts.hostname, Subdomain: s.opts.subdomain, ClientId: s.id, Version: version.Proto, MmVersion: version.MajorMinor(), User: s.opts.authtoken, }) if err != nil { panic(err) } // wait for the server to ack our register var regAck msg.RegAckMsg if err = msg.ReadMsgInto(conn, ®Ack); err != nil { panic(err) } if regAck.Error != "" { emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", regAck.Error) ctl.Cmds <- ui.CmdQuit{Message: emsg} return } // update UI state s.publicUrl = regAck.Url conn.Info("Tunnel established at %v", s.GetPublicUrl()) s.status = "online" s.serverVersion = regAck.MmVersion ctl.Update(s) SaveAuthToken(s.opts.authtoken) // start the heartbeat lastPong := time.Now().UnixNano() go heartbeat(&lastPong, conn) // main control loop for { var m msg.Message if m, err = msg.ReadMsg(conn); err != nil { panic(err) } switch m.(type) { case *msg.ReqProxyMsg: go proxy(regAck.ProxyAddr, s, ctl) case *msg.PongMsg: atomic.StoreInt64(&lastPong, time.Now().UnixNano()) } } }