1. 程式人生 > >內網穿透-ngrok原理淺析

內網穿透-ngrok原理淺析

之前在進行微信Demo開發時曾用到過ngrok這個強大的tunnel(隧道)工具,ngrok在其github官方頁面上的自我詮釋是 “introspected tunnels to localhost",這個詮釋有兩層含義:
1、可以用來建立public到localhost的tunnel,讓居於內網主機上的服務可以暴露給public,俗稱內網穿透。
2、支援對隧道中資料的introspection(內省),支援視覺化的觀察隧道內資料,並replay(重放)相關請求(諸如http請 求)。

因此ngrok可以很便捷的協助進行服務端程式除錯,尤其在進行一些Web server開發中。ngrok更強大的一點是它支援tcp層之上的所有應用協議或者說與應用層協議無關。比如:你可以通過ngrok實現ssh登入到內 網主 機,也可以通過ngrok實現遠端桌面(VNC)方式訪問內網主機。

今天我們就來簡單分析一下這款強大工具的實現原理。ngrok本身是用go語言實現的,需要go 1.1以上版本編譯。ngrok官方程式碼最新版為1.7,作者似乎已經完成了ngrok 2.0版本,但不知為何遲遲不放出最新程式碼。因此這裡我們就以ngrok 1.7版本原始碼作為原理分析的基礎。

一、ngrok tunnel與ngrok部署

網路tunnel(隧道)對多數人都是很”神祕“的概念,tunnel種類很多,沒有標準定義,我瞭解的也不多(日常工作較少涉及),這裡也就不 深入了。在《HTTP權威指南》中有關於HTTP tunnel(http上承載非web流量)和SSL tunnel的說明,但ngrok中的tunnel又與這些有所不同。

ngrok實現了一個tcp之上的端到端的tunnel,兩端的程式在ngrok實現的Tunnel內透明的進行資料互動。

ngrok分為client端(ngrok)和服務端(ngrokd),實際使用中的部署如下:

內網服務程式可以與ngrok client部署在同一主機,也可以部署在內網可達的其他主機上。ngrok和ngrokd會為建立與public client間的專用通道(tunnel)。

二、ngrok開發除錯環境搭建

在學習ngrok程式碼或試驗ngrok功能的時候,我們可能需要搭建一個ngrok的開發除錯環境。ngrok作者在ngrok developer guide

中給出了步驟:

make client和make server執行後,會建構出ngrok和ngrokd的debug版本。如果要得到release版本,請使用make release-client和<span face="Courier


New">make release-server。debug版本與release版本的區別在於debug版本不打包 assets下的資原始檔,執行時通過檔案系統訪問。

修改/etc/hosts檔案,新增兩行:

127.0.0.1 ngrok.me
127.0.0.1 test.ngrok.me

建立客戶端配置檔案debug.yml:

server_addr: ngrok.me:4443
trust_host_root_certs: false
tunnels:
      test:
        proto:
           http: 8080

不過要想讓ngrok與ngrokd順利建立通訊,我們還得製作數字證書(自簽發),原始碼中自帶的證書是無法使用的,證書製作方法可參見《搭建自 己的ngrok服務》一文,相關原理可參考《Go和HTTPS》一文,這裡就不贅述了。

我直接使用的是release版本(放在bin/release下),這樣在執行命令時可以少傳入幾個引數:

啟動服務端:
$> sudo ./bin/release/ngrokd -domain ngrok.me
[05/13/15 17:15:37] [INFO] Listening for public http connections on [::]:80
[05/13/15 17:15:37] [INFO] Listening for public https connections on [::]:443
[05/13/15 17:15:37] [INFO] Listening for control and proxy connections on [::]:4443

啟動客戶端:
$> ./bin/release/ngrok -config=debug.yml -log=ngrok.log -subdomain=test 8080

有了除錯環境,我們就可以通過debug日誌驗證我們的分析了。

ngrok的原始碼結構如下:

drwxr-xr-x   3 tony  staff  102  3 31 16:09 cache/
drwxr-xr-x  16 tony  staff  544  5 13 17:21 client/
drwxr-xr-x   4 tony  staff  136  5 13 15:02 conn/
drwxr-xr-x   3 tony  staff  102  3 31 16:09 log/
drwxr-xr-x   4 tony  staff  136  3 31 16:09 main/
drwxr-xr-x   5 tony  staff  170  5 12 16:17 msg/
drwxr-xr-x   5 tony  staff  170  3 31 16:09 proto/
drwxr-xr-x  11 tony  staff  374  5 13 17:21 server/
drwxr-xr-x   7 tony  staff  238  3 31 16:09 util/
drwxr-xr-x   3 tony  staff  102  3 31 16:09 version/

main目錄下的ngrok/和ngrokd/分別是ngrok和ngrokd main包,main函式存放的位置,但這裡僅僅是一個stub。以ngrok為例:

// ngrok/src/ngrok/main/ngrok/ngrok.go
package main

import (
    "ngrok/client"
)

func main() {
    client.Main()
}

真正的“main”被client包的Main函式實現。

client/和server/目錄分別對應ngrok和ngrokd的主要邏輯,其他目錄(或包)都是一些工具類的實現。

三、第一階段:Control Connection建立

在ngrokd的啟動日誌中我們可以看到這樣一行:

[INFO] Listening for control and proxy connections on [::]:4443

ngrokd在4443埠(預設)監聽control和proxy connection。Control Connection,顧名思義“控制連線”,有些類似於FTP協議的控制連線(不知道ngrok作者在設計協議時是否參考了FTP協議^_^)。該連線 只用於收發控制類訊息。作為客戶端的ngrok啟動後的第一件事就是與ngrokd建立Control Connection,建立過程式列圖如下:

前面提到過,ngrok客戶端的實際entrypoint在ngrok/src/ngrok/client目錄下,包名client,實際入口是 client.Main函式。

//ngrok/src/ngrok/client/main.go
func Main() {
    // parse options
    // set up logging
    // read configuration file
    …. …
    NewController().Run(config)
}

ngrok採用了MVC模式構架程式碼,這既包括ngrok與ngrokd之間的邏輯處理,也包括ngrok本地web頁面(用於隧道資料的 introspection)的處理。

//ngrok/src/ngrok/client/controller.go
func (ctl *Controller) Run(config *Configuration) {

    var model *ClientModel

    if ctl.model == nil {
        model = ctl.SetupModel(config)
    } else {
        model = ctl.model.(*ClientModel)
    }
    // init the model
    // init web ui
    // init term ui
   … …
   ctl.Go(ctl.model.Run)
   … …
  
}

我們來繼續看看model.Run都做了些什麼。

//ngrok/src/ngrok/client/model.go
func (c *ClientModel) Run() {
    … …

    for {
        // run the control channel
        c.control()
        … …
        if c.connStatus == mvc.ConnOnline {
            wait = 1 * time.Second
        }

        … …
        c.connStatus = mvc.ConnReconnecting
        c.update()
    }
}

Run函式呼叫c.control來執行Control Connection的主邏輯,並在control connection斷開後,嘗試重連。

c.control是ClientModel的一個method,用來真正建立ngrok到ngrokd的control connection,並完成基於ngrok的鑑權(使用者名稱、密碼配置在配置檔案中)。

//ngrok/src/ngrok/client/model.go
func (c *ClientModel) control() {
    … …
    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 {……}
    … …

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

    … …

    c.id = authResp.ClientId
    … ..
}

ngrok封裝了connection相關操作,程式碼在ngrok/src/ngrok/conn下面,包名conn。

//ngrok/src/ngrok/conn/conn.go
func Dial(addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) {
    var rawConn net.Conn
    if rawConn, err = net.Dial("tcp", addr); err != nil {
        return
    }

    conn = wrapConn(rawConn, typ)
    conn.Debug("New connection to: %v", rawConn.RemoteAddr())

    if tlsCfg != nil {
        conn.StartTLS(tlsCfg)
    }

    return
}

ngrok首先建立一條TCP連線,並基於該連線建立了TLS client:

func (c *loggedConn) StartTLS(tlsCfg *tls.Config) {
    c.Conn = tls.Client(c.Conn, tlsCfg)
}

不過此時並未進行TLS的初始化,即handshake。handshake發生在ngrok首次向ngrokd傳送auth訊息(msg.WriteMsg, ngrok/src/ngrok/msg/msg.go)時,go標準庫的TLS相關函式默默的完成這一handshake過程。我們經常遇到的ngrok證書驗證失敗等問題,就發生在該過程中。

在AuthResp中,ngrokd為該Control Connection分配一個ClientID,該ClientID在後續Proxy Connection建立時使用,用於關聯和校驗之用。

前面的邏輯和程式碼都是ngrok客戶端的,現在我們再從ngrokd server端程式碼review一遍Control Connection的建立過程。

ngrokd的程式碼放在ngrok/src/ngrok/server下面,entrypoint如下:

//ngrok/src/ngrok/server/main.go
func Main() {
    // parse options
    opts = parseArgs()
    // init logging
    // init tunnel/control registry
    … …
    // start listeners
    listeners = make(map[string]*conn.Listener)

    // load tls configuration
    tlsConfig, err := LoadTLSConfig(opts.tlsCrt, opts.tlsKey)
    if err != nil {
        panic(err)
    }
    // listen for http
    // listen for https
    … …

    // ngrok clients
    tunnelListener(opts.tunnelAddr, tlsConfig)
}

ngrokd啟動了三個監聽,其中最後一個tunnelListenner用於監聽ngrok發起的Control Connection或者後續的proxy connection,作者意圖通過一個埠,監聽兩種型別連線,旨在於方便部署。

//ngrok/src/ngrok/server/main.go
func tunnelListener(addr string, tlsConfig *tls.Config) {
    // listen for incoming connections
    listener, err := conn.Listen(addr, "tun", tlsConfig)
    … …

    for c := range listener.Conns {
        go func(tunnelConn conn.Conn) {
            … …
            var rawMsg msg.Message
            if rawMsg, err = msg.ReadMsg(tunnelConn); err != nil {
                tunnelConn.Warn("Failed to read message: %v", err)
                tunnelConn.Close()
                return
            }
            … …
            switch m := rawMsg.(type) {
            case *msg.Auth:
                NewControl(tunnelConn, m)
            … …
            }
        }(c)
    }
}

從tunnelListener可以看到,當ngrokd在新建立的Control Connection上收到Auth訊息後,ngrokd執行NewControl來處理該Control Connection上的後續事情。

//ngrok/src/ngrok/server/control.go
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
    var err error

    // create the object
    c := &Control{
        … …
    }

    // register the clientid
    … …
    // register the control
    … …

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

在NewControl中,ngrokd返回了AuthResp。到這裡,一條新的Control Connection建立完畢。

我們最後再來看一下Control Connection建立過程時ngrok和ngrokd的輸出日誌,增強一下感性認知:

ngrok Server:

[INFO] [tun:d866234] New connection from 127.0.0.1:59949
[DEBG] [tun:d866234] Waiting to read message
[DEBG] [tun:d866234] Reading message with length: 126
[DEBG] [tun:d866234] Read message {"Type":"Auth",
"Payload":{"Version":"2","MmVersion":"1.7","User":"","Password":"","OS":"darwin","Arch":"amd64","ClientId":""}}
[INFO] [ctl:d866234] Renamed connection tun:d866234
[INFO] [registry] [ctl] Registered control with id ac1d14e0634f243f8a0cc2306bb466af
[DEBG] [ctl:d866234] [ac1d14e0634f243f8a0cc2306bb466af] Writing message: {"Type":"AuthResp","Payload":{"Version":"2","MmVersion":"1.7","ClientId":"ac1d14e0634f243f8a0cc2306bb466af","Error":""}}

Client:

[INFO] (ngrok/log.Info:112) Reading configuration file debug.yml
[INFO] (ngrok/log.(*PrefixLogger).Info:83) [client] Trusting root CAs: [assets/client/tls/ngrokroot.crt]
[INFO] (ngrok/log.(*PrefixLogger).Info:83) [view] [web] Serving web interface on 127.0.0.1:4040
[INFO] (ngrok/log.Info:112) Checking for update
[DEBG] (ngrok/log.(*PrefixLogger).Debug:79) [view] [term] Waiting for update
[DEBG] (ngrok/log.(*PrefixLogger).Debug:79) [ctl:31deb681] New connection to: 127.0.0.1:4443
[DEBG] (ngrok/log.(*PrefixLogger).Debug:79) [ctl:31deb681] Writing message: {"Type":"Auth","Payload":{"Version":"2","MmVersion":"1.7","User":"","Password":"","OS":"darwin","Arch":"amd64","ClientId":""}}
[DEBG] (ngrok/log.(*PrefixLogger).Debug:79) [ctl:31deb681] Waiting to read message
(ngrok/log.(*PrefixLogger).Debug:79) [ctl:31deb681] Reading message with length: 120
(ngrok/log.(*PrefixLogger).Debug:79) [ctl:31deb681] Read message {"Type":"AuthResp","Payload":{"Version":"2","MmVersion":"1.7","ClientId":"ac1d14e0634f243f8a0cc2306bb466af","Error":""}}
[INFO] (ngrok/log.(*PrefixLogger).Info:83) [client] Authenticated with server, client id: ac1d14e0634f243f8a0cc2306bb466af

四、Tunnel Creation

Tunnel Creation是ngrok將配置檔案中的tunnel資訊通過剛剛建立的Control Connection傳輸給 ngrokd,ngrokd登記、啟動相應埠監聽(如果配置了remote_port或多路複用ngrokd預設監聽的http和https埠)並返回相應應答。ngrok和ngrokd之間並未真正建立新連線。

我們回到ngrok的model.go,繼續看ClientModel的control方法。在收到AuthResp後,ngrok還做了如下事情:

//ngrok/src/ngrok/client/model.go
 
   // 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{
            … …
        }

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

    // main control loop
    for {
        var rawMsg msg.Message
       
        switch m := rawMsg.(type) {
        … …
        case *msg.NewTunnel:
            … …

            tunnel := mvc.Tunnel{
                … …
            }

            c.tunnels[tunnel.PublicUrl] = tunnel
            c.connStatus = mvc.ConnOnline
           
            c.update()
        … …
        }
    }

ngrok將配置的Tunnel資訊逐一以ReqTunnel訊息傳送給ngrokd以註冊登記Tunnel,並在隨後的main control loop中處理ngrokd回送的NewTunnel訊息,完成一些登記索引工作。

ngrokd Server端對tunnel creation的處理是在NewControl的結尾處:

//ngrok/src/ngrok/server/control.go
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
    … …
    // manage the connection
    go c.manager()
    … …
}

func (c *Control) manager() {
    //… …

    for {
        select {
        case <-reap.C:
            … …

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

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

            .. …
            }
        }
    }
}

Control的manager在收到ngrok發來的ReqTunnel訊息後,呼叫registerTunnel進行處理。

// ngrok/src/ngrok/server/control.go
// 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)
    }
}

Server端建立tunnel的實際工作由NewTunnel完成:

// ngrok/src/ngrok/server/tunnel.go
func NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) {
    t = &Tunnel{
      … …
    }

    proto := t.req.Protocol
    switch proto {
    case "tcp":
        bindTcp := func(port int) error {
            if t.listener, err = net.ListenTCP("tcp",
               &net.TCPAddr{IP: net.ParseIP("0.0.0.0"),
               Port: port}); err != nil {
                … …
                return err
            }

            // create the url
            addr := t.listener.Addr().(*net.TCPAddr)
            t.url = fmt.Sprintf("tcp://%s:%d", opts.domain, addr.Port)

            // register it
            if err = tunnelRegistry.RegisterAndCache(t.url, t);
               err != nil {
                … …
                return err
            }

            go t.listenTcp(t.listener)
            return nil
        }

        // use the custom remote port you asked for
        if t.req.RemotePort != 0 {
            bindTcp(int(t.req.RemotePort))
            return
        }
        // try to return to you the same port you had before
        cachedUrl := tunnelRegistry.GetCachedRegistration(t)
        if cachedUrl != "" {
            … …
        }

        // Bind for TCP connections
        bindTcp(0)
        return

    case "http", "https":
        l, ok := listeners[proto]
        if !ok {
            … …
            return
        }

        if err = registerVhost(t, proto, l.Addr.(*net.TCPAddr).Port);
           err != nil {
            return
        }

    default:
        err = fmt.Errorf("Protocol %s is not supported", proto)
        return
    }

    … …

    metrics.OpenTunnel(t)
    return
}

可以看出,NewTunnel區別對待tcp和http/https隧道:

- 對於Tcp隧道,NewTunnel先要看是否配置了remote_port,如果remote_port不為空,則啟動監聽這個 remote_port。否則嘗試從cache裡找出你之前建立tunnel時使用的埠號,如果可用,則監聽這個埠號,否則bindTcp(0),即 隨機選擇一個埠作為該tcp tunnel的remote_port。

- 對於http/https隧道,ngrokd啟動時就預設監聽了80和443,如果ngrok請求建立http/https隧道(目前不支援設定remote_port),則ngrokd通過一種自實現的vhost的機制實現所有http/https請求多路複用到80和443埠上。ngrokd不會新增監聽埠。

從下面例子,我們也可以看出一些端倪。我們將debug.yml改為:

server_addr: ngrok.me:4443
trust_host_root_certs: false
tunnels:
      test:
        proto:
           http: 8080
      test1:
        proto:
           http: 8081
      ssh1:
        remote_port: 50000
        proto:
            tcp: 22
      ssh2:
        proto:
            tcp: 22

啟動ngrok:

$./bin/release/ngrok -config=debug.yml -log=ngrok.log start test test1  ssh1 ssh2

Tunnel Status                 online
Version                       1.7/1.7
Forwarding                    tcp://ngrok.me:50000 -> 127.0.0.1:22
Forwarding                    tcp://ngrok.me:56297 -> 127.0.0.1:22
Forwarding                    http://test.ngrok.me -> 127.0.0.1:8080
Forwarding                    http://test1.ngrok.me -> 127.0.0.1:8081
Web Interface                 127.0.0.1:4040

可以看出ngrokd為ssh2隨機挑選了一個埠56297進行了監聽,而兩個http隧道,則都預設使用了80埠。

如果像下面這樣配置會發生什麼呢?

      ssh1:
        remote_port: 50000
        proto:
            tcp: 22
      ssh2:
        remote_port: 50000
        proto:
            tcp: 22

ngrok啟動會得到錯誤資訊:
Server failed to allocate tunnel: [ctl:5332a293] [a87bd111bcc804508c835714c18a5664] Error binding TCP listener: listen tcp 0.0.0.0:50000: bind: address already in use

客戶端ngrok在ClientModel control方法的main control loop中收到NewTunnel並處理該訊息:

    case *msg.NewTunnel:
            if m.Error != "" {
                … …
            }

            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()

五、Proxy Connection和Private Connection

到目前為止,我們知道了Control Connection:用於ngrok和ngrokd之間傳輸命令;Public Connection:外部發起的,嘗試向內網服務建立的連結。

相關推薦

穿透-ngrok原理淺析

之前在進行微信Demo開發時曾用到過ngrok這個強大的tunnel(隧道)工具,ngrok在其github官方頁面上的自我詮釋是 “introspected tunnels to localhost",這個詮釋有兩層含義: 1、可以用來建立public到localho

frp穿透原理

經過一段時間的使用,概況的總結下  內網穿透   讓內網的專案,也可以通過轉發 被外網訪問到   外網地址 +埠 www.ss.com埠預設80  ,即為www.ss.com 通過frp穿透時,內網127.0.0.1+埠/專案名 ,被

穿透ngrok的使用教程

我是一個學生,這一次教學寫專案時用到了內網穿透技術,於是我寫了一下,使用教程。 內網穿透一般常用於實現內網釋出的web專案能夠被外網訪問的功能,實現內網穿透的軟體有很多,比如nat123、花生殼或ngrok,這裡我使用ngrok。 實現如下: 我們先進入ngrok.cc

利用ngrok穿透 遠端連線手機ssh終端

圖片好大 臥槽 首先手機利用linux deploy安裝了Kali Linux 然後配置好linux的sshd_config 接下來是重點 1.首先在同一個區域網內ssh連線手機中的kali 2.安裝w3m(apt-get install w3m) 3.然後 w3m ngrok.com/d

穿透ngrok伺服器搭建)

轉載:https://blog.csdn.net/zhangguo5/article/details/77848658?utm_source=5ibc.net&utm_medium=referral 簡單來說內網穿透的目的是:讓外網能訪問你本地的應用,例如在外網開啟你本地http://1

穿透方法(免費ngrok伺服器搭建)

為什麼要實現內網穿透? 內網計算機(也就是LowID),都通過至少一層閘道器連線網際網路,沒有自己的獨立IP和埠(別人看到的你的IP是閘道器的),所以別人無法主動與你建立連線,兩個內網使用者自然也就無法連通,更無法實現傳輸。但是內網計算機可以主動連線其他有獨立

如何用ngrok進行穿透

2.點選左側選單“隧道管理”——>“開通隧道”,進入頁面後點擊購買免費版,進入頁面進行如下操作:“隧道名稱”隨便填入資訊,這裡填入“xxxxxx”;“前置域名”填入xxxxx(如lybwechat);“本地埠”為127.0.0.1:8080(這個可以改,只要與tom

Ngrok穿透的幾種利用

      其實這篇之前投稿了,不過版權在我這裡,地址在這裡:http://bobao.360.cn/learning/detail/3041.html       Ngrok是這樣介紹:一條命令解決的外網訪問內網問題,本地WEB外

ngrok快三原始碼下載穿透服務部署記錄

ngrok,【征途原始碼論壇http://zhengtuwangluo.com】聯絡方式:QQ:2747044651一個用於實現內網穿透服務,golang寫的,已經很久遠的一個東西了,可自己部署的版本最後一個版本是1.7.1,很久也沒更新了,但他還是比較穩妥的,

【本人禿頂程式設計師】穿透神器:Ngrok在支付中的正確使用姿勢

←←←←←←←←←←←← 我都禿頂了,還不點關注! 前言 隨著網際網路的發展,無論是web服務還是移動APP越來越多的都集成了第三方支付(支付寶、微信、銀聯)。通常作為服務提供方,支付成功以後都會有一個後端回撥URL來通知是否呼叫者是否支付成功,這個URL必須是公網環境,並且可以被訪

一分鐘實現穿透ngrok伺服器搭建)

簡單來說內網穿透的目的是:讓外網能訪問你本地的應用,例如在外網開啟你本地http://127.0.0.1指向的Web站點。 最近公司的花生殼到期了,要續費,發現價格一直在漲,都是5年以上的老使用者,旗艦版都沒有實現內網完全穿透,打算自己動手替換這個服務,中間走

阿里雲搭建ngrok實現穿透

內網穿透想必是開發微信的同志所必須的,大部分人首先想到的是去網上找各種現成的吧,比如sunny-ngrok或者向日葵之類的,但是世界上沒有免費的午餐,免費的都是會崩的!!!下面我就來教大家怎麼用阿里雲和ngrok搭建一個內網穿透!!!! 1.準備工作: 要能實現內網穿透,

搭建自己的ngrok服務(穿透 使用簡單)

在國內開發微信公眾號、企業號以及做前端開發的朋友想必對ngrok都不陌生吧,就目前來看,ngrok可是最佳的在內網除錯微信服務的tunnel工 具。記得今年春節前,ngrok.com提供的服務還一切正常呢,但春節後似乎就一切不正常了。ngrok.com無法訪問,

阿里雲搭建自己的ngrok服務-實現穿透

參考博文:https://blog.csdn.net/qq_34292044/article/details/78559128https://blog.csdn.net/huanxiang201311/article/details/72725891一.環境準備       

穿透神器ngrok支援linux,windows,mac

原文網址:http://www.phpbulo.com/archives/408.html 買了個樹莓派,平時搗鼓著玩玩,相當於超小型卡片電腦,最近想在上面建立一個web站點使用者訪問,最簡單的方法當然是在路由器上面對映80埠,可是很多人和我一樣沒有路由器的許可權。最近

ngrok域名申請並穿透

上一篇文章介紹了[花生殼申請域名以及內網穿透](https://blog.csdn.net/qq_36330228/article/details/85226328),相比花生殼來說ngrok的域名和內網穿透都是免費的,但是ngrok的網路不穩定。下面介紹下如何申請吧: 1.進入Ng

穿透工具ngrok的安裝和使用,超簡單~

寫在前面: ngrok可以做內網穿透,外網對映,可以用其他任何有網的裝置訪問你當前開啟對映的埠專案~ 全域性安裝ngrok npm install ngrok -g 如果本地起了一個服務,埠號為3001。則輸入以下命令: ngrok http 3001

一個免費的穿透工具-sunny-ngrok

https://www.ngrok.cc/user.html 使用方法:下載安裝======然後去註冊======註冊完成會到個人資訊呢裡=======在個人資訊呢裡找到隧道管理========然後新建隧道====找到那個免費的====然後填寫資訊=====然後會得到一個隧

淺析穿透可行方案

緣起 最近在做一個微信網站,需要和微信對接。發現開發的時候需要將自己電腦上的服務對映到公網上,才能接收到微信公眾平臺的回撥。 因此,百度搜尋整理各種可行的內網穿透方案。 內網穿透原理

搭建 ngrok 服務實現穿透

文章目錄 提醒:本文最後更新於 1362 天前,文中所描述的資訊可能已發生改變,請謹慎使用。 我們經常會有「把本機開發中的 web 專案給朋友看一下」這種臨時需求,為此專門在 VPS 上部署一遍就有點太浪費了。之前我通常是在 ADSL 路由器上配個埠對映讓本機服務在外網可以訪問,但現在大部