一、准备条件

一台公网服务器和一个已备案域名


这里以Centos7为例

# 安装go环境和git环境
yum install gcc mercurial git bzr subversion golang golang-pkg-windows-amd64 golang-pkg-windows-386 -y

二、下载源码

git clone https://github.com/inconshreveable/ngrok.git

三、生成证书

进入到ngrok根目录,xiaoqiangzai.xyz域名换成自己的

cd ngrok  

mkdir cert 

cd cert

export NGROK_DOMAIN="xiaoqiangzai.xyz"

openssl genrsa -out rootCA.key 2048

openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=$NGROK_DOMAIN" -days 5000 -out rootCA.pem

openssl genrsa -out device.key 2048

openssl req -new -key device.key -subj "/CN=$NGROK_DOMAIN" -out device.csr

openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

覆盖原来的证书,提示按 y 确定

cp rootCA.pem ../assets/client/tls/ngrokroot.crt

cp device.crt ../assets/server/tls/snakeoil.crt

cp device.key ../assets/server/tls/snakeoil.key

cd ..

四、生成服务端与客户端

<!--linux服务端/客户端-->
GOOS=linux GOARCH=386 make release-server (32位)
GOOS=linux GOARCH=amd64 make release-server(64位)

GOOS=linux GOARCH=386 make release-client (32位)
GOOS=linux GOARCH=amd64 make release-client(64位)

<!--Mac OS服务端/客户端-->
GOOS=darwin GOARCH=386 make release-server
GOOS=darwin GOARCH=amd64 make release-server

GOOS=darwin GOARCH=386 make release-client
GOOS=darwin GOARCH=amd64 make release-client


<!--windows服务端/客户端-->
GOOS=windows GOARCH=386 make release-server
GOOS=windows GOARCH=amd64 make release-server

GOOS=windows GOARCH=386 make release-client
GOOS=windows GOARCH=amd64 make release-client

这里选择生成Linux服务端和Window客户端

GOOS=linux GOARCH=amd64 make release-server
GOOS=windows GOARCH=amd64 make release-client

生成的文件都在bin目录下

image-20200726010807512

image-20200726010733371

服务端:ngrok

客户端:ngrok.exe

image-20200726010503105

五、启动服务

启动服务端

./ngrokd -domain="xiaoqiangzai.xyz"

参数说明

-httpAddr=":80" http服务的访问端口 默认80

-httpsAddr=":443" https服务的访问端口 默认443

-tunnelAddr=":4443" 客户端连接服务端的端口 默认4443

端口冲突时修改默认端口

# 前台启动
./ngrokd -domain="xiaoqiangzai.xyz" -httpAddr=":88" -httpsAddr=":888" -tunnelAddr=":8888"
# 后台启动
./ngrokd -domain="xiaoqiangzai.xyz" -httpAddr=":88" -httpsAddr=":888" -tunnelAddr=":8888" > ngrok.log 2>&1 &
# 使用证书启动,trust_host_root_certs: true
./ngrokd -domain="xiaoqiangzai.xyz" -tlsKey="/usr/local/ngrok/2_www.xiaoqiangzai.xyz.key" -tlsCrt="/usr/local/ngrok/1_www.xiaoqiangzai.xyz_bundle.crt"  -httpAddr=":80" -httpsAddr=":443" -tunnelAddr=":4443" > ngrok.log 2>&1 &

image-20200726012310580


准备客户端配置文件

将生成的客户端ngrok.exe复制到本地,创建配置文件ngrok.cfg

image-20200726012621787

ngrok.cfg

域名和端口要对应服务端

image-20200726020517982

server_addr: "xiaoqiangzai.xyz:4443"  
trust_host_root_certs: false

启动客户端

# -subdomain 指定域名前缀,如ngrok为ngrok.xiaoqiangzai.xyz,8888则为映射本地端口
ngrok -config ngrok.cfg -subdomain ngrok 8888

image-20200726014113529

启动多个

ngrok -config=ngrok.cfg -subdomain www 80
# 可以指定IP
ngrok -config=ngrok.cfg -subdomain centosb 192.168.17.101:8888

image-20200726014846131

访问前端页面

http://127.0.0.1:4040/http/in

image-20200726085938824

六、验证是否成功

外网访问 http://ngrok.xiaoqiangzai.xyz,https://www.xiaoqiangzai.xyz


手机访问

image-20200726015422181


电脑访问

image-20200726015059166

七、配置多个IP和端口

ngrok.cfg

server_addr: "xiaoqiangzai.xyz:8888"
trust_host_root_certs: false
tunnels: 
  tiktop:
    subdomain: tiktop
    proto:
      http: 80
  # 配置http
  xxljob:
    subdomain: xxljob
    proto:
      # 配置IP和端口
      http: 192.168.17.1:8888
  # 配置https
  xxljobs:
    subdomain: xxljob
    proto:
      https: 192.168.17.1:8888
  # 我的centos-50100
  centos-50100:
    remote_port: 50100
    proto:
      tcp: 192.168.17.101:50100

启动命令

# 启动全部
ngrok -config ngrok.cfg start-all
# 启动指定配置
ngrok -config ngrok.cfg start tiktop xxljob
# 启动配置日志
ngrok -config ngrok.cfg -log log/ngrok.log start-all

image-20200726044913999

配置服务器转发

tunnels: 
  centos:
    remote_port: 50001
    proto:
      tcp: 192.168.17.101:22

image-20200726075800596

Linux客户端

ngrok.cfg

server_addr: "xiaoqiangzai.xyz:4443"
trust_host_root_certs: true
auth_token: qiang:xxxxxxx
tunnels:
  # 我的博客
  solo:
    subdomain: "www"
    proto:
      https: 123.57.188.214:8080
# 后台启动
nohup ./ngrok -config=ngrok.cfg -log=stdout start-all &
# 停止服务端
ps -ef | grep ngrokd | grep -v grep | awk '{print $2}' | xargs kill -9
# 停止客户端
ps -ef | grep ngrok.cfg | grep -v grep | awk '{print $2}' | xargs kill -9

image-20200812035447336

八、添加认证

当前只要知道地址,拥有客户端都可以使用,所以我们要添加一个简单的认证。

修改源码ngrok/src/ngrok/server/control.go

源码control.go

package server

import (
        "fmt"
        "io"
        "ngrok/conn"
        "ngrok/msg"
        "ngrok/util"
        "ngrok/version"
        "runtime/debug"
        "strings"
        "time"
)

const (
        pingTimeoutInterval = 30 * time.Second
        connReapInterval    = 10 * time.Second
        controlWriteTimeout = 10 * time.Second
        proxyStaleDuration  = 60 * time.Second
        proxyMaxPoolSize    = 10
)

type Control struct {
        // auth message
        auth *msg.Auth

        // actual connection
        conn conn.Conn

        // put a message in this channel to send it over
        // conn to the client
        out chan (msg.Message)

        // read from this channel to get the next message sent
        // to us over conn by the client
        in chan (msg.Message)

        // the last time we received a ping from the client - for heartbeats
        lastPing time.Time

        // all of the tunnels this control connection handles
        tunnels []*Tunnel

        // proxy connections
        proxies chan conn.Conn

        // identifier
        id string

        // synchronizer for controlled shutdown of writer()
        writerShutdown *util.Shutdown

        // synchronizer for controlled shutdown of reader()
        readerShutdown *util.Shutdown

        // synchronizer for controlled shutdown of manager()
        managerShutdown *util.Shutdown

        // synchronizer for controller shutdown of entire Control
        shutdown *util.Shutdown
}

func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
        var err error

        // create the object
        c := &Control{
                auth:            authMsg,
                conn:            ctlConn,
                out:             make(chan msg.Message),
                in:              make(chan msg.Message),
                proxies:         make(chan conn.Conn, 10),
                lastPing:        time.Now(),
                writerShutdown:  util.NewShutdown(),
                readerShutdown:  util.NewShutdown(),
                managerShutdown: util.NewShutdown(),
                shutdown:        util.NewShutdown(),
        }

        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
                }
        }

        // set logging prefix
        ctlConn.SetType("ctl")
        ctlConn.AddLogPrefix(c.id)

        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
        }

        // register the control
        if replaced := controlRegistry.Add(c.id, c); replaced != nil {
                replaced.shutdown.WaitComplete()
        }

        // start the writer first so that the following messages get sent
        go c.writer()

        // Respond to authentication
        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{}

        // manage the connection
        go c.manager()
        go c.reader()
        go c.stopper()
}

// Register a new tunnel on this control connection
func (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) {
        for _, proto := range strings.Split(rawTunnelReq.Protocol, "+") {
                tunnelReq := *rawTunnelReq
                tunnelReq.Protocol = proto

                c.conn.Debug("Registering new tunnel")
                t, err := NewTunnel(&tunnelReq, c)
                if err != nil {
                        c.out <- &msg.NewTunnel{Error: err.Error()}
                        if len(c.tunnels) == 0 {
                                c.shutdown.Begin()
                        }

                        // we're done
                        return
                }

                // add it to the list of tunnels
                c.tunnels = append(c.tunnels, t)

                // acknowledge success
                c.out <- &msg.NewTunnel{
                        Url:      t.url,
                        Protocol: proto,
                        ReqId:    rawTunnelReq.ReqId,
                }

                rawTunnelReq.Hostname = strings.Replace(t.url, proto+"://", "", 1)
        }
}

func (c *Control) manager() {
        // don't crash on panics
        defer func() {
                if err := recover(); err != nil {
                        c.conn.Info("Control::manager failed with error %v: %s", err, debug.Stack())
                }
        }()

        // kill everything if the control manager stops
        defer c.shutdown.Begin()

        // notify that manager() has shutdown
        defer c.managerShutdown.Complete()

        // reaping timer for detecting heartbeat failure
        reap := time.NewTicker(connReapInterval)
        defer reap.Stop()

        for {
                select {
                case <-reap.C:
                        if time.Since(c.lastPing) > pingTimeoutInterval {
                                c.conn.Info("Lost heartbeat")
                                c.shutdown.Begin()
                        }

                case mRaw, ok := <-c.in:
                        // c.in closes to indicate shutdown
                        if !ok {
                                return
                        }

                        switch m := mRaw.(type) {
                        case *msg.ReqTunnel:
                                c.registerTunnel(m)

                        case *msg.Ping:
                                c.lastPing = time.Now()
                                c.out <- &msg.Pong{}
                        }
                }
        }
}

func (c *Control) writer() {
        defer func() {
                if err := recover(); err != nil {
                        c.conn.Info("Control::writer failed with error %v: %s", err, debug.Stack())
                }
        }()

        // kill everything if the writer() stops
        defer c.shutdown.Begin()

        // notify that we've flushed all messages
        defer c.writerShutdown.Complete()

        // write messages to the control channel
        for m := range c.out {
                c.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout))
                if err := msg.WriteMsg(c.conn, m); err != nil {
                        panic(err)
                }
        }
}

func (c *Control) reader() {
        defer func() {
                if err := recover(); err != nil {
                        c.conn.Warn("Control::reader failed with error %v: %s", err, debug.Stack())
                }
        }()

        // kill everything if the reader stops
        defer c.shutdown.Begin()

        // notify that we're done
        defer c.readerShutdown.Complete()

        // read messages from the control channel
        for {
                if msg, err := msg.ReadMsg(c.conn); err != nil {
                        if err == io.EOF {
                                c.conn.Info("EOF")
                                return
                        } else {
                                panic(err)
                        }
                } else {
                        // this can also panic during shutdown
                        c.in <- msg
                }
        }
}

func (c *Control) stopper() {
        defer func() {
                if r := recover(); r != nil {
                        c.conn.Error("Failed to shut down control: %v", r)
                }
        }()

        // wait until we're instructed to shutdown
        c.shutdown.WaitBegin()

        // remove ourself from the control registry
        controlRegistry.Del(c.id)

        // shutdown manager() so that we have no more work to do
        close(c.in)
        c.managerShutdown.WaitComplete()

        // shutdown writer()
        close(c.out)
        c.writerShutdown.WaitComplete()

        // close connection fully
        c.conn.Close()

        // shutdown all of the tunnels
        for _, t := range c.tunnels {
                t.Shutdown()
        }

        // shutdown all of the proxy connections
        close(c.proxies)
        for p := range c.proxies {
                p.Close()
        }

        c.shutdown.Complete()
        c.conn.Info("Shutdown complete")
}

func (c *Control) RegisterProxy(conn conn.Conn) {
        conn.AddLogPrefix(c.id)

        conn.SetDeadline(time.Now().Add(proxyStaleDuration))
        select {
        case c.proxies <- conn:
                conn.Info("Registered")
        default:
                conn.Info("Proxies buffer is full, discarding.")
                conn.Close()
        }
}

// Remove a proxy connection from the pool and return it
// If not proxy connections are in the pool, request one
// and wait until it is available
// Returns an error if we couldn't get a proxy because it took too long
// or the tunnel is closing
func (c *Control) GetProxy() (proxyConn conn.Conn, err error) {
        var ok bool

        // get a proxy connection from the pool
        select {
        case proxyConn, ok = <-c.proxies:
                if !ok {
                        err = fmt.Errorf("No proxy connections available, control is closing")
                        return
                }
        default:
                // no proxy available in the pool, ask for one over the control channel
                c.conn.Debug("No proxy in pool, requesting proxy from control . . .")
                if err = util.PanicToError(func() { c.out <- &msg.ReqProxy{} }); err != nil {
                        return
                }

                select {
                case proxyConn, ok = <-c.proxies:
                        if !ok {
                                err = fmt.Errorf("No proxy connections available, control is closing")
                                return
                        }

                case <-time.After(pingTimeoutInterval):
                        err = fmt.Errorf("Timeout trying to get proxy connection")
                        return
                }
        }
        return
}

// Called when this control is replaced by another control
// this can happen if the network drops out and the client reconnects
// before the old tunnel has lost its heartbeat
func (c *Control) Replaced(replacement *Control) {
        c.conn.Info("Replaced by control: %s", replacement.conn.Id())

        // set the control id to empty string so that when stopper()
        // calls registry.Del it won't delete the replacement
        c.id = ""

        // tell the old one to shutdown
        c.shutdown.Begin()
}

在 func NewControl(){} 内添加了一个方法

import (
        //省略
        //多两个包
        "os"
        "bufio"
)
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
     //省略
     //读取文件方法
     readLine := func(token string, filename string) (bool, error) {

        if token == "" {
            return false, nil;
        }
        f, err := os.Open(filename)
        if err != nil {
            return false, err
        }
        buf := bufio.NewReader(f)
        for {
            line, err := buf.ReadString('\n')
            line = strings.TrimSpace(line)
            if line == token {
                return true, nil
            }
            if err != nil {
                if err == io.EOF {
                    return false, nil
                }
                return false, err
            }
        }
        return false, nil
}
    //省略
    //调用验证
    authd, err := readLine(authMsg.User, "authtokens.txt")

    if authd != true {
        failAuth(fmt.Errorf("authtoken %s invalid", "is"));
        return
}
     //省略
 }

修改后的control.go

package server

import (
        "fmt"
        "io"
        "ngrok/conn"
        "ngrok/msg"
        "ngrok/util"
        "ngrok/version"
        "runtime/debug"
        "strings"
        "time"
        //多两个包
        "os"
        "bufio"
)

const (
        pingTimeoutInterval = 30 * time.Second
        connReapInterval    = 10 * time.Second
        controlWriteTimeout = 10 * time.Second
        proxyStaleDuration  = 60 * time.Second
        proxyMaxPoolSize    = 10
)

type Control struct {
        // auth message
        auth *msg.Auth

        // actual connection
        conn conn.Conn

        // put a message in this channel to send it over
        // conn to the client
        out chan (msg.Message)

        // read from this channel to get the next message sent
        // to us over conn by the client
        in chan (msg.Message)

        // the last time we received a ping from the client - for heartbeats
        lastPing time.Time

        // all of the tunnels this control connection handles
        tunnels []*Tunnel

        // proxy connections
        proxies chan conn.Conn

        // identifier
        id string

        // synchronizer for controlled shutdown of writer()
        writerShutdown *util.Shutdown

        // synchronizer for controlled shutdown of reader()
        readerShutdown *util.Shutdown

        // synchronizer for controlled shutdown of manager()
        managerShutdown *util.Shutdown

        // synchronizer for controller shutdown of entire Control
        shutdown *util.Shutdown
}

func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
        var err error

        // create the object
        c := &Control{
                auth:            authMsg,
                conn:            ctlConn,
                out:             make(chan msg.Message),
                in:              make(chan msg.Message),
                proxies:         make(chan conn.Conn, 10),
                lastPing:        time.Now(),
                writerShutdown:  util.NewShutdown(),
                readerShutdown:  util.NewShutdown(),
                managerShutdown: util.NewShutdown(),
                shutdown:        util.NewShutdown(),
        }

        failAuth := func(e error) {
                _ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()})
                ctlConn.Close()
        }
    
    //读取文件方法
        readLine := func(token string, filename string) (bool, error) {

        if token == "" {
            return false, nil;
        }
        f, err := os.Open(filename)
        if err != nil {
            return false, err
        }
        buf := bufio.NewReader(f)
        for {
            line, err := buf.ReadString('\n')
            line = strings.TrimSpace(line)
            if line == token {
                return true, nil
            }
            if err != nil {
                if err == io.EOF {
                    return false, nil
                }
                return false, err
            }
        }
        return false, nil
      }

        // 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
                }
        }

        // set logging prefix
        ctlConn.SetType("ctl")
        ctlConn.AddLogPrefix(c.id)

        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
        }
    
       //调用验证
    authd, err := readLine(authMsg.User, "authtokens.txt")

    if authd != true {
        failAuth(fmt.Errorf("authtoken %s invalid", "is"));
        return
    }

        // register the control
        if replaced := controlRegistry.Add(c.id, c); replaced != nil {
                replaced.shutdown.WaitComplete()
        }

        // start the writer first so that the following messages get sent
        go c.writer()

        // Respond to authentication
        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{}

        // manage the connection
        go c.manager()
        go c.reader()
        go c.stopper()
}

// Register a new tunnel on this control connection
func (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) {
        for _, proto := range strings.Split(rawTunnelReq.Protocol, "+") {
                tunnelReq := *rawTunnelReq
                tunnelReq.Protocol = proto

                c.conn.Debug("Registering new tunnel")
                t, err := NewTunnel(&tunnelReq, c)
                if err != nil {
                        c.out <- &msg.NewTunnel{Error: err.Error()}
                        if len(c.tunnels) == 0 {
                                c.shutdown.Begin()
                        }

                        // we're done
                        return
                }

                // add it to the list of tunnels
                c.tunnels = append(c.tunnels, t)

                // acknowledge success
                c.out <- &msg.NewTunnel{
                        Url:      t.url,
                        Protocol: proto,
                        ReqId:    rawTunnelReq.ReqId,
                }

                rawTunnelReq.Hostname = strings.Replace(t.url, proto+"://", "", 1)
        }
}

func (c *Control) manager() {
        // don't crash on panics
        defer func() {
                if err := recover(); err != nil {
                        c.conn.Info("Control::manager failed with error %v: %s", err, debug.Stack())
                }
        }()

        // kill everything if the control manager stops
        defer c.shutdown.Begin()

        // notify that manager() has shutdown
        defer c.managerShutdown.Complete()

        // reaping timer for detecting heartbeat failure
        reap := time.NewTicker(connReapInterval)
        defer reap.Stop()

        for {
                select {
                case <-reap.C:
                        if time.Since(c.lastPing) > pingTimeoutInterval {
                                c.conn.Info("Lost heartbeat")
                                c.shutdown.Begin()
                        }

                case mRaw, ok := <-c.in:
                        // c.in closes to indicate shutdown
                        if !ok {
                                return
                        }

                        switch m := mRaw.(type) {
                        case *msg.ReqTunnel:
                                c.registerTunnel(m)

                        case *msg.Ping:
                                c.lastPing = time.Now()
                                c.out <- &msg.Pong{}
                        }
                }
        }
}

func (c *Control) writer() {
        defer func() {
                if err := recover(); err != nil {
                        c.conn.Info("Control::writer failed with error %v: %s", err, debug.Stack())
                }
        }()

        // kill everything if the writer() stops
        defer c.shutdown.Begin()

        // notify that we've flushed all messages
        defer c.writerShutdown.Complete()

        // write messages to the control channel
        for m := range c.out {
                c.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout))
                if err := msg.WriteMsg(c.conn, m); err != nil {
                        panic(err)
                }
        }
}

func (c *Control) reader() {
        defer func() {
                if err := recover(); err != nil {
                        c.conn.Warn("Control::reader failed with error %v: %s", err, debug.Stack())
                }
        }()

        // kill everything if the reader stops
        defer c.shutdown.Begin()

        // notify that we're done
        defer c.readerShutdown.Complete()

        // read messages from the control channel
        for {
                if msg, err := msg.ReadMsg(c.conn); err != nil {
                        if err == io.EOF {
                                c.conn.Info("EOF")
                                return
                        } else {
                                panic(err)
                        }
                } else {
                        // this can also panic during shutdown
                        c.in <- msg
                }
        }
}

func (c *Control) stopper() {
        defer func() {
                if r := recover(); r != nil {
                        c.conn.Error("Failed to shut down control: %v", r)
                }
        }()

        // wait until we're instructed to shutdown
        c.shutdown.WaitBegin()

        // remove ourself from the control registry
        controlRegistry.Del(c.id)

        // shutdown manager() so that we have no more work to do
        close(c.in)
        c.managerShutdown.WaitComplete()

        // shutdown writer()
        close(c.out)
        c.writerShutdown.WaitComplete()

        // close connection fully
        c.conn.Close()

        // shutdown all of the tunnels
        for _, t := range c.tunnels {
                t.Shutdown()
        }

        // shutdown all of the proxy connections
        close(c.proxies)
        for p := range c.proxies {
                p.Close()
        }

        c.shutdown.Complete()
        c.conn.Info("Shutdown complete")
}

func (c *Control) RegisterProxy(conn conn.Conn) {
        conn.AddLogPrefix(c.id)

        conn.SetDeadline(time.Now().Add(proxyStaleDuration))
        select {
        case c.proxies <- conn:
                conn.Info("Registered")
        default:
                conn.Info("Proxies buffer is full, discarding.")
                conn.Close()
        }
}

// Remove a proxy connection from the pool and return it
// If not proxy connections are in the pool, request one
// and wait until it is available
// Returns an error if we couldn't get a proxy because it took too long
// or the tunnel is closing
func (c *Control) GetProxy() (proxyConn conn.Conn, err error) {
        var ok bool

        // get a proxy connection from the pool
        select {
        case proxyConn, ok = <-c.proxies:
                if !ok {
                        err = fmt.Errorf("No proxy connections available, control is closing")
                        return
                }
        default:
                // no proxy available in the pool, ask for one over the control channel
                c.conn.Debug("No proxy in pool, requesting proxy from control . . .")
                if err = util.PanicToError(func() { c.out <- &msg.ReqProxy{} }); err != nil {
                        return
                }

                select {
                case proxyConn, ok = <-c.proxies:
                        if !ok {
                                err = fmt.Errorf("No proxy connections available, control is closing")
                                return
                        }

                case <-time.After(pingTimeoutInterval):
                        err = fmt.Errorf("Timeout trying to get proxy connection")
                        return
                }
        }
        return
}

// Called when this control is replaced by another control
// this can happen if the network drops out and the client reconnects
// before the old tunnel has lost its heartbeat
func (c *Control) Replaced(replacement *Control) {
        c.conn.Info("Replaced by control: %s", replacement.conn.Id())

        // set the control id to empty string so that when stopper()
        // calls registry.Del it won't delete the replacement
        c.id = ""

        // tell the old one to shutdown
        c.shutdown.Begin()
}

重新编译服务端

GOOS=linux GOARCH=amd64 make release-server

在同级目录下创建authtokens.txt

touch authtokens.txt
# 将账号密码添加到文件
echo "username:password" > authtokens.txt

image-20200726065110292

重新启动服务端即可,客户端需要配置账号密码否则无法连接

server_addr: "xiaoqiangzai.xyz:8888"
trust_host_root_certs: false
auth_token: username:password
tunnels: 
  nacos:
    subdomain: nacos
    proto:
      http: 192.168.17.101:8848
  xxljob:
    subdomain: xxljob
    proto:
      http: 192.168.17.1:8888
  fileupload:
    subdomain: fileupload
    proto:
      http: 127.0.0.1:8080

九、常见问题

防火墙问题:关闭防火墙或者暴露相应的端口

域名解析问题:添加三种解析

image-20200726021044227

端口占用问题:修改默认端口,但是修改