简介

本篇博客将介绍内网穿透原理及方法,并将以代码的形式展现出来可靠且安全的内网穿透、端口映射转发工具
程序开源在我的GitHb仓库中,学习和使用了的麻烦给个Star啦,万分感谢

更新

  1. 更新了web管理,能够管理允许登录的IP,设置IP白名单防火墙
  2. 更新了代理上网功能,能够代理内网,例如实现在校外访问校园内网才能访问的网站

一、背景

1.1 情景假设

  1. 假如我在学校里有一台台式电脑A,这台台式电脑在实验室局域网内拥有一个局域网IP 192.168.0.A(为了方便我们这样描述IP)
  2. 我在家里有一台笔记本B,这台笔记本在家中局域网内拥有一个局域网IP192.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 具体流程

  1. 服务端首先监听10101端口
  2. 客户端建立与101.101.101.101:10101的TCP连接,这个连接Socket我们将之称为信息交互Socket
  3. 服务端在10101端口接收到的连接Socket我们也称为信息交互Socket
  4. 客户端将想要映射的端口通过信息交互Socket告知服务端,即告知服务端帮忙监听下云端的40022号端口
  5. 服务端通过信息交互Socket得知需要对40022号端口进行监听
  6. 当我在家中连接101.101.101.101:40022时,服务端将接收到连接请求并得到一个Socket,我们称之为SocketWan,随后将这个连接事件通过信息交互Socket告诉那个对应的客户端
  7. 客户端在信息交互Socket上得知了这个连接事件,客户端程序发起一个到本机127.0.0.1:22的连接请求,这个连接将得到一个Socket,我们称之为SocketLan
  8. 完成了所有的连接建立,后面就是进行SocketWanSocketLan之间的消息转发了,这之间的消息均需要通过信息交互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 编程要点

  1. 客户端需要读取配置文件,并登录至服务端,且携带想要映射的云端端口(类似云端40022端口)
  2. 服务端监听服务端口10101,且需要处理客户端的登录事件,验证登录信息,并新开一个线程处理C/S的信息交互,并新开N个线程对需要监听的N个云端端口进行监听
  3. 服务端需要处理新连接请求,即有用户连接到40022端口了,那么我们需要通知客户端你想要监听的40022号端口有了新连接
  4. 客户端需要处理新连接事件,新建线程发起对本机或局域网内对应主机+端口的连接(例如本机127.0.0.1:22)
  5. 客户端和服务端都将收到建立了连接的端口发来的应用层消息,我们需要对这些消息进行封装、形成格式规范的消息头+消息体,客户端和服务端通过信息交互Socket传递消息,接收方获取到转发事件对消息进行解析并转发到对应端口
  6. 服务端和客户端都需要处理关闭连接请求
  7. 消息传输时需要处理TCP粘包/拆包问题,需要规范我们的消息类型,对于登录事件、新连接事件、关闭连接事件、消息转发事件以及心跳检测事件等我们都需要有具体的消息格式规范
  8. 服务端和客户端都需要处理异常断开情况,需进行心跳检测来判断是否发生了异常断开而未被捕捉产生的阻塞
  9. 为达到安全使用的目的,对于服务端<->客户端的消息传输,防止被抓包获得我们的通信内容,我们需要对传输进行加密
  10. 在登录时我们需要进行密码校验,以应对外部的攻击,例如恶意要求监听服务端1-65535所有端口
  11. 注意资源的释放,关闭连接和异常断开时的线程资源释放,很多时候会出现难以发现的资源占用,可通过相关的命令(jstack jmap jhat)等查看进程线程的资源占用情况
  12. 支持失败重试,当意外断网时,定期进行登录尝试

2.4 关于web管理IP白名单的更新

使用springboot写了一个ip白名单防火墙管理网页,因为开放端口映射是比较危险的,公网上充斥着大量的攻击,可能内网机器由于映射到公网上受到攻击,因此我实现了一个IP管理的功能,在原有程序上增加了springboot框架,使用sqlite建立了一个表格,一般的linux系统都安装了sqlite3数据库引擎,没安装的可以百度如何安装,存储内容是,

字段备注
ipip名称(主键),例如192.168.0.1
addressIP的属地,精确至市
create_time创建时间
comment备注

创建了web管理页面,登录页面认证后可添加需要放行的IP,该表外的IP将不被允许连接

2.5 关于socks5代理穿透内网上网功能的更新

例如实现当你在家中的时候依然可以访问到校内图书馆网址,或者访问实验室内局域网内任意机子,即添加代理功能,客户端让云服务器监听7890端口,校外用户将系统代理设置为云服务器IP:7890就可以愉快使用内网了,实现的需要遵从socks5协议,不了解的可以参考Socks 5 协议解析,编写过程中学习了java编写的低配版,该实现是和我想达到的目的少了一层转发

三、程序简介

3.1 简介

程序开源在我的GitHb仓库中,如果帮到了你麻烦点个star啦,万分感谢
项目是一个Maven工程,程序主要分为客户端相关、服务端相关以及公用的工具类,最新版本代码中添加了web和proxy包,分别实现web防火墙管理和socks5代理实现
在这里插入图片描述
服务端的主程序为com.liang.server.Server.java,客户端的主程序为com.liang.client.Client.java,服务端的配置文件如下,这里单机测试时ip地址都设置为127.0.0.1

common:
  bind_port: 10101    # 服务端口  可更换,保持与客户端一致即可
  token: 123456       # 密码

web:
  bind_port: 10102  # 网页管理登录端口,可管理IP白名单
  username: admin   # 登录用户名
  password: 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 # 需要被映射的内网机器的IP
    local_port: 22      # 需要被映射的内网机器的端口
    remote_port: 40022  # 对应的云端服务器映射端口    # 达到的效果是访问 server_addr+40022 相当于局域网内local_ip+local_port

  vnc-21:
    type: tcp
    local_ip: 127.0.0.1
    local_port: 5901
    remote_port: 45901

socks5_proxy: # 代理 将浏览器的代理或系统代理改为 socks5,server_addr:7999 实现代理学校内网上网
  type: tcp
  remote_port: 7999

3.2 调试学习

运行前需确保系统上有sqlite数据库引擎,一般的linux系统都默认安装了sqlite3数据库引擎,可直接命令行sqlite3查看是否正常弹出,没安装的可以百度如何安装

  1. 运行com.liang.server.Server.java,将会弹出如下(由于加了springboot管理,打印内容变了)
    在这里插入图片描述
  2. 运行com.liang.client.Client.java后,客户端显示
    在这里插入图片描述
  3. 登录后服务端显示如下
    在这里插入图片描述
  4. 我们将可以通过本地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中编辑运行、调试配置
    在这里插入图片描述
  5. 添加如下trace参数即可将日志级别设置为TRACE,这样程序中的log.trace打印出来的日志将会显示便于学习和调试
    在这里插入图片描述

四、上线使用

4.1 测试使用

  1. 上线使用时,可直接在GitHub releases中下载使用,也可以自行修改编译出来进行使用
  2. NatServer-v1.jarconfig_server.yaml放置于云端服务器中的某个文件夹NatSoft内,可视情况修改配置文件,并执行 java -jar NatServer-v1.jar即可运行,执行java -jar NatServer-v1.jar trace可以指定日志级别为TRACE,测试调试时可以这样设置,但真实上线使用时不建议打印(耗时)
    配置内容及解释示例如下
    common:
      bind_port: 10101    # 服务端口  可更换,保持与客户端一致即可
      token: 123456       # 密码
    
    web:
      bind_port: 10102  # 网页管理登录端口,可管理IP白名单 浏览器输入server_addr:10102可登录
      username: admin   # 登录用户名
      password: 123456  # 登录密码
    
  3. NatClient-v1.jarconfig_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-2:
        type: tcp
        local_ip: 127.0.0.1 # 需要被映射的内网机器的IP
        local_port: 22      # 需要被映射的内网机器的端口
        remote_port: 40022  # 对应的云端服务器映射端口    # 达到的效果是访问 server_addr+40022 相当于局域网内local_ip+local_port
    
      vnc-21:
        type: tcp
        local_ip: 127.0.0.1
        local_port: 5901
        remote_port: 45901
    
    socks5_proxy: # 代理 将浏览器的代理或系统代理改为 socks5,server_addr:7999 实现穿透学校内网上网
      type: tcp
      remote_port: 7999
    
    

4.2 设置自启动服务

上面的测试使用在终端断开后将停止运行,因此我们需要注册我们的服务,达到开机自启动,或者手动运行服务后终端掉线依然运行

4.2.1 服务端开机自启动

执行vim /etc/systemd/system/natServer.service创建服务,编辑如下

[Unit]
Description=nat server daemon
After=syslog.target  network.target
Wants=network.target

[Service]
Type=simple
User=name	# 如果是root用户可省略
WorkingDirectory=/home/name/NatSoft			# 编辑的时候一定要删除注释 这里更改为自己放置jar包和配置的绝对路径
ExecStart=/path/to/your/java -jar NatServer-v1.jar	# 编辑的时候一定要删除注释 这里更改为自己在java命令的安装位置 可使用 which java查看
Restart= always
RestartSec=1min
[Install]
WantedBy=multi-user.target

执行如下

#启动natServer
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
User=name	# 编辑的时候一定要删除注释 这样可以使得进程归用户所有,使用jps查看时可以查看到,如果不设置,那么普通用户jps查看不到
WorkingDirectory=/home/name/NatSoft			# 编辑的时候一定要删除注释 这里更改为自己放置jar包和配置文件的绝对路径
ExecStart=/path/to/your/java -jar NatClient-v1.jar # 编辑的时候一定要删除注释 这里更改为自己在java命令的安装位置 可使用 which java查看
Restart= always
RestartSec=1min
[Install]
WantedBy=multi-user.target

执行如下

#启动natServer
systemctl daemon-reload
systemctl start natClient
#设置为开机启动
systemctl enable natClient

4.3 IP白名单防火墙管理

  1. 输入你设置的web管理端口,server_addr:10102
    在这里插入图片描述
  2. IP白名单列表
    在里面可以删除和修改每条记录
    在这里插入图片描述
  3. 添加IP
    点击上方添加IP,将进入如下界面,下面的IP自动填充为访问设备的IP,属地为联网查询到并填充的,备注可以手动填写,另外我更新了一种IP过滤策略,一个IP是32位的,当添加IP为192.168.0.0/24时,则只需要IP的前24位与给定IP相同即可,即192.168.0开头即可
    在这里插入图片描述
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐