简介
本篇博客将介绍内网穿透原理及方法,并将以代码的形式展现出来可靠且安全的内网穿透、端口映射转发工具 程序开源在我的GitHb仓库中,学习和使用了的麻烦给个Star啦,万分感谢
一、背景
1.1 情景假设
- 假如我在学校里有一台台式电脑A,这台台式电脑在实验室局域网内拥有一个局域网IP
192.168.0.A (为了方便我们这样描述IP) - 我在家里有一台笔记本B,这台笔记本在家中局域网内拥有一个局域网IP
192.168.0.B
1.2 想要达到的目的
- 我在家里想使用笔记本B通过ssh登录到A电脑的22号端口进行shell相关操作
1.3 局限
- 我们是没法直接访问到的,因为在当前网络环境下,我们使用的机子一般都不具备公网IP,这样我们在寻址时只靠对方的局域网IP是无法找到他的
1.3 解决方案一(路由器NAT)
- 我们的A机器是处于实验室的局域网内,但如果我们有权限去控制上层的网关路由器,即这个上层的路由器具有一个公网IP,假设为
123.123.123.123 ,那么我们可以在路由器中设置端口映射(NAT),例如将路由器的10022 端口映射到局域网内A电脑的22号端口,这样我们在家中只要通过IP+端口123.123.123.123:10022 就可以登录到实验室里台式机A的22号ssh服务端口了 - 但很多时候我们并没有权限去控制这样一个网关路由,因为在现在的网络环境下,很有可能我们整个一层楼甚至一栋楼使用一个公网IP,每个实验室里又一个路由器,存在路由器多级嵌套的情况,网关路由的操作权限不会那么轻易获取,并且即使获取也将出现每一层路由器都需要配置端口映射而十分繁琐
1.4 解决方案二(云服务器转发)
- 而换一种想法,如果我们拥有一台具有公网IP的云服务器,那么我们完全可以将云服务器作为一个中继,大家(A和B)都连接到这台具有公网IP的云服务器并保持连接,云服务器都记录好连接着的机器谁是谁,大家需要相互沟通时(B想连接A)就告诉云服务器我想和谁沟通并将想沟通的消息发送出去即可,这里可能解释的不清晰,我们后面将做进一步的详细介绍,后续的网络编程即是实现这样一个消息转发功能而最终达到端口映射。
二、方案介绍
2.1 方案简介
必要条件,一台具有公网IP的云服务器 通过云服务器将访问传输的消息进行转发
- 假设我们的云服务器IP为
101.101.101.101 ,我们将要编写一个服务端程序运行在云服务器上,并监听10101 服务端口 - 还需要编写一个客户端运行在实验室台式机上,该客户端需要登录并连接到服务端的
10101 号端口进行C/S信息交互
如果我们想将云端的40022号端口映射到实验室台式机的22号端口,那么这对C/S程序最终将达到的目的是,当我们输入公网IP+端口101.101.101.101:40022 时我们将能够直接与实验室内的192.168.0.A:22 进行通信。
2.2 具体流程
- 服务端首先监听
10101 端口 - 客户端建立与
101.101.101.101:10101 的TCP连接,这个连接Socket我们将之称为信息交互Socket - 服务端在10101端口接收到的连接Socket我们也称为信息交互Socket
- 客户端将想要映射的端口通过信息交互Socket告知服务端,即告知服务端帮忙监听下云端的40022号端口
- 服务端通过信息交互Socket得知需要对40022号端口进行监听
- 当我在家中连接
101.101.101.101:40022 时,服务端将接收到连接请求并得到一个Socket,我们称之为SocketWan ,随后将这个连接事件通过信息交互Socket 告诉那个对应的客户端 - 客户端在
信息交互Socket 上得知了这个连接事件,客户端程序发起一个到本机127.0.0.1:22 的连接请求,这个连接将得到一个Socket,我们称之为SocketLan - 完成了所有的连接建立,后面就是进行
SocketWan 和SocketLan 之间的消息转发了,这之间的消息均需要通过信息交互Socket 进行间接转发,即①SocketWan得到的消息,一并通过信息交互Socket转发给客户端,客户端在信息交互Socket上了解到有来自SocketWan的消息,立即转发到对应的SocketLan上,这就实现从101.101.101.101:40022-->192.168.0.A:22 的单向消息传输;②当SocketLan得到消息时,将其得到的消息通过信息交互Socket一并转发给服务端,服务端再将消息转发至对应的SocketWan上,这就实现从192.168.0.A:22-->101.101.101.101:40022 的另一单向传输,最终就实现了两者的双向消息传输
2.3 编程要点
- 客户端需要读取配置文件,并登录至服务端,且携带想要映射的云端端口(类似云端40022端口)
- 服务端监听服务端口10101,且需要处理客户端的登录事件,验证登录信息,并新开一个线程处理C/S的信息交互,并新开N个线程对需要监听的N个云端端口进行监听
- 服务端需要处理新连接请求,即有用户连接到40022端口了,那么我们需要通知客户端你想要监听的40022号端口有了新连接
- 客户端需要处理新连接事件,新建线程发起对本机或局域网内对应主机+端口的连接(例如本机127.0.0.1:22)
- 客户端和服务端都将收到建立了连接的端口发来的应用层消息,我们需要对这些消息进行封装、形成格式规范的消息头+消息体,客户端和服务端通过
信息交互Socket 传递消息,接收方获取到转发事件对消息进行解析并转发到对应端口 - 服务端和客户端都需要处理关闭连接请求
- 消息传输时需要处理TCP粘包/拆包问题,需要规范我们的消息类型,对于登录事件、新连接事件、关闭连接事件、消息转发事件以及心跳检测事件等我们都需要有具体的消息格式规范
- 服务端和客户端都需要处理异常断开情况,需进行心跳检测来判断是否发生了异常断开而未被捕捉产生的阻塞
- 为达到安全使用的目的,对于服务端<->客户端的消息传输,防止被抓包获得我们的通信内容,我们需要对传输进行加密
- 在登录时我们需要进行密码校验,以应对外部的攻击,例如恶意要求监听服务端1-65535所有端口
- 注意资源的释放,关闭连接和异常断开时的线程资源释放,很多时候会出现难以发现的资源占用,可通过相关的命令(jstack jmap jhat)等查看进程线程的资源占用情况
- 支持失败重试,当意外断网时,定期进行登录尝试
三、程序简介
3.1 简介
程序开源在我的GitHb仓库中 项目是一个Maven工程,程序主要分为客户端相关、服务端相关以及公用的工具类 服务端的主程序为com.liang.server.Server.java ,客户端的主程序为com.liang.client.Client.java ,服务端的配置文件如下,这里单机测试时ip地址都设置为127.0.0.1
common:
bind_port: 10101
token: 123456
客户端配置文件config_client.yaml
common:
server_addr: 127.0.0.1
server_port: 10101
token: 123456
nat:
ssh-2:
type: tcp
local_ip: 127.0.0.1
local_port: 22
remote_port: 40022
vnc-21:
type: tcp
local_ip: 127.0.0.1
local_port: 5901
remote_port: 45901
3.2 调试运行
运行com.liang.server.Server.java ,将会弹出如下 运行com.liang.client.Client.java 后,客户端显示 登录后服务端显示如下 我们将可以通过本地40022号端口间接连接到本地22号端口,例如输入ssh name@127.0.0.1 -p 40022 进行连接,如遭遇报错提示主机不一致等消息执行ssh-keygen -f "/home/name/.ssh/known_hosts" -R "[127.0.0.1]:40022" ,其中name为你的用户名,连接后将能够使用 程序中有许多的日志打印,但程序的默认日志级别为INFO,如想查看日志的追踪打印,将日志级别设置为TRACE即可,日志配置文件在resources/logback.xml 中,也可以不更改该文件,在运行程序时使用命令进行指定,例如在idea中编辑运行、调试配置 添加如下trace参数即可将日志级别设置为TRACE,这样程序中的log.trace打印出来的日志将会显示便于学习和调试
四、上线使用
4.1 测试使用
- 上线使用时,可直接在GitHub releases中下载使用,也可以自行修改编译出来进行使用
- 将
NatServer-v1.jar 和config_server.yaml 放置于云端服务器中的某个文件夹NatSoft内,可视情况修改配置文件,并执行 java -jar NatServer-v1.jar 即可运行,执行java -jar NatServer-v1.jar trace 可以指定日志级别为TRACE,测试调试时可以这样设置,但真实上线使用时不建议打印(耗时) 配置内容及解释示例如下common:
bind_port: 10101
token: 123456
- 将
NatClient-v1.jar 和config_client.yaml 放置于局域网内的电脑的某个文件夹NatSoft内,修改config_client.yaml ,更改想要映射的端口和云服务器的IP密码等,执行 java -jar NatClient-v1.jar 即可 配置内容示例如下common:
server_addr: XXX.XXX.XXX.XXX
server_port: 10101
token: 123456
nat:
ssh-1:
type: tcp
local_ip: 127.0.0.1
local_port: 22
remote_port: 40022
ssh-2:
type: tcp
local_ip: 192.168.0.202
local_port: 22
remote_port: 40023
4.2 设置自启动服务
上面的测试使用在终端断开后将停止运行,因此我们需要注册我们的服务,达到开机自启动,或者手动运行服务后终端掉线依然运行
4.2.1 服务端开机自启动
执行sudo vim /etc/systemd/system/natServer.service 创建服务,编辑如下
[Unit]
Description=nat server daemon
After=syslog.target network.target
Wants=network.target
[Service]
Type=simple
WorkingDirectory=/home/name/NatSoft
ExecStart=/path/to/your/java -jar NatServer-v1.jar
Restart= always
RestartSec=1min
[Install]
WantedBy=multi-user.target
执行如下
systemctl daemon-reload
systemctl start natServer
systemctl enable natServer
4.2.2 客户端开机自启动
执行sudo vim /etc/systemd/system/natClient.service 创建服务,编辑如下
[Unit]
Description=nat client daemon
After=syslog.target network.target
Wants=network.target
[Service]
Type=simple
WorkingDirectory=/home/name/NatSoft
ExecStart=/path/to/your/java -jar NatClient-v1.jar
Restart= always
RestartSec=1min
[Install]
WantedBy=multi-user.target
执行如下
systemctl daemon-reload
systemctl start natClient
systemctl enable natClient
|