通过分析ngrok日志推测其实现原理和方式 有更新!

  Bob

    ngrok 是一个代理服务器,提供网络代理功能,例如:

    		+---------------+
    		|  ngrok server |
    		+---------------+
    		     /          \
    		    /             \
    	+---------------+     \
    	|  ngrok client |      \
    	+---------------+        \
    		|                       \
    	+---------------+	    +---------------+  
    	|     A app	|	    |      B app	|
    	+---------------+	    +---------------+

    有如下的条件:

    1 A, B 是 2 个 应用程序,运行在内网中;

    2 ngrok server 运行在公网服务器中;

    3 ngrok client 与 A 运行在同一个系统中;

    //========================================================================

    如果 B 想与 A 进行交互,有如下的流程:

    1 B 把数据发给 ngrok server;

    2 ngrok server 把数据转发给 ngrok client;

    3 ngrok client 把数据转发给 A;

    4 A 处理数据,把需要返回给 B 的数据,返回给 ngrok client;

    5 ngrok client 把数据转发给 ngrok server;

    6 ngrok server 把数据转发给 B;

    最终,A, B 就通过 ngrok 的转发,完成数据的交互;

    //========================================================================

    我们可以认为,ngrok 代理了 A 对象对外提供的业务,当 B 对象向与 A 交互的时候,只需要与 A 对象的代理 ngrok 交互就可以了;

    那么,这一切交互过程,存在一定的“协议”通信;

    下面讲解在交互过程中,涉及到的“协议”:

    下面简称: client == ngrok client, server == ngrok server;

    //========================================================================

    (1) CMD = Auth

    authentication --- 认证

    client 向 server 进行认证注册;

    client 启动,发送 CMD = Auth 命令给 server; 告诉服务器,我这个客户端需要连接到该服务器,进行“认证”; 例如:

    {"Type":"Auth","Payload":{"Version":"2","MmVersion":"1.7","User":"","Password": "","OS":"darwin","Arch":"amd64","ClientId":""}}

    //========================================================================

    (2) CMD = AuthResp

    authentication response --- 认证回应

    server 给 client 回应 CMD = Auth 命令;例如:

    {"Type":"AuthResp","Payload":{"Version":"2","MmVersion":"1.7","ClientId":"c53d9a89c0ff87c37f14be30ff1f0ecb","Error":""}}

    //========================================================================

    (3) CMD = ReqTunnel


    request tunnel --- 请求隧道

    client 向 server 请求隧道的建立;例如:

    {"Type":"ReqTunnel","Payload":{"Protocol":"http","ReqId":"NWLRB","Hostname": "","Subdomain":"wkf168","HttpAuth":"","RemotePort":0,"authtoken":""}

    client 把自己的信息发送给 server,请求 server 建立隧道;

    Protocol --- 请求的协议
    ReqId --- Client 的一个 ID 信息;
    Subdomain --- 子域名


    //========================================================================

    (4) CMD = ReqProxy

    request proxy --- 请求代理

    server 发送给 client,请求 client 进行“代理业务”;例如:

    {"Type":"ReqProxy","Payload":{}}

    此时,client 创建一个 socket fd 连接到 server,server 就把 accept 到的 socket fd 作为“隧道”;

    //========================================================================

    (5) CMD = RegProxy

    register proxy --- 注册代理

    当 client 收到 server 的“请求代理”命令之后,可以给服务器发送“注册代理”命令,例如:

    {"Type":"RegProxy","Payload":{"ClientId":"c53d9a89c0ff87c37f14be30ff1f0ecb"}}

    其中,Client ID 是当前 client 的一个认证ID,是服务器携带 AuthResp 命令的时候,返回的信息;

    此时,client 把这个 ID 发送给 sever,携带 RegProxy 命令,表示要注册成为一个代理;

    //========================================================================

    (6) CMD = NewTunnel

    new tunnel --- 新建一个隧道;

    server 收到 client 的注册代理命令之后,给 client 注册了一个代理的身份;

    同时,把 client 在 server 的代理身份信息返回,例如:

    {"Type":"NewTunnel","Payload":{"ReqId":"NWLRB","Url":"http://wkf168.ngrok.bob.kim","Protocol":"http","Error":""}}

    此时,client 已经在 server 端完成了“代理身份”的创建,返回:

    RegId --- 是 client 发送 ReqTunnel 命令时携带的一个 ID 信息;
    Url --- 是 server 为当前这个 client 代理分配的一个 URL;

    那么,当其他应用程序访问 Url 的时候,就把请求的数据,转发给当前的 client;

    //========================================================================

    此时,经过上面的这几个步骤,完成了“隧道”的建立;

    那么,当有其他应用程序访问 Url 的时候,就有如下的命令交互:

    //========================================================================

    (7) CMD = StartProxy

    start proxy --- 开始启动代理

    例如:

    {"Type":"StartProxy","Payload":{"Url":"http://wkf168.ngrok.bob.kim","ClientAddr":"183.14.133.76:49127"}}

    当有应用程序访问 Url 的时候,ngrok server 接收到数据;然后,给 client 发送 StartProxy 命令,要求 client 开始启动代理服务;

    那么,在 ReqProxy 命令中,server 给 client 发送该命令之后,client 就创建了一个 socket fd 连接到 server;

    那么,我们定义这个 socket fd 为 fdA;

    此时,这个 fdA 就作为一个“隧道”接口;

    此时,client 也是从这个 fdA 隧道接口,接收到 “StartProxy” 命令;

    那么,client 就新建一个 socket fd,连接到本地的 IP + PORT; 我们定义这个 socket fd 为 fdB;

    最终,fdA 与 fdB 形成一个映射的关系对,有:

    1 fdA 接收到的数据,转发给 fdB;

    2 fdB 接收到的数据,转发给 fdA;

    //========================================================================

    (8) CMD = Ping --- 作为心跳包使用
    (9) CMD = Pong --- 是 Ping 命令的回复

    //========================================================================

    如同 RTSP 协议一样,有:

    1 Client 与 Server 之间,有一条 socket fd 作为“协议交互”使用的通道;

    是收发上面提到的这个 CMD 指令;

    2 Client 与 Server 再传输音视频数据的时候,需要建立新的 socket fd 来交互数据;

    同理,在 ngrok 中,也是一样,ngrok client 开始的时候,创建一条 socket fd 与 ngrok server 连接,交互通信命令;

    当 ngrok client 接收到 CMD = ReqProxy 命令的时候,就创建一条新的 socket fd,连接到 ngrok server,作为“数据交互”的隧道接口;

    //========================================================================

    所以,ngrok client 与 ngrok server 在进行隧道数据转发的时候,有如下的流程:

    1 client 开始建立一条 socket fd 连接到 server,作为 CMD 命令交互通道;定义该 socket fd 为 fdA;

    2 server 通过 fdA 给 client 发送 CMD = ReqProxy 命令; 请求 client 进行代理活动;

    那么,client 就新建一条 socket fd 连接到 server,定义该 socket fdB;

    3 server 接收到 fdB 的连接之后,给该接口发送 CMD = StartProxy 命令,要求该 fdB 接口开始进行代理活动;

    最终,这条 fdB 就作为 client 与 server 交互转发数据的“隧道”接口;

     

    更详细的源码级别的分析参考:https://blog.csdn.net/lyb3290/article/details/80239890


    [ngrok客户端windows 64版_bob.rar]

    如有疑问或同行交流欢迎加群讨论:铂金信息技术交流群 151258054