文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Go语言网络编程:Go实现简单的内网穿透

2023-09-02 20:37

关注

一、内网穿透

1. 内网穿透介绍

内网穿透是一种通过互联网建立远程连接到私有网络(内网)中设备的技术。通常情况下,内网设备由于位于一个受限制的网络环境中,无法直接从外部访问。内网穿透通过将内网设备的流量转发到公共网络上的服务器,然后再将该流量转发到客户端,从而实现与内网设备的通信。

2.内网穿透下访问流程

假设目前已经实现了内网穿透,以下从用户角度出发,给出了用户访问穿透后的公网网络的步骤

  1. 用户发出请求:用户通过公网地址发出一个访问请求,目标是服务端穿透后的公网地址。
  2. 服务端接收请求:服务端监听公网地址,接收到用户的请求后,将请求信息发送给客户端,如一个"New Connection"的消息。
  3. 客户端接收请求:客户端在内网中,持续监听来自服务端的信息。当接收到"New Connection"的消息后,知道有新的请求需要处理。
  4. 客户端连接隧道服务器:客户端接收到新的请求后,会创建一个新的连接到隧道服务器。隧道服务器在公网上,是客户端和服务端之间的桥梁。
  5. 客户端连接内网服务:同时,客户端也会创建一个新的连接到内网中的目标服务。这个服务是用户最初想要访问的服务。
  6. 数据转发:客户端将内网服务的数据拷贝到隧道服务器(如io.Copy(tunnelConn, clientConn)),同时将隧道服务器的数据拷贝到内网服务(io.Copy(clientConn, tunnelConn))。这样,用户的请求就通过隧道服务器,被转发到了内网服务。
  7. 服务端接收隧道数据:服务端在公网上,监听来自隧道服务器的连接。当隧道服务器有新的数据时,服务端会接收这些数据。
  8. 服务端转发数据:服务端接收到隧道服务器的数据后,会将这些数据转发给公网地址。可以通过io.Copy(appConn, tunnelConn)io.Copy(tunnelConn, appConn)实现,这两个函数将隧道服务器的数据拷贝到用户,同时将用户的数据拷贝到隧道服务器。
  9. 用户接收数据:用户最终接收到来自服务端的数据,这些数据实际上是内网服务的响应。这样,用户就成功地通过公网访问了内网服务。

以上就是用户发出访问请求到服务端、隧道、客户端的详细过程。通过这个过程,我们可以看到内网穿透是如何工作的。

二、go实现简单的内网穿透

实现内网穿透需要两个部分:客户端和服务端。客户端运行在内网中,服务端运行在公网中。客户端和服务端通过隧道进行通信,客户端将内网服务的数据发送到服务端,服务端将公网的请求发送到客户端。

要实现内网穿透,通常需要以下几个步骤:

0. 项目源码地址:https://github.com/Pistachiout/Net-Penetration

1. constant.go常量端口定义

package constantconst (// 需要内网穿透的服务端口,运行内网穿透客户端后,该端口将映射到公网,公网可以访问该端口AppPort = ":4000"// ServerPort 服务端监听端口,用于连接客户端,一般为公网端口,服务端监听该端口,接收客户端的数据,然后转发给服务端本地服务ServerPort = ":8081"// TunnelPort 隧道端口,用于客户端和服务端建立隧道,客户端和服务端通过该端口通信TunnelPort = ":8082"// AppTargetPort 穿透后的目的服务端口,在服务端端口运行客户端应用AppTargetPort = ":8083"// ServerIP 服务端公网ip地址,用于连接服务端,一般为公网IPServerIP = ""// ServerAddr 服务端监听地址,用于接收客户端的数据,然后转发给服务端本地服务ServerAddr = ServerIP + ServerPort// TunnelAddr 隧道地址,用于客户端和服务端建立隧道,客户端和服务端通过该地址通信TunnelAddr = ServerIP + TunnelPort// BufSize 缓冲区大小BufSize = 1024)

以上常量定义了在内网穿透过程中所需的各种端口和地址。

这些常量提供了在内网穿透过程中所需的各种配置信息,可以根据具体的应场景和需求进行相应的调整和配置。若用户拥有公网ip,还需要添加公网ip地址

2. 辅助函数包helper.go

package helperimport ("log""net""time")// CreateListen 监听,参数为监听地址listenAddr,返回 TCPListener,通过 net.ResolveTCPAddr 解析地址,通过 net.ListenTCP 监听端口////监听是指服务端监听某个端口,等待客户端的连接,一旦客户端连接上来,服务端就会创建一个新的goroutine处理客户端的请求。//// ResolveTCPAddr是一个解析TCP地址的函数,addr为域名或者IP地址加端口号,返回一个TCPAddr,该结构体包含了ip和port// ListenTCP函数监听TCP地址,addr则是一个TCP地址,返回值l是一个net.Listener接口,可以用来接收连接。func CreateListen(listenAddr string) (*net.TCPListener, error) {tcpAddr, err := net.ResolveTCPAddr("tcp", listenAddr)if err != nil {return nil, err}tcpListener, err := net.ListenTCP("tcp", tcpAddr)return tcpListener, err}// CreateConnect 连接,参数为服务端地址connectAddr,返回 TCPConn,通过 net.ResolveTCPAddr 解析地址,通过 net.DialTCP 连接服务端// 连接是指客户端连接服务端,连接成功后,客户端就可以向服务端发送数据了,与监听不同的是,连接是客户端发起的,而监听是服务端发起的。// DialTCP函数在网络协议tcp上连接本地地址laddr和远端地址raddrfunc CreateConnect(connectAddr string) (*net.TCPConn, error) {// 解析地址,返回TCPAddrtcpAddr, err := net.ResolveTCPAddr("tcp", connectAddr)if err != nil {return nil, err}tcpConn, err := net.DialTCP("tcp", nil, tcpAddr)return tcpConn, err}// KeepAlive 保持连接,参数为连接conn,通过循环向连接中写入数据,保持连接,每隔3秒写入一次,如果写入失败,说明连接已经断开,退出循环func KeepAlive(conn *net.TCPConn) {for {_, err := conn.Write([]byte("KeepAlive"))if err != nil {log.Printf("[KeepAlive] Error %s", err)return}time.Sleep(time.Second * 3)}}// GetDataFromConnection for循环获取Connection中的数据func GetDataFromConnection(bufSize int, conn *net.TCPConn) ([]byte, error) {b := make([]byte, 0)for {// 读取数据data := make([]byte, bufSize)n, err := conn.Read(data)if err != nil {return nil, err}b = append(b, data[:n]...)if n < bufSize {break}}return b, nil}

这段代码是项目中常用的辅助函数包,它提供了一些用于网络通信的帮助函数:

  1. CreateListen(listenAddr string) (*net.TCPListener, error):该函数用于创建一个TCP监听器。它接受一个监听地址listenAddr作为参数,并通过net.ResolveTCPAddr解析地址,然后通过net.ListenTCP监听指定的端口。函数返回一个*net.TCPListener类型的指针和一个错误。
  2. CreateConnect(connectAddr string) (*net.TCPConn, error):该函数用于创建一个TCP连接。它接受一个服务端地址connectAddr作为参数,并通过net.ResolveTCPAddr解析地址,然后通过net.DialTCP与服务端建立连接。函数返回一个*net.TCPConn类型的指针和一个错误
  3. KeepAlive(conn *net.TCPConn):该函数用于保持TCP连接的活跃状态。通过循环向连接中写入数据,保持连接,每隔3秒写入一次,如果写入失败,说明连接已经断开,退出循环
  4. GetDataFromConnection(bufSize int, conn *net.TCPConn) ([]byte, error):该函数用于从TCP连接中获取数据。它接受一个缓冲区大小bufSize和一个*net.TCPConn类型的指针作为参数。函数通过循环从连接中读取数据,并将读取的数据追加到一个字节数组中,直到读取完所有数据或达到缓冲区大小。函数返回读取到的数据字节数组和一个错误。

3. 客户端main.go

package mainimport ("Net-Penetration/constant""Net-Penetration/helper""io""log")// 内网穿透客户端,用于将本地服务映射到公网,使得公网可以访问本地服务,实现内网穿透,func main() {// 连接服务端conn, err := helper.CreateConnect(constant.ServerAddr)if err != nil {panic(err)}log.Printf("连接成功,连接地址为:%s\n", conn.RemoteAddr().String())// 保持连接,读取数据for {// 从连接中读取数据data, err := helper.GetDataFromConnection(constant.BufSize, conn)if err != nil {log.Printf("读取数据失败,错误信息为:%s\n", err.Error())continue}log.Printf("接收到数据:%s\n", string(data))// 判断是否为新连接,如果是新连接,则连接隧道服务器,否则转发消息if string(data) == "New Connection" {// 连接隧道服务器go messgaeForward()}}}// 连接隧道服务器进行消息转发func messgaeForward() {// 连接隧道服务器tunnelConn, err := helper.CreateConnect(constant.TunnelAddr)if err != nil {panic(err)}// 连接客户端服务clientConn, err := helper.CreateConnect(constant.AppPort)if err != nil {panic(err)}//消息转发//io.Copy()函数实现了数据的拷贝,可以将数据从一个接口拷贝到另一个接口,这里将客户端的数据拷贝到隧道服务器,将隧道服务器的数据拷贝到客户端//io.Copy()函数会一直阻塞,直到两个接口中的数据全部拷贝完成go io.Copy(clientConn, tunnelConn)go io.Copy(tunnelConn, clientConn)}
  1. main()函数:该函数是程序的入口点。它首先通过helper.CreateConnect函数连接到服务端,并打印连接地址。然后进入一个无限循环,从连接中读取数据。如果读取到的数据是"New Connection",则调用messgaeForward()函数连接隧道服务器进行消息转发。
  2. messgaeForward()函数:该函数用于连接隧道服务器并进行消息转发。它首先通过helper.CreateConnect函数分别连接到隧道服务器和客户端服务。然后使用io.Copy函数将客户端的数据拷贝到隧道服务器,将隧道服务器的数据拷贝到客户端。这样可以实现消息的双向转发。
    内网穿透的实现主要涉及到两个部分:客户端(Client)和服务端(Server)。下面我们将从代码角度出发,详细解释这两部分如何实现内网穿透。

客户端的主要任务是连接到服务端,并将内网服务的数据发送到服务端。这是通过以下关键步骤实现的:

  1. 连接到服务端:客户端首先需要连接到服务端。这是通过helper.CreateConnect(constant.ServerAddr)函数实现的,该函数创建一个到服务端的TCP连接。
  2. 读取服务端数据:客户端连接到服务端后,需要读取服务端发送的数据。这是通过helper.ReadMessage(serverConn)函数实现的,该函数从服务端连接中读取数据。
  3. 处理服务端数据:客户端读取到服务端数据后,需要根据数据内容进行相应的处理。如果读取到的数据是"New Connection",则需要连接到隧道服务器进行消息转发。
  4. 消息转发:消息转发是通过messgaeForward()函数实现的。该函数首先连接到隧道服务器和客户端服务,然后将客户端服务的数据拷贝到隧道服务器,同时将隧道服务器的数据拷贝到客户端服务。

4.服务端main.go

package mainimport ("Net-Penetration/constant""Net-Penetration/helper""io""log""net""sync")// serverConn 服务端连接var serverConn *net.TCPConn// appConn 目的服务端连接var appConn *net.TCPConn// wg 用于等待所有协程结束var wg sync.WaitGroup// 内网穿透服务端func main() {//监听服务端go serverListen()//监听目的服务端go appListen()//启动隧道服务go tunnelListen()//等待所有协程结束wg.Add(1)wg.Wait()}func serverListen() {//监听服务端,用于接收客户端连接tcpListener, err := helper.CreateListen(constant.ServerAddr)if err != nil {panic(err)}log.Printf("服务端监听地址为:%s\n", tcpListener.Addr().String())//接收客户端连接for {serverConn, err = tcpListener.AcceptTCP()if err != nil {log.Printf("接收连接失败,错误信息为:%s\n", err.Error())return}//保持连接go helper.KeepAlive(serverConn)}}// 监听隧道服务,用于接收隧道客户端连接,隧道客户端连接用于转发目的服务端和客户端之间的消息,实现内网穿透func tunnelListen() {tcpListener, err := helper.CreateListen(constant.TunnelAddr)if err != nil {panic(err)}log.Printf("隧道监听地址为:%s\n", tcpListener.Addr().String())for {tunnelConn, err := tcpListener.AcceptTCP()if err != nil {log.Printf("接收连接失败,错误信息为:%s\n", err.Error())return}// 数据转发go io.Copy(appConn, tunnelConn)go io.Copy(tunnelConn, appConn)}}// 监听目的服务端,用于接收目的服务端连接,目的服务端可以是本地转发的端口,也可以是远程服务器的端口func appListen() {//监听目的服务端tcpListener, err := helper.CreateListen(constant.AppTargetPort)if err != nil {panic(err)}log.Printf("应用目的服务端监端口地址为:%s\n", tcpListener.Addr().String())for {appConn, err = tcpListener.AcceptTCP()if err != nil {log.Printf("接收连接失败,错误信息为:%s\n", err.Error())return}_, err := serverConn.Write([]byte("New Connection"))if err != nil {log.Printf("发送消息失败,错误信息为:%s\n", err.Error())}}}
  1. main()函数:该函数是程序的入口点。它启动了三个协程,分别用于监听服务端、目的服务端和隧道服务端。然后使用sync.WaitGroup等待所有协程结束。
  2. serverListen()函数:该函数用于监听客户端连接。它首先通过helper.CreateListen函数创建一个TCP监听器,并打印监听地址。然后进入一个无限循环,接收客户端连接。每当有新的客户端连接时,将其保持活跃,并启动一个新的协程进行处理。
  3. appListen()函数:该函数用于监听目的服务端连接。它首先通过helper.CreateListen函数创建一个TCP监听器,并打印监听地址。然后进入一个无限循环,接收目的服务端连接。每当有新的目的服务端连接时,向客户端发送"New Connection"的消息。
  4. tunnelListen()函数:该函数用于监听隧道客户端连接。它首先通过helper.CreateListen函数创建一个TCP监听器,并打印监听地址。然后进入一个无限循环,接收隧道客户端连接。每当有新的隧道客户端连接时,启动两个新的协程,分别将数据从隧道客户端拷贝到目的服务端,以及将数据从目的服务端拷贝到隧道客户端。

服务端的主要任务是监听客户端连接、目的服务端连接和隧道客户端连接,并进行数据转发。这是通过以下关键步骤实现的:

  1. 监听客户端连接:服务端首先需要监听客户端连接。这是通过helper.CreateListen(constant.ServerListenAddr)函数实现的,该函数创建一个监听客户端连接的TCP监听器。
  2. 处理客户端连接:服务端接收到客户端连接后,需要保持连接。这是通过helper.KeepAlive(serverConn)函数实现的,该函数保持与客户端的连接。
  3. 监听目的服务端连接:服务端需要监听目的服务端连接。这是通过helper.CreateListen(constant.AppTargetPort)函数实现的,该函数创建一个监听目的服务端连接的TCP监听器。
  4. 处理目的服务端连接:服务端接收到目的服务端连接后,需要向客户端发送"New Connection"的消息。这是通过serverConn.Write([]byte("New Connection"))实现的。
  5. 监听隧道客户端连接:服务端需要监听隧道客户端连接。这是通过helper.CreateListen(constant.TunnelListenAddr)函数实现的,该函数创建一个监听隧道客户端连接的TCP监听器。
  6. 数据转发:服务端接收到隧道客户端连接后,需要进行数据转发。这是通过io.Copy(appConn, tunnelConn)io.Copy(tunnelConn, appConn)实现的,这两个函数将隧道客户端的数据拷贝到目的服务端,同时将目的服务端的数据拷贝到隧道客户端。

5.应用端测试main.go

package mainimport ("Net-Penetration/constant""encoding/json""log""net/http")// 本地应用,用于测试内网穿透func main() {http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {q := request.URL.Query()b, err := json.Marshal(q)if err != nil {log.Println(err)}writer.Write(b)})log.Printf("本地服务已启动:%s\n", constant.AppPort)http.ListenAndServe(constant.AppPort, nil)}

6.1 本地测试步骤:

首先运行服务端main.go
2. 运行客户端main.go
3. 运行应用端main.go
4. 此时可分别打开AppPort和AppTargetPort均可发现应用服务

在这里插入图片描述

6.2 公网ip测试步骤:以Linux云服务器为例

  1. 首先修改constant.go中的ServerIP为自己的公网ip
  2. 将服务端main.go打包,上传到Linux云服务器,并添加权限。windows下将go程序打包为linux可执行程序需要配置go编译环境,具体参考windows下将go程序打包为linux可执行程序教程
  3. 在服务器运行上传的main
  4. 在客户端运行客户端main.go
  5. 运行应用端main.go
  6. 此时可打开公网ip:AppTargetPort可发现内网的应用服务

来源地址:https://blog.csdn.net/qq_45808700/article/details/131417192

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯