├── .gitignore ├── Makefile ├── README.md ├── make.bat ├── screenshots └── architecture.png └── src └── punching ├── client.conf ├── client ├── config.go ├── frontend.go └── main.go ├── constant ├── pair.go └── proxy.go ├── logger └── logger.go ├── main ├── client │ └── client.go ├── proxy │ └── proxy.go └── server │ └── server.go ├── proxy.conf ├── proxy ├── config.go ├── main.go └── main_test.go ├── server.conf ├── server ├── backend.go ├── config.go ├── main.go └── proxy.go └── util ├── configuration.go ├── conv.go ├── net.go ├── netconn_darwin.go ├── netconn_linux.go ├── netconn_test.go ├── netconn_windows.go ├── package.go ├── rand.go └── rand_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | bin/ 3 | pkg/ 4 | src/code.google.com 5 | src/github.com 6 | src/bitbucket.org 7 | src/launchpad.net 8 | src/gopkg.in 9 | .idea/ 10 | *.test 11 | src/punching/log/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : default server client proxy all_windows all_darwin windows arm darwin deps fmt clean all 2 | export GOPATH:=$(shell pwd) 3 | 4 | PREFIX='' 5 | default: all 6 | 7 | GOOS= 8 | GOARCH= 9 | GOARM= 10 | 11 | fmt: 12 | go fmt punching/... 13 | 14 | deps: 15 | go get -d -v punching/... 16 | 17 | server: deps 18 | go install punching/main/server 19 | 20 | client: deps 21 | go install punching/main/client 22 | 23 | proxy: deps 24 | go install punching/main/proxy 25 | 26 | 27 | server_linux: deps 28 | GOOS=linux GOARCH=amd64 go install punching/main/server 29 | client_linux: deps 30 | GOOS=linux GOARCH=amd64 go install punching/main/client 31 | proxy_linux: deps 32 | GOOS=linux GOARCH=amd64 go install punching/main/proxy 33 | 34 | server_windows: deps 35 | GOOS=windows GOARCH=amd64 go install punching/main/server 36 | client_windows: deps 37 | GOOS=windows GOARCH=amd64 go install punching/main/client 38 | proxy_windows: deps 39 | GOOS=windows GOARCH=amd64 go install punching/main/proxy 40 | 41 | server_darwin: deps 42 | GOOS=darwin GOARCH=amd64 go install punching/main/server 43 | client_darwin: deps 44 | GOOS=darwin GOARCH=amd64 go install punching/main/client 45 | proxy_darwin: deps 46 | GOOS=darwin GOARCH=amd64 go install punching/main/proxy 47 | 48 | server_arm: deps 49 | GOOS=linux GOARCH=arm GOARM=5 go install punching/main/server 50 | client_arm: deps 51 | GOOS=linux GOARCH=arm GOARM=5 go install punching/main/client 52 | proxy_arm: deps 53 | GOOS=linux GOARCH=arm GOARM=5 go install punching/main/proxy 54 | 55 | 56 | all_darwin: fmt client_darwin server_darwin proxy_darwin 57 | all_linux: fmt client_linux server_linux proxy_linux 58 | all_windows: fmt client_windows server_windows proxy_windows 59 | all_arm: fmt client_arm server_arm proxy_arm 60 | all_platform: all_darwin all_linux all_windows all_arm 61 | all: fmt client server proxy 62 | 63 | clean: 64 | go clean -i -r punching/... 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 使用场景 2 | 3 | 有时,我们需要在家里访问公司的电脑,但如果任一方路由器分配的IP是运营商的内网IP,而不是公网IP,双方就无法直接通信。为了让双方电脑可以通讯,我们可以借助第三方提供的tunnel服务,利用服务器转发进行通讯,比较出名的是花生壳的内网穿透服务。本项目实现是的另外的解决思路,基于P2P NAT穿透,使位于NAT网络后的计算机直接通讯,无需借助第三方服务器进行数据转发,简单,高效和安全。 4 | 5 | ### 项目介绍 6 | 7 | ![构架图](https://github.com/chenboxing/punching/raw/bak/screenshots/architecture.png) 8 | 9 | 10 | 如上图所示,在公网上部署一台跨网服务器,该服务器运行解析端(proxy), P2P客户端和服务端启动时向跨网解析端提交TCP连接请求,以便公网解析端根据请求,记录各自自连接时NAT地址,并告知对方的NAT地址。P2P客户端和服务端尝试同时连接,进行NAT穿透。在穿透成功后,P2P终端可以脱离跨网解析端独立进行TCP数据通讯,无需第三方数据转发。 11 | 12 | ### 如何使用 13 | 14 | 1. 迁出源码 git clone https://github.com/chenboxing/punching.git 15 | 2. 进入项目目录 src/punching/ 16 | 3. 编译源码 17 | make all # 编译所在平台的所有端 18 | make_windows # 编译windows平台的所有端 19 | make_linux # 编译linux平台的所有端 20 | make_darwin # 编译mac os 平台的所有端 21 | make_arm # 编译arm嵌入式平台的所有端,arm版本基于5 22 | 23 | 编译后二进制文件放在 punching/bin/目录下 24 | 25 | 26 | 4. 跨网解析端和P2P端配置和使用 27 | 28 | 4.1 跨网解析端部署(如没有公网服务器,此步可跳过): 29 | 30 | 把proxy(代理转发端)和配置文件proxy.conf 部署到公网计算机上,配置proxy.conf配置节[proxy],设置侦听端口,默认7777 31 | 运行解析端 32 | ./proxy 33 | 34 | 4.2 配置P2P服务端和客户端 35 | 36 | [ThirdProxy] 37 | address = nat.move8.cn 38 | email = xxxx@xxxxx.com 39 | password = xxxxxxx 40 | 41 | 先在Nat网络一端,你需要开放访问服务的计算机上部署server端,配置config.conf配置节[server],在listen项里设置你要开放的应用服务,如 192.168.1.45:80, proxy添写你的代理转发端公网地址和端口,比如 xxx.f3322.net:7777, 如果你需要使用本站提供的代理转发,此项请为空,参考上面填写节[ThirdProxy]信息。 42 | 43 | 配置好,启动Server端: 44 | nat_server.exe 45 | 46 | 配置在Client端 47 | 在Nat网络的另一端,部署client端,先配置config.conf,在节[client],需要先设置好要侦听的端口信息,比如listen = :8585, proxy添写你的代理转发端公网地址和端口,比如 xxx.f3322.net:7777, 如果你需要使用本站提供的代理转发,此项请为空,参考上面填写节[ThirdProxy]信息。 48 | 49 | 配置好,启动Server端: 50 | nat_client.exe 51 | 52 | ### 当前局限 53 | 1. 只能提供一对一的P2P通讯(Client端,Server端),无法多人访问开启的P2P服务端服务.如果你想实现多人访问开启的服务,你可以使用tunnel(隧道)工具,但前提是需要架设一台线上服务器,部署tunnel服务端,在要开放服务的计算机上部署tunnel客户端。 54 | 2. 现在,项目只实现了TCP P2P穿透方案,如果后台服务需要UDP协议通讯 ,无法工作,比如vnc服务就无法访问,需要实现UDP和TCP穿透才可以工作。windows xp sp2下的平台也不支持P2P连接的TCP同时连接特性,在xp sp2平台上无法工作。 55 | 56 | 57 | ### 依赖的第三方包 58 | 1. github.com/cihub/seelog: 日志记录增加包 59 | 2. github.com/BurntSushi/toml: toml配置文件处理包 60 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | set GOPATH=%GOPATH%;%cd% 2 | go get -d -v punching/... 3 | go fmt punching/... 4 | go install punching/main/server 5 | go install punching/main/client 6 | go install punching/main/proxy 7 | -------------------------------------------------------------------------------- /screenshots/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenboxing/punching/1783785d75594053022d846146dfe124a4888050/screenshots/architecture.png -------------------------------------------------------------------------------- /src/punching/client.conf: -------------------------------------------------------------------------------- 1 | [client] 2 | proxy = "127.0.0.1:7777" 3 | listen = ":8585" 4 | key = "ABCD" 5 | 6 | [ThirdProxy] 7 | address = "proxy.move8.cn:7777" -------------------------------------------------------------------------------- /src/punching/client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "punching/util" 7 | ) 8 | 9 | /* 10 | [server] 11 | proxy = "" 12 | dial = "" 13 | key = "" 14 | 15 | [ThirdProxy] 16 | address = "proxy.move8.cn:7777" 17 | */ 18 | 19 | type ClientConfig struct { 20 | Proxy string `toml:"proxy"` // Proxy 服务的地址 21 | Listen string `toml:"listen"` // 服务端提供的服务地址 22 | Key string `toml:"key"` // 客户端和服务端的匹配码 23 | } 24 | 25 | type ThirdProxyConfig struct { 26 | Address string `toml:"address"` // Proxy 服务的地址 27 | } 28 | 29 | var Config ClientConfig 30 | var ThirdConfig ThirdProxyConfig 31 | 32 | func InitConfig() (err error) { 33 | 34 | // 加载配置信息 35 | fileName := "client.conf" 36 | 37 | if os.Getenv("CLIENT_CONF") != "" { 38 | fileName = os.Getenv("CLIENT_CONF") 39 | } 40 | 41 | sectionName1 := "client" 42 | if err01 := util.DecodeSection(fileName, sectionName1, &Config); err01 != nil { 43 | err = fmt.Errorf("Load config file failed, error:%s", err01.Error()) 44 | return 45 | } 46 | 47 | sectionName2 := "ThirdProxy" 48 | if err02 := util.DecodeSection(fileName, sectionName2, &ThirdConfig); err != nil { 49 | err = fmt.Errorf("Load config file failed, error:%s", err02.Error()) 50 | return 51 | } 52 | 53 | if Config.Key == "" { 54 | err = fmt.Errorf("匹配码不能为空,请在client.conf配置匹配码") 55 | return 56 | } 57 | 58 | if Config.Proxy == "" && ThirdConfig.Address == "" { 59 | err = fmt.Errorf("Proxy服务地址和第三方Proxy服务地址不能同时为空") 60 | return 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /src/punching/client/frontend.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net" 5 | . "punching/constant" 6 | "punching/logger" 7 | "punching/util" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var ListenAcceptMap map[string]net.Conn 13 | var ExitChanMap map[string]chan bool 14 | 15 | var RWLock *sync.RWMutex 16 | 17 | func handleClientConn(source net.Conn) { 18 | 19 | // 4 bits unique session id 20 | var sessionID string 21 | // Unique session id 22 | for { 23 | RWLock.Lock() 24 | sessionID = util.GenerateRandomPairKey() 25 | _, ok := ListenAcceptMap[sessionID] 26 | RWLock.Unlock() 27 | if !ok { 28 | break 29 | } 30 | } 31 | logger.Infof("Enter handleClientConn:%s", sessionID) 32 | 33 | RWLock.Lock() 34 | ListenAcceptMap[sessionID] = source 35 | ExitChanMap[sessionID] = make(chan bool) 36 | RWLock.Unlock() 37 | logger.Infof("建立Map,%s", sessionID) 38 | 39 | defer func() { 40 | 41 | e1 := source.Close() 42 | if e1 != nil { 43 | logger.Error("关闭Sourcer失败") 44 | } 45 | RWLock.Lock() 46 | delete(ListenAcceptMap, sessionID) 47 | delete(ExitChanMap, sessionID) 48 | logger.Infof("删除map:%s", sessionID) 49 | RWLock.Unlock() 50 | 51 | }() 52 | 53 | go func() { 54 | 55 | buf := make([]byte, 1024) 56 | var flag int 57 | 58 | for { 59 | 60 | len01, err := source.Read(buf) 61 | 62 | if len01 <= 0 || err != nil { 63 | logger.Errorf("读取Source源连接出错,原因为:%s", err) 64 | 65 | //发送控制 66 | packQuit := util.PackageNat(PAIR_CONTROL_QUIT, sessionID, []byte("")) 67 | Wch <- packQuit 68 | return 69 | } 70 | 71 | controlID := PAIR_CONTROL_NORMAL 72 | if flag == 0 { 73 | // 第一次 74 | controlID = PAIR_CONTROL_FIRST 75 | flag = 1 76 | } 77 | 78 | pack := util.PackageNat(controlID, sessionID, buf[0:len01]) 79 | Wch <- pack 80 | 81 | } 82 | 83 | }() 84 | 85 | select { 86 | case <-ExitChanMap[sessionID]: 87 | logger.Warn("需要退出Accept") 88 | return 89 | } 90 | } 91 | 92 | // 侦听端口,将连接转到natConn 93 | func ClientListenHandle() { 94 | 95 | ListenAcceptMap = make(map[string]net.Conn) 96 | ExitChanMap = make(map[string]chan bool) 97 | RWLock = new(sync.RWMutex) 98 | 99 | addrOn := Config.Listen 100 | 101 | l, err := net.Listen("tcp", addrOn) 102 | if err != nil { 103 | logger.Errorf("listen %s encountered errors %s", addrOn, err) 104 | return 105 | } 106 | logger.Infof("server running at port %s", addrOn) 107 | 108 | // 全局读取来自nat源的包 109 | go handleReadConn() 110 | 111 | go func() { 112 | for { 113 | c, err := l.Accept() 114 | if err != nil { 115 | logger.Errorf("accept error: %s", err) 116 | break 117 | } 118 | go handleClientConn(c) 119 | } 120 | }() 121 | } 122 | 123 | // 读取目标流到源 124 | func handleReadConn() { 125 | for { 126 | select { 127 | case pact := <-Rch: 128 | 129 | // 获取src 130 | controlID := pact.ControlID 131 | sessionID := string(pact.SessionID) 132 | data := pact.Data 133 | 134 | //退出 135 | if controlID == PAIR_CONTROL_QUIT { 136 | if c, ok := ExitChanMap[sessionID]; ok { 137 | logger.Info("发送退出信号") 138 | c <- true 139 | } else { 140 | logger.Info("在ExitChanMap里找不到Key为:", sessionID) 141 | } 142 | } else { 143 | if src, ok := ListenAcceptMap[sessionID]; ok { 144 | len2, err2 := src.Write(data) 145 | if err2 != nil || len2 <= 0 { 146 | logger.Infof("源写入出错:%s", err2) 147 | } else { 148 | logger.Info(time.Now().UnixNano(), "源写入:", len2) 149 | } 150 | 151 | } else { 152 | logger.Info("在Map里找不到Key为:", sessionID) 153 | } 154 | 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/punching/client/main.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "os" 5 | "punching/logger" 6 | "punching/util" 7 | "time" 8 | ) 9 | 10 | var ( 11 | Dch chan bool 12 | Rch chan util.PairPackage 13 | Wch chan []byte 14 | ) 15 | 16 | func Main() { 17 | 18 | // 加载配置信息 19 | if err := InitConfig(); err != nil { 20 | logger.Errorf("加载配置信息出错,原因为:%s", err) 21 | return 22 | } 23 | 24 | proxyAddr := Config.Proxy 25 | if proxyAddr == "" { 26 | proxyAddr = ThirdConfig.Address 27 | } 28 | pairName := Config.Key 29 | 30 | logger.Infof("准备连接代理解析端:%s", proxyAddr) 31 | 32 | tryCount := 0 33 | var connPeer util.NetConn 34 | var errPeer error 35 | 36 | for { 37 | 38 | localAddr, destAddr, err := util.ClientDialProxy(proxyAddr, pairName) 39 | 40 | if err != nil { 41 | logger.Errorf("连接解析端出错,%s", err) 42 | return 43 | } 44 | 45 | logger.Infof("已获取NAT地址:本地地址:%s,远程地址:%s ", localAddr, destAddr) 46 | 47 | tryCount += 1 48 | 49 | if tryCount == 11 { 50 | logger.Errorf("已经尝试了10次,连接还是失败,退出,请重新运行客户端") 51 | return 52 | } 53 | //连接P2P服务端 54 | connPeer, errPeer = util.DialPeer(localAddr, destAddr) 55 | if errPeer != nil { //无法连接上 56 | logger.Errorf("连接P2P服务端,出现错误,%s,第%d次", errPeer, tryCount) 57 | time.Sleep(3 * time.Second) 58 | continue 59 | } else { 60 | break 61 | } 62 | 63 | } 64 | 65 | Dch = make(chan bool) 66 | Rch = make(chan util.PairPackage) 67 | Wch = make(chan []byte) 68 | 69 | go RHandler(connPeer) //Nat端写通道 70 | go WHandler(connPeer) //Nat端读通道 71 | 72 | // 侦听端口,开启服务,将端口输入转发到P2P端 73 | ClientListenHandle() 74 | 75 | // 如果P2P端通讯出错,退出 76 | select { 77 | case <-Dch: 78 | logger.Errorf("接收到退出信息") 79 | os.Exit(1) 80 | } 81 | } 82 | 83 | func RHandler(conn util.NetConn) { 84 | 85 | //声明一个临时缓冲区,用来存储被截断的数据 86 | tmpBuffer := make([]byte, 0) 87 | 88 | buff := make([]byte, 1024) 89 | for { 90 | j, err := conn.Read(buff) 91 | if err != nil { 92 | logger.Errorf("读取连接数据出错,原因为:%s", err) 93 | Dch <- true 94 | break 95 | } 96 | logger.Info("准备解包数据:", j) 97 | // 解包 98 | tmpBuffer = util.UnpackageNat(append(tmpBuffer, buff[:j]...), Rch) 99 | } 100 | } 101 | 102 | func WHandler(conn util.NetConn) { 103 | for { 104 | select { 105 | case msg := <-Wch: 106 | l, err := conn.Write(msg) 107 | if err != nil { 108 | logger.Errorf("写到Nat目录连接出错:%s", err) 109 | Dch <- true 110 | } else { 111 | logger.Info(time.Now().UnixNano(), "已写入到Nat:", l) 112 | } 113 | // } 114 | 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/punching/constant/pair.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | // Constant for the client and server 4 | 5 | const ( 6 | PAIR_CONTROL_FIRST byte = 11 // 控制码 C->S第一个包 7 | PAIR_CONTROL_QUIT byte = 10 // 控制码 退出 8 | PAIR_CONTROL_NORMAL byte = 0 // 控制码 9 | 10 | PAIR_PACKAGE_HEAD_LENGTH = 6 // C<->S 自定义包头长度 11 | PAIR_PACKAGE_CONTROL_LENGTH = 1 // 包控制码长度 12 | PAIR_PACKAGE_SESSIONID_LENGTH = 4 // 包会话ID长度 13 | PAIR_PACKAGE_DATA_LENGTH = 4 // 包数据长度 14 | PAIR_PACKAGE_PREFIX_LENGTH = 15 // head[6] + control[1] + sessionid[4] + data length[4] 15 | 16 | ) 17 | 18 | const ( 19 | ROLE_SERVER byte = 1 // 点对点服务端 20 | ROLE_CLIENT byte = 2 // 点对点客户端 21 | ) 22 | 23 | var ( 24 | PAIR_PACKAGE_HEAD string = "CBXNAT" // C<->S 自定义包头 25 | 26 | ) 27 | -------------------------------------------------------------------------------- /src/punching/constant/proxy.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | PROXY_PACKAGE_HEAD byte = 'H' // C<->S 自定义包头 5 | PROXY_CONTROL_FIRST byte = 11 // 控制ID 第一个数据包 6 | PROXY_CONTROL_NORMAL byte = 0 // 控制码 正常发送 7 | PROXY_CONTROL_ACK byte = 12 // 控制码 确认 8 | PROXY_CONTROL_QUIT byte = 10 // 控制码 退出 9 | PROXY_CONTROL_HEARTBIT byte = 13 // 控制码 心跳包 10 | PROXY_CONTROL_HEARTBITACK byte = 14 // 心跳包确认 11 | PROXY_CONTROL_ERROR_NO_SERVER byte = 201 // 服务端还没有注册 12 | PROXY_CONTROL_ERROR_CLIENT_EXIST byte = 202 // 客户端已经存在 13 | PROXY_CONTROL_ERROR_SERVER_EXIST byte = 203 // 服务端已经存在 14 | 15 | ) 16 | -------------------------------------------------------------------------------- /src/punching/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cihub/seelog" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // init 初始化包 12 | func init() { 13 | // 解析服务配置文件 14 | logFileName := "./log/punching.log" 15 | if os.Getenv("Log_FILE") != "" { 16 | logFileName = os.Getenv("Log_FILE") 17 | } 18 | xml := ` 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ` 32 | 33 | xml = strings.Replace(xml, "#LOG_FILE_NAME", logFileName, 1) 34 | // 解析日志配置(从默认配置) 35 | logg, err := seelog.LoggerFromConfigAsBytes([]byte(xml)) 36 | if err != nil { 37 | panic(fmt.Errorf("log configuration parse error: %s", err.Error())) 38 | } 39 | seelog.ReplaceLogger(logg) 40 | 41 | } 42 | 43 | var ( 44 | // Tracef 写一条格式化的日志信息。级别等于 Trace 45 | Tracef = seelog.Tracef 46 | // Trace 写一条日志信息。级别等于 Trace 47 | Trace = seelog.Trace 48 | 49 | // Debugf 写一条格式化的日志信息。级别等于 Debug 50 | Debugf = seelog.Debugf 51 | 52 | // Debug 写一条日志信息。级别等于 Debug 53 | Debug = seelog.Debug 54 | 55 | // Infof 写一条格式化的日志信息。级别等于 Info 56 | Infof = seelog.Infof 57 | 58 | // Info 写一条日志信息。级别等于 Info 59 | Info = seelog.Info 60 | 61 | // Warnf 写一条格式化的日志信息。级别等于 Warn 62 | Warnf = seelog.Warnf 63 | 64 | // Warn 写一条日志信息。级别等于 Warn 65 | Warn = seelog.Warn 66 | 67 | // Errorf 写一条格式化的日志信息。级别等于 Error 68 | Errorf = seelog.Errorf 69 | 70 | // Error 写一条日志信息。级别等于 Error 71 | Error = seelog.Error 72 | 73 | // Criticalf 写一条格式化的日志信息。级别等于 Critical 74 | Criticalf = seelog.Criticalf 75 | 76 | // Critical 写一条日志信息。级别等于 Critical 77 | Critical = seelog.Critical 78 | ) 79 | 80 | // Flush 将所有日志信息写入缓存 81 | func Flush() { 82 | seelog.Flush() 83 | } 84 | -------------------------------------------------------------------------------- /src/punching/main/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "punching/client" 4 | 5 | func main() { 6 | client.Main() 7 | } 8 | -------------------------------------------------------------------------------- /src/punching/main/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "punching/proxy" 4 | 5 | func main() { 6 | proxy.Main() 7 | } 8 | -------------------------------------------------------------------------------- /src/punching/main/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "punching/server" 4 | 5 | func main() { 6 | server.Main() 7 | } 8 | -------------------------------------------------------------------------------- /src/punching/proxy.conf: -------------------------------------------------------------------------------- 1 | [proxy] 2 | listen = ":7777" -------------------------------------------------------------------------------- /src/punching/proxy/config.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "punching/util" 7 | ) 8 | 9 | type ProxyConfig struct { 10 | Listen string `toml:"listen"` // Proxy 服务的地址 11 | } 12 | 13 | var Config ProxyConfig 14 | 15 | func InitConfig() (err error) { 16 | 17 | // 加载配置信息 18 | // fileName := "/Users/chenboxing/nat/src/punching/src/punching/proxy.conf" 19 | fileName := "proxy.conf" 20 | if os.Getenv("PROXY_CONF") != "" { 21 | fileName = os.Getenv("PROXY_CONF") 22 | } 23 | sectionName := "proxy" 24 | if err01 := util.DecodeSection(fileName, sectionName, &Config); err01 != nil { 25 | err = fmt.Errorf("Load config file failed, error:%s", err01.Error()) 26 | return 27 | } 28 | 29 | if Config.Listen == "" { 30 | err = fmt.Errorf("侦听地址为空,请在配置文件proxy.conf配置listen值") 31 | return 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /src/punching/proxy/main.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | 6 | "net" 7 | . "punching/constant" 8 | "punching/logger" 9 | "punching/util" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // ServerConn 服务端到代理端连接 15 | type ServerConn struct { 16 | Rch chan []byte // 读通道 17 | Wch chan []byte // 写通道 18 | Dch chan bool // 连接退 19 | LocalAddr string // 客户端IP信息 20 | Pairname string // 匹配名称 21 | SyncAt int64 // 上次心跳时间 22 | } 23 | 24 | // ClientConn 客户端到代理端连接 25 | type ClientConn struct { 26 | Pairname string //匹配码 27 | } 28 | 29 | // 全局变量 30 | var ( 31 | OnlineServerList map[string]*ServerConn // 服务端连接列表Map 32 | OnlineClientList map[string]string // 客户端连接列表Map 33 | RWLockClient *sync.RWMutex //读写锁 34 | RWLockServer *sync.RWMutex 35 | ) 36 | 37 | func Main() { 38 | 39 | // 加载配置信息 40 | if err := InitConfig(); err != nil { 41 | logger.Errorf("加载配置信息出错,原因为:%s", err) 42 | return 43 | } 44 | 45 | OnlineServerList = make(map[string]*ServerConn) 46 | OnlineClientList = make(map[string]string) 47 | 48 | RWLockClient = new(sync.RWMutex) 49 | RWLockServer = new(sync.RWMutex) 50 | 51 | listenAddr := Config.Listen 52 | tcpAddr, err := net.ResolveTCPAddr("tcp", listenAddr) 53 | if err != nil { 54 | logger.Errorf("Resolved address failed %s", err) 55 | panic(err) 56 | } 57 | 58 | listen, err := net.ListenTCP("tcp", tcpAddr) 59 | 60 | if err != nil { 61 | logger.Errorf("监听端口失败:%s", err) 62 | return 63 | } 64 | logger.Infof("已初始化连接,正在侦听:%s, 等待客户端连接...", listenAddr) 65 | 66 | for { 67 | conn, err := listen.AcceptTCP() 68 | if err != nil { 69 | fmt.Println("连接异常:", err.Error()) 70 | continue 71 | } 72 | logger.Infof("本地地址:%s,来自远程地址:%s", conn.LocalAddr().String(), conn.RemoteAddr().String()) 73 | go Handler(conn) 74 | } 75 | } 76 | 77 | // processRoleClient 处理客户端连接 78 | func processRoleClient(conn net.Conn, pairName string) { 79 | 80 | // 判断匹配的服务端是否已经注册 81 | RWLockServer.RLock() 82 | 83 | serverConn, ok := OnlineServerList[pairName] 84 | 85 | RWLockServer.RUnlock() 86 | 87 | if !ok { 88 | // 客户端没有注册 89 | packErr := util.PackageProxy(PROXY_CONTROL_ERROR_NO_SERVER, []byte("")) 90 | conn.Write(packErr) 91 | return 92 | } 93 | 94 | // Check the client with the save pair name exists 95 | RWLockClient.RLock() 96 | _, ok = OnlineClientList[pairName] 97 | RWLockClient.RUnlock() 98 | 99 | if ok { 100 | packErr := util.PackageProxy(PROXY_CONTROL_ERROR_CLIENT_EXIST, []byte("")) 101 | conn.Write(packErr) 102 | return 103 | } 104 | 105 | // 添加到客户端列表 106 | RWLockClient.Lock() 107 | OnlineClientList[pairName] = pairName 108 | RWLockClient.Unlock() 109 | 110 | // 发送Nat地址和接收确认 111 | toClientAddrs := serverConn.LocalAddr + "," + conn.RemoteAddr().String() 112 | 113 | pack := util.PackageProxy(PROXY_CONTROL_NORMAL, []byte(toClientAddrs)) 114 | conn.Write(pack) 115 | 116 | //buf := make([]byte, 512) 117 | //lenAck, err := conn.Read(buf) 118 | // 119 | //if err != nil { 120 | // logger.Errorf("读客户端确认数据出错,%s", err) 121 | // return 122 | //} 123 | // 124 | //ackPack, err01 := util.UnpackageProxy(buf[0:lenAck]) 125 | //if err01 != nil { 126 | // logger.Errorf("包解析出问题") 127 | // return 128 | //} 129 | //flag := 0 130 | //if ackPack.ControlID == PROXY_CONTROL_ACK { 131 | // flag += 1 132 | //} 133 | 134 | toServerAddrs := conn.RemoteAddr().String() + "," + serverConn.LocalAddr 135 | addrPack := util.PackageProxy(PROXY_CONTROL_NORMAL, []byte(toServerAddrs)) 136 | 137 | serverConn.Wch <- addrPack 138 | 139 | //// 等待服务端的确认数据 140 | //select { 141 | //case bufAck := <-serverConn.Rch: 142 | // pack, err02 := util.UnpackageProxy(bufAck) 143 | // if err02 != nil { 144 | // if pack.ControlID == PROXY_CONTROL_ACK { 145 | // flag += 1 146 | // } 147 | // } 148 | // break 149 | //} 150 | // 151 | //logger.Infof("当前的连接信息为: %d", flag) 152 | 153 | // 收到服务端的确认数据 154 | // if flag == 2 { 155 | RWLockServer.Lock() 156 | //serverConn.Dch <- true // 关闭服务端连接 157 | delete(OnlineServerList, pairName) 158 | RWLockServer.Unlock() 159 | // } 160 | 161 | RWLockClient.Lock() 162 | delete(OnlineClientList, pairName) 163 | RWLockClient.Unlock() 164 | 165 | return 166 | 167 | } 168 | 169 | // Handle 连接处理函数 170 | func Handler(conn net.Conn) { 171 | 172 | //defer func() { 173 | // if err := recover(); err != nil { 174 | // logger.Errorf("连接出现问题:%s", err) 175 | // } 176 | //}() 177 | 178 | defer conn.Close() 179 | buf := make([]byte, 1024) 180 | 181 | var pairName string 182 | 183 | for { 184 | 185 | i, err := conn.Read(buf) 186 | if err != nil { 187 | logger.Errorf("读取数据错误:%s", err) 188 | return 189 | } 190 | 191 | firstPack, err01 := util.UnpackageProxy(buf[0:i]) 192 | 193 | if err01 != nil { 194 | logger.Errorf("包格式出错,%s", err01) 195 | return 196 | } 197 | 198 | // Todo 获取时间差距 服务器时间 ticks - 客户端时间 ticks 199 | // 比如说3秒后 200 | 201 | clientType := firstPack.Data[0] 202 | 203 | if len(firstPack.Data) > 1 { 204 | pairName = string(firstPack.Data[1:]) 205 | } 206 | 207 | // 处理客户端连接 208 | if clientType == ROLE_CLIENT { 209 | processRoleClient(conn, pairName) 210 | return // 退出客户端连接 211 | } 212 | break 213 | } 214 | 215 | // 下面的操作都是针对服务端连接 216 | logger.Info("处理P2P服务端连接") 217 | 218 | // 服务端连接允许匹配码为空,系统将随机产生唯一匹配码 219 | if pairName == "" { 220 | for { 221 | pairName = util.GenerateRandomPairKey() 222 | RWLockServer.Lock() 223 | if _, ok := OnlineServerList[pairName]; !ok { 224 | break 225 | } 226 | RWLockServer.Unlock() 227 | } 228 | } else { 229 | 230 | // 是否存在pair name 231 | RWLockServer.RLock() 232 | _, ok := OnlineServerList[pairName] 233 | RWLockServer.RUnlock() 234 | 235 | // 已存在 236 | if ok { 237 | errPack := util.PackageProxy(PROXY_CONTROL_ERROR_SERVER_EXIST, []byte("")) 238 | conn.Write(errPack) 239 | logger.Errorf("服务端列表中已存在:%s", pairName) 240 | return 241 | } 242 | 243 | } 244 | 245 | logger.Infof("匹配码为:%s", pairName) 246 | 247 | // 生成服务器连接对象添加到列表 248 | RWLockServer.Lock() 249 | serverConn := &ServerConn{Rch: make(chan []byte), 250 | Wch: make(chan []byte), 251 | Dch: make(chan bool), 252 | Pairname: pairName, 253 | LocalAddr: conn.RemoteAddr().String()} 254 | OnlineServerList[pairName] = serverConn 255 | RWLockServer.Unlock() 256 | 257 | replyData := conn.RemoteAddr().String() + "," + pairName 258 | replyPack := util.PackageProxy(PROXY_CONTROL_NORMAL, []byte(replyData)) 259 | if _, err := conn.Write(replyPack); err != nil { 260 | logger.Errorf("回复P2P服务端包出错", err) 261 | return 262 | } 263 | 264 | // 写通道 265 | go WHandler(conn, serverConn) 266 | 267 | // 读通道 268 | go RHandler(conn, serverConn) 269 | 270 | // 等待退出通道 271 | select { 272 | case <-serverConn.Dch: 273 | logger.Info("close handler goroutine") 274 | } 275 | } 276 | 277 | // 正常写数据 匹配端连接上来会写信息 278 | // 定时检测 conn die => goroutine die 279 | func WHandler(conn net.Conn, C *ServerConn) { 280 | 281 | for { 282 | select { 283 | case d := <-C.Wch: 284 | logger.Info("通道接收到数据,准备写") 285 | conn.Write(d) 286 | } 287 | } 288 | } 289 | 290 | // 读客户端数据 + 心跳检测 291 | func RHandler(conn net.Conn, C *ServerConn) { 292 | 293 | // 心跳ack 294 | // 业务数据 写入Wch 295 | 296 | for { 297 | data := make([]byte, 128) 298 | // 设置读超时 299 | err := conn.SetReadDeadline(time.Now().Add(2 * time.Second)) 300 | if err != nil { 301 | logger.Errorf("设置读超时失败,%s", err) 302 | } 303 | if i, derr := conn.Read(data); derr == nil { 304 | // 可能是来自客户端的消息确认 305 | // 数据消息 306 | pack, err01 := util.UnpackageProxy(data[0:i]) 307 | if err01 != nil { 308 | logger.Errorf("包无法解析,%s", err01) 309 | continue 310 | } 311 | if pack.ControlID == PROXY_CONTROL_HEARTBITACK { 312 | logger.Info("Received hartbeat ack") //// C.Rch <- data 313 | } 314 | 315 | continue 316 | } 317 | 318 | //如果等待10秒没有读到客户端数据或读写出错,写心跳包 319 | // 写心跳包 320 | heartPack := util.PackageProxy(PROXY_CONTROL_HEARTBIT, []byte("")) 321 | conn.Write(heartPack) 322 | conn.SetReadDeadline(time.Now().Add(2 * time.Second)) 323 | if _, herr := conn.Read(data); herr == nil { 324 | 325 | // fmt.Println(string(data)) 326 | // 更新心跳时间 327 | RWLockServer.RLock() 328 | serverConn, ok := OnlineServerList[C.Pairname] 329 | if ok { 330 | serverConn.SyncAt = time.Now().Unix() 331 | } 332 | RWLockServer.RUnlock() 333 | 334 | } else { 335 | logger.Errorf("读取连接出错,%s", herr) 336 | RWLockServer.Lock() 337 | delete(OnlineServerList, C.Pairname) 338 | RWLockServer.Unlock() 339 | logger.Infof("删除在线P2P服务端,%s", C.Pairname) 340 | return 341 | } 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/punching/proxy/main_test.go: -------------------------------------------------------------------------------- 1 | package proxy_test 2 | 3 | import ( 4 | "net" 5 | "punching/client" 6 | 7 | "io" 8 | // "io/ioutil" 9 | "net/http" 10 | "os" 11 | "punching/logger" 12 | "punching/proxy" 13 | "punching/server" 14 | "testing" 15 | "time" 16 | ) 17 | 18 | // var WG sync.WaitGroup 19 | 20 | const ( 21 | RENDER_FILE_PATH = "/Users/chenboxing/nat/src/punching/index.html" 22 | ) 23 | 24 | func runHttpWeb(addr string) { 25 | 26 | //第一个参数为客户端发起http请求时的接口名,第二个参数是一个func,负责处理这个请求。 27 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 28 | 29 | f, _ := os.Open(RENDER_FILE_PATH) 30 | defer f.Close() 31 | //读取页面内容 32 | io.Copy(w, f) 33 | }) 34 | 35 | //服务器要监听的主机地址和端口号 36 | err := http.ListenAndServe(addr, nil) 37 | 38 | if err != nil { 39 | logger.Errorf("ListenAndServe error: ", err.Error()) 40 | } else { 41 | logger.Infof("开启服务在:%s", addr) 42 | } 43 | 44 | } 45 | 46 | func TestNat(t *testing.T) { 47 | 48 | // 开启Proxy服务 49 | go func() { 50 | proxy.Main() 51 | }() 52 | 53 | for { 54 | time.Sleep(2 * time.Second) 55 | conn, err := net.Dial("tcp", proxy.Config.Listen) 56 | if err != nil { 57 | time.Sleep(2 * time.Second) 58 | } else { 59 | conn.Close() 60 | break 61 | } 62 | } 63 | 64 | // Server连接 65 | go func() { 66 | server.Main() 67 | }() 68 | 69 | server.InitConfig() 70 | pairName := server.Config.Key 71 | 72 | logger.Infof("Pairname is :%s, %+v", pairName, server.Config) 73 | // Check if the P2P server is available 74 | for { 75 | logger.Info(len(proxy.OnlineServerList)) 76 | if _, ok := proxy.OnlineServerList[pairName]; !ok { 77 | time.Sleep(2 * time.Second) 78 | continue 79 | } 80 | break 81 | } 82 | 83 | logger.Info("准备开启客户端") 84 | 85 | go func() { 86 | client.Main() 87 | }() 88 | 89 | //client.InitConfig() 90 | //for { 91 | // time.Sleep(2 * time.Second) 92 | // logger.Infof("准备连接:%s", client.Config.Listen) 93 | // conn, err := net.Dial("tcp", client.Config.Listen) 94 | // if err != nil { 95 | // time.Sleep(2 * time.Second) 96 | // } else { 97 | // conn.Close() 98 | // break 99 | // } 100 | //} 101 | // 102 | //// 启用Web后台服务 103 | //server.Config.Dial = ":7779" 104 | // 105 | //logger.Infof("开启后台服务:%s",server.Config.Dial) 106 | ////开启服务 107 | //go runHttpWeb(server.Config.Dial) 108 | // 109 | //url := "http://" + client.Config.Listen 110 | //logger.Infof("获取网页内容:") 111 | // 112 | //if resp, err := http.Get(url); err != nil { 113 | // t.Errorf("读取配置端口出错,%s", client.Config.Listen) 114 | //} else { 115 | // defer resp.Body.Close() 116 | // arrContent, _ := ioutil.ReadAll(resp.Body) 117 | // 118 | // if all, err := ioutil.ReadFile(RENDER_FILE_PATH); err != nil { 119 | // t.Errorf("读取文件出现错误, %s", err) 120 | // } else { 121 | // if len(all) != len(arrContent) { 122 | // t.Errorf("文件大小不一致:%d,%d", len(all), len(arrContent)) 123 | // } 124 | // } 125 | // 126 | //} 127 | 128 | select {} 129 | } 130 | -------------------------------------------------------------------------------- /src/punching/server.conf: -------------------------------------------------------------------------------- 1 | [server] 2 | proxy = "127.0.0.1:7777" 3 | dial = "192.168.3.19:3389" 4 | key = "ABCD" 5 | 6 | [ThirdProxy] 7 | address = "proxy.move8.cn:7777" 8 | 9 | -------------------------------------------------------------------------------- /src/punching/server/backend.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net" 5 | "os" 6 | . "punching/constant" 7 | "punching/logger" 8 | "punching/util" 9 | "sync" 10 | ) 11 | 12 | var ExitChanMap map[string]chan bool 13 | var RWLock *sync.RWMutex 14 | var DialTargetMap map[string]net.Conn 15 | 16 | func handleServerConn() { 17 | 18 | DialTargetMap = make(map[string]net.Conn) 19 | ExitChanMap = make(map[string]chan bool) 20 | RWLock = new(sync.RWMutex) 21 | 22 | var targetAddr = Config.Dial 23 | for { 24 | select { 25 | case pack := <-Rch: 26 | 27 | //确定target是否存在,如果不存在,重新生成target 28 | 29 | controlID := pack.ControlID 30 | sessionID := pack.SessionID 31 | data := pack.Data 32 | 33 | //log.Println("读取Nat接收包:handleReadConn", string(r[0:34]), "长度为", len(r)) 34 | 35 | if controlID == PAIR_CONTROL_QUIT { 36 | 37 | RWLock.RLock() 38 | 39 | if c, ok := ExitChanMap[sessionID]; ok { 40 | logger.Info("发送退出信号") 41 | c <- true 42 | } else { 43 | logger.Errorf("在ExitChanMap里找不到Key为:%s", sessionID) 44 | } 45 | RWLock.RUnlock() 46 | break 47 | } 48 | 49 | //第一次 50 | if controlID == PAIR_CONTROL_FIRST { 51 | logger.Info("准备连接:", targetAddr) 52 | target, err := net.Dial("tcp", targetAddr) 53 | if err != nil { 54 | logger.Errorf("连接目标出错:%s", targetAddr) 55 | break 56 | } 57 | 58 | ExitChanMap[sessionID] = make(chan bool) 59 | DialTargetMap[sessionID] = target 60 | 61 | _, err2 := target.Write(pack.Data) 62 | if err2 != nil { 63 | logger.Errorf("连接成功后写目标出错,%s", err2) 64 | break 65 | } 66 | go ReadFromTarget(target, sessionID) 67 | } else { 68 | 69 | if dialtarget, ok := DialTargetMap[sessionID]; ok { 70 | 71 | len2, err2 := dialtarget.Write(data) 72 | logger.Info("已写入:", len2) 73 | if err2 != nil { 74 | logger.Errorf("写目标:%s,出错:%s", targetAddr, err2) 75 | 76 | //发送控制 77 | quitPack := util.PackageNat(PAIR_CONTROL_QUIT, sessionID, []byte("")) 78 | Wch <- quitPack 79 | 80 | break 81 | } 82 | 83 | } else { 84 | logger.Errorf("找不到目标Dial:%s", sessionID) 85 | } 86 | 87 | } 88 | 89 | case <-Dch: 90 | //出错 91 | logger.Warn("收到退出信息") 92 | os.Exit(1) 93 | } 94 | } 95 | } 96 | 97 | // 读取目标流到源 98 | func ReadFromTarget(target net.Conn, sessionID string) { 99 | 100 | defer func() { 101 | target.Close() 102 | RWLock.Lock() 103 | delete(DialTargetMap, sessionID) 104 | delete(ExitChanMap, sessionID) 105 | RWLock.Unlock() 106 | }() 107 | 108 | go func() { 109 | //buf := make([]byte, 512-34) 110 | buf := make([]byte, 1024) 111 | for { 112 | 113 | j, err := target.Read(buf) 114 | 115 | if err != nil || j == 0 { 116 | logger.Errorf("读取目标连接数据出错,原因为:%s", err) 117 | 118 | pack := util.PackageNat(PAIR_CONTROL_QUIT, sessionID, []byte("")) 119 | Wch <- pack 120 | return 121 | } 122 | logger.Info("准备构造数据") 123 | pack := util.PackageNat(PAIR_CONTROL_NORMAL, sessionID, buf[0:j]) 124 | 125 | Wch <- pack 126 | 127 | } 128 | }() 129 | 130 | //接受到退出标识 131 | select { 132 | case <-ExitChanMap[sessionID]: 133 | logger.Warn("需要退出Accept") 134 | return 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/punching/server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "punching/util" 7 | ) 8 | 9 | /* 10 | [server] 11 | proxy = "" 12 | dial = "" 13 | key = "" 14 | 15 | [ThirdProxy] 16 | address = "proxy.move8.cn:7777" 17 | */ 18 | 19 | type ServerConfig struct { 20 | Proxy string `toml:"proxy"` // Proxy 服务的地址 21 | Dial string `toml:"dial"` // 服务端提供的服务地址 22 | Key string `toml:"key"` // 客户端和服务端的匹配码 23 | } 24 | 25 | type ThirdProxyConfig struct { 26 | Address string `toml:"address"` // Proxy 服务的地址 27 | } 28 | 29 | var Config ServerConfig 30 | var ThirdConfig ThirdProxyConfig 31 | 32 | func InitConfig() (err error) { 33 | 34 | // 加载配置信息 35 | fileName := "server.conf" 36 | if os.Getenv("SERVER_CONF") != "" { 37 | fileName = os.Getenv("SERVER_CONF") 38 | } 39 | sectionName1 := "server" 40 | if err01 := util.DecodeSection(fileName, sectionName1, &Config); err01 != nil { 41 | err = fmt.Errorf("Load config file failed, error:%s", err01.Error()) 42 | return 43 | } 44 | 45 | sectionName2 := "ThirdProxy" 46 | if err02 := util.DecodeSection(fileName, sectionName2, &ThirdConfig); err != nil { 47 | err = fmt.Errorf("Load config file failed, error:%s", err02.Error()) 48 | return 49 | } 50 | 51 | if Config.Proxy == "" && ThirdConfig.Address == "" { 52 | err = fmt.Errorf("Proxy服务地址和第三方Proxy服务地址不能同时为空") 53 | return 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /src/punching/server/main.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "punching/logger" 5 | "punching/util" 6 | "time" 7 | ) 8 | 9 | var ( 10 | Dch chan bool 11 | Rch chan util.PairPackage 12 | Wch chan []byte 13 | ) 14 | 15 | func Main() { 16 | 17 | // 加载配置信息 18 | if err := InitConfig(); err != nil { 19 | logger.Errorf("加载配置信息出错,原因为:%s\n", err) 20 | return 21 | } 22 | 23 | // Proxy Server Address 24 | proxyAddr := Config.Proxy 25 | if proxyAddr == "" { 26 | proxyAddr = ThirdConfig.Address 27 | } 28 | pairName := Config.Key 29 | 30 | var connPeer util.NetConn 31 | 32 | // 如果跟Peer连接出错,要重新连接Proxy 33 | for { 34 | 35 | logger.Infof("准备连接Proxy:%s", proxyAddr) 36 | conn, err := ServerDialProxy(proxyAddr, pairName) 37 | 38 | if err != nil { 39 | logger.Errorf("连接到Proxy出现错误,", err) 40 | time.Sleep(5 * time.Second) 41 | continue 42 | } 43 | 44 | localAddr, remoteAddr, errWait := WaitForPeer(conn) 45 | 46 | if errWait != nil { 47 | logger.Errorf("服务端在等待P2P客户端连入出错,原因为:", errWait) 48 | time.Sleep(5 * time.Second) 49 | continue 50 | } 51 | 52 | logger.Infof("服务端:本地地址:%s,对方地址:%s,准备连接", localAddr, remoteAddr) 53 | //连接对方 54 | var errPeer error 55 | connPeer, errPeer = util.DialPeer(localAddr, remoteAddr) 56 | if errPeer != nil { //无法连接上 57 | logger.Errorf("无法连接对方,本地地址:%s,远程地址:%s,错误:%s", localAddr, remoteAddr, errPeer) 58 | continue 59 | } 60 | 61 | //已经连接上 62 | 63 | // 连接要开启的服务 64 | Dch = make(chan bool) 65 | Rch = make(chan util.PairPackage) 66 | Wch = make(chan []byte) 67 | 68 | go RHandler(connPeer) //Nat端写通道 69 | go WHandler(connPeer) //Nat端读通道 70 | 71 | // 转发到提供服务端口,并将服务端口数据转到Nat端 72 | handleServerConn() 73 | 74 | // 如果P2P端通讯出错,退出 75 | select { 76 | case <-Dch: 77 | continue 78 | } 79 | } 80 | 81 | } 82 | 83 | func RHandler(conn util.NetConn) { 84 | 85 | //声明一个临时缓冲区,用来存储被截断的数据 86 | tmpBuffer := make([]byte, 0) 87 | 88 | buff := make([]byte, 1024) 89 | for { 90 | j, err := conn.Read(buff) 91 | if err != nil { 92 | logger.Errorf("读取连接数据出错,原因为:", err) 93 | Dch <- true 94 | break 95 | } 96 | logger.Info("准备解包数据:", j) 97 | // 解包 98 | tmpBuffer = util.UnpackageNat(append(tmpBuffer, buff[:j]...), Rch) 99 | } 100 | } 101 | 102 | func WHandler(conn util.NetConn) { 103 | for { 104 | select { 105 | case msg := <-Wch: 106 | l, err := conn.Write(msg) 107 | if err != nil { 108 | logger.Errorf("写到Nat目录连接出错:", err) 109 | Dch <- true 110 | } else { 111 | logger.Info(time.Now().UnixNano(), "已写入到Nat:", l) 112 | } 113 | 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/punching/server/proxy.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | 6 | "errors" 7 | "net" 8 | . "punching/constant" 9 | "punching/logger" 10 | "punching/util" 11 | "strings" 12 | ) 13 | 14 | var ( 15 | ProxyDch chan util.ProxyPackage 16 | ProxyRch chan []byte 17 | ProxyWch chan []byte 18 | ) 19 | 20 | // 等待,直到P2P客户端连入,在此期间会一直接受Proxy解析端发来的心跳包 21 | func WaitForPeer(conn *util.NetConn) (localAddr string, remoteAddr string, err error) { 22 | defer conn.Close() 23 | logger.Infof("Enter WaitForPeer") 24 | 25 | // 接收心跳和客户端连接确认 26 | ProxyRch = make(chan []byte) 27 | ProxyWch = make(chan []byte) 28 | 29 | ProxyDch = make(chan util.ProxyPackage) 30 | 31 | go RProxyHandler(conn) 32 | 33 | select { 34 | case pack := <-ProxyDch: 35 | 36 | switch pack.ControlID { 37 | case PROXY_CONTROL_QUIT: //无法跟proxy连接 38 | //关闭连接 39 | logger.Error("收到退出包") 40 | err = fmt.Errorf("收到退出") 41 | return 42 | case PROXY_CONTROL_NORMAL: // 获取客户端发来信息 43 | logger.Info("读取到客户端发来的信息包") 44 | data := pack.Data 45 | str := string(data) 46 | parts := strings.Split(str, ",") 47 | remoteAddr = parts[0] 48 | localAddr = parts[1] 49 | return 50 | } 51 | } 52 | 53 | return 54 | } 55 | 56 | // ServerDialProxy P2P服务端连接Proxy解析 57 | func ServerDialProxy(proxyAddr string, pairName string) (retConn *util.NetConn, err error) { 58 | 59 | var conn = &util.NetConn{} 60 | 61 | // 不指定端口,让系统自动分配 62 | err = conn.Bind("") 63 | if err != nil { 64 | logger.Errorf("绑定出错%s", err) 65 | return 66 | } 67 | 68 | // 连接到Proxy解析服务器 69 | tcpAddr, err := net.ResolveTCPAddr("tcp", proxyAddr) 70 | 71 | err = conn.Connect(util.InetAddr(tcpAddr.IP.String()), tcpAddr.Port) 72 | 73 | if err != nil { 74 | logger.Errorf("连接服务出错:%s", proxyAddr) 75 | fmt.Println("连接服务端出错", err.Error()) 76 | return 77 | } 78 | 79 | logger.Infof("已连接服务器,服务器地址是:%s:%d", tcpAddr.IP.String(), tcpAddr.Port) 80 | 81 | // 构造自定义包 82 | data := make([]byte, 0) 83 | data = append(data, []byte{ROLE_SERVER}...) 84 | data = append(data, []byte(pairName)...) 85 | 86 | packFirst := util.PackageProxy(PROXY_CONTROL_FIRST, data) 87 | 88 | _, err = conn.Write(packFirst) 89 | 90 | if err != nil { 91 | logger.Errorf("写入Proxy连接出错,%s", err) 92 | return 93 | } 94 | 95 | buff := make([]byte, 1024) 96 | 97 | // 获取返回信息 98 | i, err := conn.Read(buff) 99 | if err != nil { 100 | logger.Errorf("读取数据出错,%s", err) 101 | return 102 | } 103 | 104 | controlID := buff[1] 105 | switch controlID { 106 | case PROXY_CONTROL_NORMAL: 107 | retData := string(buff[2:i]) 108 | items := strings.Split(retData, ",") 109 | localAddr := items[0] 110 | rePairName := items[1] 111 | 112 | logger.Infof("P2P服务端侦听地址为:%s, 匹配码为:%s", localAddr, rePairName) 113 | break 114 | case PROXY_CONTROL_ERROR_SERVER_EXIST: 115 | logger.Error("错误,P2P服务端已存在") 116 | err = errors.New("错误,P2P服务端已存在") 117 | break 118 | default: 119 | err = fmt.Errorf("无效的控制码,%d", int(controlID)) 120 | } 121 | 122 | return conn, err 123 | 124 | } 125 | 126 | func RProxyHandler(conn *util.NetConn) { 127 | 128 | for { 129 | // 心跳包,回复ack 130 | data := make([]byte, 512) 131 | i, err0 := conn.Read(data) 132 | if err0 != nil { 133 | logger.Errorf("读取Proxy连接出错,%s", err0) 134 | ProxyDch <- util.ProxyPackage{ControlID: PROXY_CONTROL_QUIT} 135 | return 136 | } 137 | 138 | // Invalid package 139 | pack, err := util.UnpackageProxy(data[0:i]) 140 | if err != nil { 141 | logger.Errorf("解包错误,%s", err) 142 | ProxyDch <- util.ProxyPackage{ControlID: PROXY_CONTROL_QUIT} 143 | return 144 | } 145 | 146 | if pack.ControlID == PROXY_CONTROL_HEARTBIT { 147 | // Received heartbeat package 148 | // 确认 149 | ackPack := util.PackageProxy(PROXY_CONTROL_HEARTBITACK, []byte("")) 150 | conn.Write(ackPack) 151 | 152 | } else { 153 | 154 | ProxyDch <- pack 155 | return 156 | 157 | } 158 | 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/punching/util/configuration.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/BurntSushi/toml" 6 | "os" 7 | ) 8 | 9 | // LoadTomlFile 加载配置文件 10 | func LoadTomlFile(fileName string) (sections map[string]toml.Primitive, m toml.MetaData, err error) { 11 | // 判断配置文件是否存在 12 | if _, err = os.Stat(fileName); err != nil { 13 | if os.IsNotExist(err) { 14 | err = fmt.Errorf("configuration file %s does not exist.\r\n", fileName) 15 | } else { 16 | err = fmt.Errorf("configuration file %s execption:%s\r\n", fileName, err.Error()) 17 | } 18 | return 19 | } 20 | 21 | // 加载配置文件 22 | 23 | var file toml.Primitive 24 | var meta toml.MetaData 25 | 26 | if meta, err = toml.DecodeFile(fileName, &file); err != nil { 27 | err = fmt.Errorf("load configuration file %s failed:%s", fileName, err.Error()) 28 | } else { 29 | 30 | err = meta.PrimitiveDecode(file, §ions) 31 | } 32 | m = meta 33 | return 34 | } 35 | 36 | // DecodeSection 解码一个节点的配置信息 37 | func DecodeSection(filename, name string, v interface{}) (err error) { 38 | 39 | sections, meta, err := LoadTomlFile(filename) 40 | if err != nil { 41 | return 42 | } 43 | 44 | if section, ok := sections[name]; ok { 45 | return meta.PrimitiveDecode(section, v) 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /src/punching/util/conv.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func InetAddr(ipaddr string) [4]byte { 11 | var ( 12 | ips = strings.Split(ipaddr, ".") 13 | ip [4]uint64 14 | ret [4]byte 15 | ) 16 | for i := 0; i < 4; i++ { 17 | ip[i], _ = strconv.ParseUint(ips[i], 10, 8) 18 | } 19 | for i := 0; i < 4; i++ { 20 | ret[i] = byte(ip[i]) 21 | } 22 | return ret 23 | } 24 | 25 | func InetPort(ipport string) uint16 { 26 | ret, _ := strconv.ParseUint(ipport, 10, 16) 27 | return uint16(ret) 28 | } 29 | 30 | // IntToBytes 整形转换成字节 31 | func IntToBytes(n int) []byte { 32 | x := int32(n) 33 | 34 | bytesBuffer := bytes.NewBuffer([]byte{}) 35 | binary.Write(bytesBuffer, binary.BigEndian, x) 36 | return bytesBuffer.Bytes() 37 | } 38 | 39 | // BytesToInt 字节转换成整形 40 | func BytesToInt(b []byte) int { 41 | bytesBuffer := bytes.NewBuffer(b) 42 | var x int32 43 | binary.Read(bytesBuffer, binary.BigEndian, &x) 44 | return int(x) 45 | } 46 | -------------------------------------------------------------------------------- /src/punching/util/net.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | . "punching/constant" 7 | "punching/logger" 8 | "strings" 9 | ) 10 | 11 | // ClientDialProxy P2P客户端连接到Proxy端 12 | // 连接成功后获取本地地址,远程地址和匹配码,否则将返回错误 13 | func ClientDialProxy(proxyAddr string, pairName string) (localAddr string, remoteAddr string, err error) { 14 | 15 | var conn = NetConn{} 16 | 17 | // 不指定端口,让系统自动分配 18 | err = conn.Bind("") 19 | if err != nil { 20 | logger.Errorf("绑定出错,%s", err) 21 | return 22 | } 23 | 24 | // 连接到Proxy解析服务器 25 | tcpAddr, err := net.ResolveTCPAddr("tcp", proxyAddr) 26 | err = conn.Connect(InetAddr(tcpAddr.IP.String()), tcpAddr.Port) 27 | 28 | if err != nil { 29 | logger.Errorf("连接服务端出错,%s", err) 30 | return 31 | } 32 | defer conn.Close() 33 | logger.Infof("已连接服务器,服务器地址是:%s:%d", tcpAddr.IP.String(), tcpAddr.Port) 34 | 35 | // 构造自定义包 36 | data := make([]byte, 0) 37 | data = append(data, []byte{ROLE_CLIENT}...) 38 | data = append(data, []byte(pairName)...) 39 | 40 | packFirst := PackageProxy(PROXY_CONTROL_FIRST, data) 41 | 42 | _, err = conn.Write(packFirst) 43 | if err != nil { 44 | logger.Errorf("写入Proxy连接出错,%s", err) 45 | return 46 | } 47 | 48 | buff := make([]byte, 1024) 49 | 50 | // 获取返回信息 51 | i, err := conn.Read(buff) 52 | if err != nil { 53 | logger.Errorf("读取数据出错,%s", err) 54 | return 55 | } 56 | 57 | controlID := buff[1] 58 | switch controlID { 59 | case PROXY_CONTROL_NORMAL: 60 | retData := string(buff[2:i]) 61 | items := strings.Split(retData, ",") 62 | remoteAddr = items[0] 63 | localAddr = items[1] 64 | 65 | //// 发送确认 66 | //packAck := PackageProxy(PROXY_CONTROL_ACK, []byte("")) 67 | //conn.Write(packAck) 68 | return 69 | case PROXY_CONTROL_ERROR_NO_SERVER: 70 | err = fmt.Errorf("错误,P2P服务端不存在") 71 | break 72 | case PROXY_CONTROL_ERROR_CLIENT_EXIST: 73 | err = fmt.Errorf("错误,P2P客户端已存在") 74 | break 75 | default: 76 | err = fmt.Errorf("无效的控制码,%d", int(controlID)) 77 | } 78 | 79 | return 80 | } 81 | 82 | func DialPeer(localAddr string, remoteAddr string) (netconn NetConn, err error) { 83 | 84 | remoteTCPAddr, err := net.ResolveTCPAddr("tcp", remoteAddr) 85 | if err != nil { 86 | logger.Errorf("The format of remote address is invalid, %s", err) 87 | return 88 | } 89 | var conn NetConn 90 | 91 | // 不指定端口,让系统自动分配 92 | err = conn.Bind(localAddr) 93 | if err != nil { 94 | logger.Errorf("绑定出错,%s", err) 95 | return 96 | } 97 | 98 | // 有时连接一次并不成功,尝试多次连接 99 | //tryCount := 0 100 | //for { 101 | // tryCount += 1 102 | // 103 | // if tryCount > 10 { 104 | // err = fmt.Errorf("Attempt to connect remote address, but failed, local addrss: %s, "+ 105 | // "remote address:%s", localAddr, remoteAddr) 106 | // return 107 | // } 108 | err = conn.Connect(InetAddr(remoteTCPAddr.IP.String()), remoteTCPAddr.Port) 109 | if err != nil { 110 | logger.Warnf("第次不能连接远程服务器:%s", err) 111 | //time.Sleep(1 * time.Second) 112 | //continue 113 | } else { 114 | logger.Infof("已经连接到peer: %s", remoteTCPAddr.String()) 115 | //break 116 | } 117 | //} 118 | return conn, err 119 | } 120 | -------------------------------------------------------------------------------- /src/punching/util/netconn_darwin.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os" 9 | 10 | "punching/logger" 11 | "syscall" 12 | "time" 13 | ) 14 | 15 | type NetConn struct { 16 | fd int // 文件句柄 17 | conn net.Conn // 连接对象 18 | } 19 | 20 | func (hole *NetConn) Close() { 21 | 22 | //if hole.conn != nil { 23 | logger.Info("断开连接") 24 | hole.conn.Close() 25 | //} 26 | 27 | } 28 | 29 | func (hole *NetConn) Bind(addr string) (err error) { 30 | 31 | proto := "tcp" 32 | 33 | syscall.ForkLock.RLock() 34 | var fd int 35 | if fd, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); err != nil { 36 | syscall.ForkLock.RUnlock() 37 | return 38 | } 39 | syscall.ForkLock.RUnlock() 40 | 41 | defer func() { 42 | if err != nil { 43 | syscall.Close(fd) 44 | } 45 | }() 46 | 47 | if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { 48 | return 49 | } 50 | 51 | if len(addr) > 0 { 52 | var tcp *net.TCPAddr 53 | tcp, err = net.ResolveTCPAddr(proto, addr) 54 | if err != nil && tcp.IP != nil { 55 | log.Println(err) 56 | return 57 | } 58 | sockaddr := &syscall.SockaddrInet4{Port: tcp.Port} 59 | if err = syscall.Bind(fd, sockaddr); err != nil { 60 | return 61 | } 62 | } 63 | 64 | hole.fd = fd 65 | 66 | return 67 | } 68 | 69 | func (hole *NetConn) Connect(addr [4]byte, port int) (err error) { 70 | 71 | if hole.fd == 0 { 72 | 73 | err = errors.New("请先调用Bind()函数") 74 | return 75 | } 76 | 77 | addrInet4 := syscall.SockaddrInet4{ 78 | Addr: addr, 79 | Port: port, 80 | } 81 | 82 | chConnect := make(chan error) 83 | logger.Info(time.Now().UnixNano(), "准备连接对方") 84 | go func() { 85 | err = syscall.Connect(hole.fd, &addrInet4) 86 | chConnect <- err 87 | }() 88 | 89 | //有时候连接被远端抛弃的时候, syscall.Connect() 会很久才返回 90 | ticker := time.NewTicker(10 * time.Second) 91 | select { 92 | case <-ticker.C: 93 | err = fmt.Errorf("Connect timeout") 94 | return 95 | case e := <-chConnect: 96 | if e != nil { 97 | err = e 98 | logger.Errorf("Connect error: %s", err) 99 | return 100 | } 101 | } 102 | 103 | // 转为net.conn对象 104 | var file *os.File 105 | file = os.NewFile(uintptr(hole.fd), fmt.Sprintf("tcpholepunching.%d", time.Now().UnixNano())) 106 | if conn0, err0 := net.FileConn(file); err0 != nil { 107 | log.Println("Connect error", err0) 108 | err = err0 109 | return 110 | } else { 111 | hole.conn = conn0 112 | } 113 | 114 | if err = file.Close(); err != nil { 115 | log.Println("Connect error", err) 116 | return 117 | } 118 | return 119 | 120 | } 121 | 122 | func (hole *NetConn) Read(buffer []byte) (length int, err error) { 123 | 124 | return hole.conn.Read(buffer) 125 | } 126 | 127 | func (hole *NetConn) Write(data []byte) (length int, err error) { 128 | 129 | return hole.conn.Write(data) 130 | } 131 | -------------------------------------------------------------------------------- /src/punching/util/netconn_linux.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | "punching/logger" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | type NetConn struct { 14 | fd int // 文件句柄 15 | conn net.Conn // 连接对象 16 | } 17 | 18 | func (hole *NetConn) Close() { 19 | //if hole.conn != nil { 20 | hole.conn.Close() 21 | //} 22 | 23 | } 24 | 25 | func (hole *NetConn) Bind(addr string) (err error) { 26 | 27 | proto := "tcp" 28 | 29 | syscall.ForkLock.RLock() 30 | var fd int 31 | if fd, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); err != nil { 32 | syscall.ForkLock.RUnlock() 33 | return 34 | } 35 | syscall.ForkLock.RUnlock() 36 | 37 | defer func() { 38 | if err != nil { 39 | syscall.Close(fd) 40 | } 41 | }() 42 | 43 | if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { 44 | return 45 | } 46 | 47 | if len(addr) > 0 { 48 | var tcp *net.TCPAddr 49 | tcp, err = net.ResolveTCPAddr(proto, addr) 50 | if err != nil && tcp.IP != nil { 51 | log.Println(err) 52 | return 53 | } 54 | sockaddr := &syscall.SockaddrInet4{Port: tcp.Port} 55 | if err = syscall.Bind(fd, sockaddr); err != nil { 56 | return 57 | } 58 | } 59 | 60 | hole.fd = fd 61 | 62 | return 63 | } 64 | 65 | func (hole *NetConn) Connect(addr [4]byte, port int) (err error) { 66 | 67 | if hole.fd == 0 { 68 | 69 | err = fmt.Errorf("请先调用Bind()函数") 70 | return 71 | } 72 | 73 | addrInet4 := syscall.SockaddrInet4{ 74 | Addr: addr, 75 | Port: port, 76 | } 77 | 78 | chConnect := make(chan error) 79 | logger.Info(time.Now().UnixNano(), "准备连接对方") 80 | go func() { 81 | err = syscall.Connect(hole.fd, &addrInet4) 82 | chConnect <- err 83 | }() 84 | 85 | //有时候连接被远端抛弃的时候, syscall.Connect() 会很久才返回 86 | ticker := time.NewTicker(60 * time.Second) 87 | select { 88 | case <-ticker.C: 89 | err = fmt.Errorf("Connect timeout") 90 | return 91 | case e := <-chConnect: 92 | if e != nil { 93 | err = e 94 | logger.Errorf("Connect error: %s", err) 95 | return 96 | } 97 | } 98 | 99 | // 转为net.conn对象 100 | var file *os.File 101 | file = os.NewFile(uintptr(hole.fd), fmt.Sprintf("tcpholepunching.%d", time.Now().UnixNano())) 102 | if conn0, err0 := net.FileConn(file); err0 != nil { 103 | log.Println("Connect error", err0) 104 | err = err0 105 | return 106 | } else { 107 | hole.conn = conn0 108 | } 109 | 110 | if err = file.Close(); err != nil { 111 | log.Println("Connect error", err) 112 | return 113 | } 114 | return 115 | 116 | } 117 | 118 | func (hole *NetConn) Read(buffer []byte) (length int, err error) { 119 | 120 | return hole.conn.Read(buffer) 121 | } 122 | 123 | func (hole *NetConn) Write(data []byte) (length int, err error) { 124 | 125 | return hole.conn.Write(data) 126 | } 127 | -------------------------------------------------------------------------------- /src/punching/util/netconn_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | //import ( 4 | // "testing" 5 | // "punching/util" 6 | //) 7 | 8 | //func TestNetConn(t *testing.T){ 9 | // var conn util.NetConn 10 | // defer conn.Close() 11 | // if err := conn.Bind("tcp", ""); err != nil{ 12 | // t.Errorf("绑定出错,%s", err.Error()) 13 | // } 14 | // if err := conn.Connect(util.InetAddr("211.102.90.92"), 22); err != nil{ 15 | // t.Errorf("绑定出错,%s", err.Error()) 16 | // } 17 | // 18 | // 19 | //} 20 | -------------------------------------------------------------------------------- /src/punching/util/netconn_windows.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "punching/logger" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func MAKEWORD(low, high uint8) uint32 { 13 | var ret uint16 = uint16(high)<<8 + uint16(low) 14 | return uint32(ret) 15 | } 16 | 17 | type NetConn struct { 18 | sock syscall.Handle 19 | } 20 | 21 | func (hole *NetConn) Close() { 22 | 23 | syscall.WSACleanup() 24 | syscall.Closesocket(hole.sock) 25 | 26 | } 27 | 28 | func (hole *NetConn) Bind(addr string) (err error) { 29 | 30 | proto := "tcp" 31 | 32 | var wsadata syscall.WSAData 33 | 34 | if err = syscall.WSAStartup(MAKEWORD(2, 2), &wsadata); err != nil { 35 | log.Println("Startup error") 36 | return 37 | } 38 | 39 | var sock syscall.Handle 40 | syscall.ForkLock.RLock() 41 | if sock, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); err != nil { 42 | syscall.ForkLock.RUnlock() 43 | return 44 | } 45 | syscall.ForkLock.RUnlock() 46 | 47 | defer func() { 48 | if err != nil { 49 | syscall.Close(sock) 50 | } 51 | }() 52 | 53 | if err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { 54 | return 55 | } 56 | 57 | if len(addr) > 0 { 58 | var tcp *net.TCPAddr 59 | tcp, err = net.ResolveTCPAddr(proto, addr) 60 | if err != nil && tcp.IP != nil { 61 | log.Println(err) 62 | return 63 | } 64 | sockaddr := &syscall.SockaddrInet4{Port: tcp.Port} 65 | if err = syscall.Bind(sock, sockaddr); err != nil { 66 | return 67 | } 68 | } 69 | 70 | hole.sock = sock 71 | return 72 | } 73 | 74 | func (hole *NetConn) Connect(addr [4]byte, port int) (err error) { 75 | if hole.sock == 0 { 76 | err = fmt.Errorf("请先执行Bind()") 77 | return 78 | } 79 | addrInet4 := syscall.SockaddrInet4{ 80 | Addr: addr, 81 | Port: port, 82 | } 83 | 84 | chConnect := make(chan error) 85 | logger.Info(time.Now().UnixNano(), "准备连接对方") 86 | go func() { 87 | err = syscall.Connect(hole.sock, &addrInet4) 88 | chConnect <- err 89 | }() 90 | 91 | //有时候连接被远端抛弃的时候, syscall.Connect() 会很久才返回 92 | ticker := time.NewTicker(30 * time.Second) 93 | select { 94 | case <-ticker.C: 95 | err = fmt.Errorf("Connect timeout") 96 | return 97 | case e := <-chConnect: 98 | if e != nil { 99 | err = e 100 | log.Println("Connect error: ", err) 101 | return 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | func (hole *NetConn) Read(buffer []byte) (length int, err error) { 108 | 109 | dataWsaBuf := syscall.WSABuf{Len: uint32(len(buffer)), Buf: &buffer[0]} 110 | flags := uint32(0) 111 | recvd := uint32(0) 112 | 113 | err = syscall.WSARecv(hole.sock, &dataWsaBuf, 1, &recvd, &flags, nil, nil) 114 | if err != nil { 115 | return 0, err 116 | } 117 | return int(recvd), nil 118 | } 119 | 120 | func (hole *NetConn) Write(data []byte) (length int, err error) { 121 | var ( 122 | dataWsaBuf syscall.WSABuf 123 | SendBytes uint32 124 | overlapped syscall.Overlapped 125 | ) 126 | dataWsaBuf.Len = uint32(len(data)) 127 | dataWsaBuf.Buf = &data[0] 128 | err = syscall.WSASend(hole.sock, &dataWsaBuf, 1, &SendBytes, 0, &overlapped, nil) 129 | if err != nil { 130 | return 0, err 131 | } else { 132 | return int(SendBytes), nil 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/punching/util/package.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | . "punching/constant" 7 | ) 8 | 9 | // PairPackage P2P端通讯封装包 10 | type PairPackage struct { 11 | Head string // 头 6位字符 12 | ControlID byte // 控制ID 13 | SessionID string // 会话ID 4位字符 14 | Data []byte // 数据 15 | } 16 | 17 | // 代理解析端通讯包 18 | type ProxyPackage struct { 19 | Head byte // 头 20 | ControlID byte // 控制ID 21 | Data []byte // 数据 22 | } 23 | 24 | // 跟代理解析端通讯封包 25 | func PackageProxy(control byte, data []byte) []byte { 26 | pack := bytes.NewBuffer(nil) 27 | pack.Write([]byte{PROXY_PACKAGE_HEAD}) 28 | pack.Write([]byte{control}) 29 | pack.Write(data) 30 | return pack.Bytes() 31 | } 32 | 33 | // 跟代理解析端拆包 34 | func UnpackageProxy(buffer []byte) (pack ProxyPackage, err error) { 35 | 36 | if len(buffer) < 2 { 37 | err = errors.New("格式不对,长度小于2") 38 | return 39 | } 40 | 41 | if buffer[0] != PROXY_PACKAGE_HEAD { 42 | err = errors.New("包头不对") 43 | return 44 | } 45 | pack = ProxyPackage{buffer[0], buffer[1], buffer[2:]} 46 | return 47 | } 48 | 49 | // Customize P2P data package 50 | // The format of package defined below: 51 | // head(6)+control(1)+session id(4) + data length (4) + data 52 | func PackageNat(control byte, sessionID string, data []byte) []byte { 53 | pack := bytes.NewBuffer(nil) 54 | pack.Write([]byte(PAIR_PACKAGE_HEAD)) // Head [6]byte 55 | pack.Write([]byte{control}) 56 | pack.Write([]byte(sessionID)) 57 | pack.Write(IntToBytes(len(data))) // length of sent data 58 | 59 | pack.Write(data) 60 | return pack.Bytes() 61 | } 62 | 63 | // Nat网络后面的Client端和Server端拆包 64 | // 需要考虑沾包,分析出的完整封装包传入读channel 65 | func UnpackageNat(buffer []byte, readChan chan PairPackage) (data []byte) { 66 | 67 | length := len(buffer) 68 | 69 | var i int 70 | for i = 0; i < length; i = i + 1 { 71 | if length < i+PAIR_PACKAGE_PREFIX_LENGTH { 72 | break 73 | } 74 | 75 | if string(buffer[i:i+PAIR_PACKAGE_HEAD_LENGTH]) == PAIR_PACKAGE_HEAD { 76 | 77 | // Length of data 78 | dataLength := BytesToInt(buffer[i+PAIR_PACKAGE_PREFIX_LENGTH-PAIR_PACKAGE_DATA_LENGTH : i+ 79 | PAIR_PACKAGE_PREFIX_LENGTH]) 80 | 81 | if length < i+PAIR_PACKAGE_PREFIX_LENGTH+dataLength { 82 | break 83 | } 84 | 85 | // data 86 | data := buffer[i+PAIR_PACKAGE_PREFIX_LENGTH : i+PAIR_PACKAGE_PREFIX_LENGTH+ 87 | dataLength] 88 | 89 | controlID := buffer[i+PAIR_PACKAGE_HEAD_LENGTH : i+PAIR_PACKAGE_HEAD_LENGTH+ 90 | PAIR_PACKAGE_CONTROL_LENGTH] 91 | 92 | iSessionIDStartPos := PAIR_PACKAGE_HEAD_LENGTH + PAIR_PACKAGE_CONTROL_LENGTH 93 | sessionID := string(buffer[i+iSessionIDStartPos : i+iSessionIDStartPos+PAIR_PACKAGE_SESSIONID_LENGTH]) 94 | 95 | pack := PairPackage{ 96 | Head: PAIR_PACKAGE_HEAD, 97 | Data: data, 98 | ControlID: controlID[0], 99 | SessionID: sessionID, 100 | } 101 | 102 | readChan <- pack 103 | 104 | i += PAIR_PACKAGE_PREFIX_LENGTH + dataLength - 1 105 | } 106 | } 107 | 108 | if i == length { 109 | return make([]byte, 0) 110 | } 111 | return buffer[i:] 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/punching/util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | // GenerateRandomPairKey 获取4位随机匹配码 9 | func GenerateRandomPairKey() string { 10 | //97~122 小写字母 11 | rndNums := GenerateRandomNumber(97, 122, 4) 12 | key := "" 13 | for _, num := range rndNums { 14 | key = key + string(byte(num)) 15 | } 16 | return key 17 | } 18 | 19 | //生成count个[start,end)结束的不重复的随机数 20 | func GenerateRandomNumber(start int, end int, count int) []int { 21 | // Check the range 22 | if end < start || (end-start) < count { 23 | return nil 24 | } 25 | // Slice to store the result 26 | nums := make([]int, 0) 27 | //随机数生成器,加入时间戳保证每次生成的随机数不一样 28 | 29 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 30 | for len(nums) < count { 31 | //生成随机数 32 | num := r.Intn(end-start) + start 33 | //查重 34 | exist := false 35 | for _, v := range nums { 36 | if v == num { 37 | exist = true 38 | break 39 | } 40 | } 41 | if !exist { 42 | nums = append(nums, num) 43 | } 44 | } 45 | return nums 46 | } 47 | -------------------------------------------------------------------------------- /src/punching/util/rand_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "punching/util" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestGenerateRandomPairKey(t *testing.T) { 12 | t1 := util.GenerateRandomPairKey() 13 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 14 | num := r.Intn(122-97) + 97 15 | 16 | t.Log(string(byte(num))) 17 | 18 | fmt.Println("fff") 19 | fmt.Println(t1) 20 | if len(t1) != 4 { 21 | t.Errorf("长度不对,%s", t1) 22 | } 23 | } 24 | --------------------------------------------------------------------------------