通过分析ngrok日志推测其实现原理和方式
https://bob.kim/articles/2018/05/09/1525819680566.html
grok 是一个代理服务器,提供网络代理功能,例如:
|
有如下的条件:
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