├── nac.syso ├── .gitignore ├── go.mod ├── conf.yaml ├── Dockerfile ├── nac.manifest ├── LICENSE ├── core ├── constant.go ├── intra_handler.go ├── intra_server_handler.go ├── core_handler.go ├── pool_handler.go ├── connect_handler.go ├── natok_server_handler.go └── natok_handler.go ├── s-cert.pem ├── s-cert.key ├── go.sum ├── README.md ├── conf └── app_config.go ├── main.go └── grid-snake.svg /nac.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natokay/go-natok-cli/HEAD/nac.syso -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse artifacts, including WTP generated manifests # 2 | .classpath 3 | .project 4 | 5 | # IDEA artifacts and output dirs # 6 | *.ipr 7 | *.iws 8 | .idea 9 | 10 | *.exe 11 | vendor 12 | natok-cli 13 | *.log 14 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module natok-cli 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/kardianos/service v1.2.2 7 | github.com/sirupsen/logrus v1.9.3 8 | gopkg.in/yaml.v2 v2.4.0 9 | ) 10 | 11 | require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 12 | -------------------------------------------------------------------------------- /conf.yaml: -------------------------------------------------------------------------------- 1 | natok: 2 | server: 3 | # - host: yourdomain.com 4 | # port: 1001 5 | # access-key: 752b65f22c39e006db38078585dc4fa4 6 | - host: localhost 7 | port: 1001 8 | access-key: 752b65f22c39e006db38078585dc4fa4 9 | cert-key-path: s-cert.key 10 | cert-pem-path: s-cert.pem 11 | log-file-path: out.log 12 | log-debug-level: false -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:8 2 | 3 | ENV HOME=/app/ 4 | 5 | ADD natok-cli ${HOME} 6 | ADD s-cert.key ${HOME} 7 | ADD s-cert.pem ${HOME} 8 | ADD application.json ${HOME} 9 | 10 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 11 | RUN echo 'Asia/Shanghai' >/etc/timezone 12 | 13 | WORKDIR ${HOME} 14 | ENTRYPOINT ["sh","-c","./natok-cli"] 15 | 16 | # docker build -f Dockerfile -t natok-cli:0.1 . 17 | # docker run --name=natok-cli --restart=always --net=host -d natok-cli:0.1 18 | -------------------------------------------------------------------------------- /nac.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | natok-cli 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 代码咖啡因 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /core/constant.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "sync" 4 | 5 | // 数据包常量 6 | const ( 7 | Uint8Size = 1 8 | Uint16Size = 2 9 | Uint32Size = 4 10 | Uint64Size = 8 11 | MaxPacketSize = 4 * 1 << 20 // 最大数据包大小为最大数据包大小为 4M 12 | ) 13 | 14 | // 消息类型常量 15 | const ( 16 | TypeAuth = 0x01 // 验证消息以检查访问密钥是否正确 17 | typeNoAvailablePort = 0x02 // 访问密钥没有可用端口 18 | TypeConnectNatok = 0xa1 //连接到NATOK服务 19 | TypeConnectIntra = 0xa2 //连接到内部服务 20 | TypeDisconnect = 0x04 // 断开 21 | TypeTransfer = 0x05 // 数据传输 22 | TypeIsInuseKey = 0x06 // 访问秘钥已在其他客户端使用 23 | TypeHeartbeat = 0x07 // 心跳 24 | TypeDisabledAccessKey = 0x08 // 禁用的访问密钥 25 | TypeDisabledTrialClient = 0x09 // 禁用的试用客户端 26 | TypeInvalidKey = 0x10 // 无效的访问密钥 27 | HeartbeatInterval = 10 //心跳间隔时长10秒 28 | ) 29 | 30 | // Counter 计数器 31 | type Counter struct { 32 | mu sync.Mutex 33 | count int 34 | } 35 | 36 | func (c *Counter) Increment() { 37 | c.mu.Lock() 38 | defer c.mu.Unlock() 39 | c.count++ 40 | } 41 | 42 | func (c *Counter) Decrement() { 43 | c.mu.Lock() 44 | defer c.mu.Unlock() 45 | c.count-- 46 | } 47 | 48 | func (c *Counter) GetCount() int { 49 | c.mu.Lock() 50 | defer c.mu.Unlock() 51 | return c.count 52 | } 53 | -------------------------------------------------------------------------------- /s-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDWTCCAkGgAwIBAgIJAK2vWakY/TwYMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV 3 | BAYTAkNOMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg 4 | Q29tcGFueSBMdGQwIBcNMjAwOTExMDgyODQ4WhgPMjEyMDA4MTgwODI4NDhaMEIx 5 | CzAJBgNVBAYTAkNOMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl 6 | ZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 7 | AQC/ar8oN+k5zXRF9Lsv/TN//K0mXmB/5poSFXzZjcqeOflDpdy3iV0FDd8/7OJW 8 | CU+5f91w78JSgLC9sth6cNQbSh0/Wu4xVmOV4uTQ6VSNAyfGsA5RiUWM2StfmvIh 9 | W5is9iDGffbzcuJda4ehQP2NtjJ/QVH3Dqor0sUcMQwi92DJjJORzqwRorkF9NHx 10 | NvHY0/oSPlkUkXX/w2xPPHME88sPpURMEc0j+yV9rTGhvLxqh6d3To9KhlkaieHo 11 | j+zI0C73E10z2sHzxLHXKxRCCX+4AK57oeyi8bSSB4IxIyaw+sxgvu1LTVknfewB 12 | GmmO0jjZfGkesfXnYOjrO30rAgMBAAGjUDBOMB0GA1UdDgQWBBR31XafwrSG1VCh 13 | SIaZ6E5lHVnB9DAfBgNVHSMEGDAWgBR31XafwrSG1VChSIaZ6E5lHVnB9DAMBgNV 14 | HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC7Ai/2bklqngszXsQTH0B3loLn 15 | DUjE8yiL/1aryat09fB0HL/LfuKESkNnNLSWqH3mKzXgOcnx5zR7T+i/83kHgtkj 16 | kDn0saaFdVPudTYiJuYhsnVKnaQwGeGfPz3deiLT8qnNgBraw2148+OhyXi4v9y9 17 | CPrGQq58SaULPxr0SJCpcSsUaNRiBYKHHYfLrBv4nbJ3VOwRhpEI8pAQ7v2UlbCN 18 | 6wW115ZkeeZiIk5NMl4garHXBM+1vm0FqLUNwRIfTK2eX4n9NnZXepgDJFJXvL6x 19 | 8e/uTN4rKAHIBBxRZ0lUxd1C0I0dTNJBrc7aCVnahlaSOc1szZvPXM/JWX1f 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /core/intra_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import log "github.com/sirupsen/logrus" 4 | 5 | // IntraServerHandler struct 内网服务处理 6 | type IntraServerHandler struct { 7 | Uri string 8 | AccessKey string 9 | NatokHandler *NatokHandler 10 | connectHandler *ConnectHandler 11 | } 12 | 13 | // Encode 编码消息 14 | func (s *IntraServerHandler) Encode(msg interface{}) []byte { 15 | if msg == nil { 16 | return []byte{} 17 | } 18 | return msg.([]byte) 19 | } 20 | 21 | // Decode 解码消息 22 | func (s *IntraServerHandler) Decode(buf []byte) (interface{}, int) { 23 | return buf, len(buf) 24 | } 25 | 26 | // Receive 请求接收 27 | func (s *IntraServerHandler) Receive(connHandler *ConnectHandler, data interface{}) { 28 | if conn := connHandler.ConnHandler; conn != nil { 29 | msg := Message{Type: TypeTransfer, Data: data.([]byte)} 30 | conn.Write(msg) 31 | log.Debugf("intra receive message %s", connHandler.Name) 32 | } 33 | } 34 | 35 | // Error 错误处理 36 | func (s *IntraServerHandler) Error(connHandler *ConnectHandler) { 37 | if natokHandler := connHandler.ConnHandler; natokHandler != nil { 38 | msg := Message{Type: TypeDisconnect, Uri: s.Uri} 39 | natokHandler.Write(msg) 40 | connHandler.ConnHandler = nil 41 | } 42 | } 43 | 44 | // Failure 失败处理 45 | func (s *IntraServerHandler) Failure() { 46 | msg := Message{Type: TypeDisconnect, Uri: s.Uri} 47 | s.connectHandler.Write(msg) 48 | } 49 | -------------------------------------------------------------------------------- /s-cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAv2q/KDfpOc10RfS7L/0zf/ytJl5gf+aaEhV82Y3Knjn5Q6Xc 3 | t4ldBQ3fP+ziVglPuX/dcO/CUoCwvbLYenDUG0odP1ruMVZjleLk0OlUjQMnxrAO 4 | UYlFjNkrX5ryIVuYrPYgxn3283LiXWuHoUD9jbYyf0FR9w6qK9LFHDEMIvdgyYyT 5 | kc6sEaK5BfTR8Tbx2NP6Ej5ZFJF1/8NsTzxzBPPLD6VETBHNI/slfa0xoby8aoen 6 | d06PSoZZGonh6I/syNAu9xNdM9rB88Sx1ysUQgl/uACue6HsovG0kgeCMSMmsPrM 7 | YL7tS01ZJ33sARppjtI42XxpHrH152Do6zt9KwIDAQABAoIBAEFjAoeHidjf8O8Q 8 | qXy8HoKC2tb3eDlYmZrB0lMyl1szbI2KM/pSJv9Z/MAGeE5xgdVY81jn3dZ29Wjn 9 | lgFFV3828wS4WBNscjo6NnWSrvo4cLbzXwDFRofVi3ZuJHX2pxG2Rf3n+5qvzNmi 10 | qMMRw0tMSLWlp40gakrsBb8alg2/IevSSxKjRMt5HsihNbbLydR2qSGNBcmsDKQB 11 | uLzwmpgPeyhUdvBeX+99yqwJ6AJ4GpDyi1dGgbsiK/Z5F0T8AwD0ic+LCkuWFCL0 12 | K2HBx/HsfrTpj5dGcDTxxFI1JdvJWcioPYvUlDcHg98i/zwng6eCx/d3Q+jzK5w3 13 | 6/NYwrECgYEA5i4FO8Va73hAACI005wEqjJRsapI5tI4ckFLtiwFnU385GdeNJzv 14 | CGtBMRCFpEAdVKmqs+EVHKntPg4+LzfYKnsyBA9qnns7kHjP2Efqfq/ixAOZMaeX 15 | ++pPgQJ2bEszjqaXakXdykoDbr21GAM1fPBoslY9OE51p1tOGGCKMZMCgYEA1OOX 16 | Rf6r75GfQd0JnvCys5iNZwnhKvbR7UvKjRDv09fZ6/Q5HmFZldmmxq0SGWmgQzSz 17 | UvVg5i8pc4DghMzJvDtCFSGgPTLn2xNTvECB8Jwm7o1rJw39/CnamMkadLg9Cb2i 18 | g8IMn9Mml+xx1hI9hsbL38vEKpoFNKFqvO1kpQkCgYEArfeCRRZ4EB2WYYN44aY9 19 | cFTvoZPN3YZs2w22p0zGQYm75PSrIqCpmHdXojmWh/ldMau6NJGdXzie8hPZs95F 20 | JnZN6vur3XPOJPbqP9C6zl0oynTdx8We/OquhBbUYizEHsCSF+QOKOGfjoca47cp 21 | KfCZcI/1XSUPjxlXAN2WFLkCgYASnPuC8StTPOYxugO3U9AsB7CFS8XWHdJo7vF8 22 | t/hgC0VQbf/4egZ9JZSBVmx4sFWEyrzLCg040vLK2H/I3KbewEec1V3PO/4tl1kA 23 | 4pr50I1O2ip+Naj5PSeRqDOZ9OnRSjVFU9gKuUlsiw3A68NZX1Q/8u7p0qGV4m8U 24 | qaTdEQKBgQCRNs3pSvr5MBst/TpLddrBBwRxBdZHZkgCecoKC9OtWG3llcKCO939 25 | LuDR8TcTGx6cga9CJsdxF7uxViRl1ggtRe8qZUwnTWLFdobPP6Zo4VpjJhtPANwl 26 | PIm4B0GReEUSmAcOCAERMZh4n/3BYUNRu/EMgEB3XEyt28JstoNDSQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= 5 | github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= 6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 8 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 9 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 10 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 11 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 12 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 15 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 19 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /core/intra_server_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/kataras/golog" 5 | ) 6 | 7 | // IntraServerHandler struct 内网服务处理 8 | type IntraServerHandler struct { 9 | Uri string 10 | AccessKey string 11 | PoolHandler *PoolHandler 12 | natokHandler *ConnectHandler 13 | } 14 | 15 | // Encode 编码消息 16 | func (i *IntraServerHandler) Encode(msg interface{}) []byte { 17 | if msg == nil { 18 | return []byte{} 19 | } 20 | return msg.([]byte) 21 | } 22 | 23 | // Decode 解码消息 24 | func (i *IntraServerHandler) Decode(buf []byte) (interface{}, int) { 25 | return buf, len(buf) 26 | } 27 | 28 | // Receive 请求接收 29 | func (i *IntraServerHandler) Receive(connHandler *ConnectHandler, data interface{}) { 30 | if connHandler.ConnHandler == nil { 31 | return 32 | } 33 | msg := Message{Type: TypeTransfer, Data: data.([]byte)} 34 | connHandler.ConnHandler.Write(msg) 35 | } 36 | 37 | // Success 成功 交换连接通道 38 | func (i *IntraServerHandler) Success(connHandler *ConnectHandler) { 39 | natokHandler, err := i.PoolHandler.Pull() 40 | if err != nil { 41 | golog.Errorf("Get Connection Uri:%s error:%+v", i.Uri, err) 42 | msg := Message{Type: TypeDisconnect, Uri: i.Uri} 43 | i.natokHandler.Write(msg) 44 | connHandler.Conn.Close() 45 | } else { 46 | natokHandler.ConnHandler = connHandler 47 | connHandler.ConnHandler = natokHandler 48 | 49 | msg := Message{Type: TypeConnect, Uri: i.Uri + "@" + i.AccessKey} 50 | natokHandler.Write(msg) 51 | golog.Infof("Intranet server connect success, notify natok server:%s", msg.Uri) 52 | } 53 | } 54 | 55 | // Error 错误处理 56 | func (i *IntraServerHandler) Error(connHandler *ConnectHandler) { 57 | conn := connHandler.ConnHandler 58 | if conn != nil { 59 | msg := Message{Type: TypeDisconnect, Uri: i.Uri} 60 | conn.Write(msg) 61 | conn.ConnHandler = nil 62 | } 63 | connHandler.MsgHandler = nil 64 | } 65 | 66 | // Failure 失败处理 67 | func (i *IntraServerHandler) Failure() { 68 | msg := Message{Type: TypeDisconnect, Uri: i.Uri} 69 | i.natokHandler.Write(msg) 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NATOK · ![GitHub Repo stars](https://img.shields.io/github/stars/natokay/go-natok-server) ![GitHub Repo stars](https://img.shields.io/github/stars/natokay/go-natok-cli) 2 | 3 |
4 | 5 | 6 |
7 |

8 | 9 | 10 | - 🌱 natok是一个将局域网内个人服务代理到公网可访问的内网穿透工具,基于tcp协议、支持udp协议,支持任何tcp上层协议(列如:http、https、ssh、telnet、data base、remote desktop....)。 11 | - 🤔 目前市面上提供类似服务的有:花生壳、natapp、ngrok等等。当然,这些工具都很优秀!但是免费提供的服务都很有限,想要有比较好的体验都需要支付一定的套餐费用,由于数据包会流经第三方,因此总归有些不太友好。 12 | - ⚡ natok-server与natok-cli都基于GO语言开发,几乎不存在并发问题。运行时的内存开销也很低,一般在几十M左右。所以很推荐自主搭建服务! 13 | 14 | 15 | **natok-cli的相关配置:conf.yaml** 16 | ```yaml 17 | natok: 18 | server: 19 | - host: natok1.cn #服务器地址:域名 或者 ip 20 | port: 1001 #服务器端口:可自定义 21 | #客户端访问密钥,从natok-server的web页面中C端列表里获取 22 | access-key: 74a7a42fcdc4ccb6c8641ce543fe2e07 23 | - host: natok2.cn 24 | port: 1001 25 | access-key: 74a7a42fcdc4ccb6c8641ce543fe2e07 26 | cert-key-path: s-cert.key #TSL加密密钥,可自己指定。注:需与server端保持一致 27 | cert-pem-path: s-cert.pem #TSL加密证书,可自己指定。注:需与server端保持一致 28 | log-file-path: out.log #程序日志输出配置 29 | ``` 30 | 31 | - windows系统启动: 双击 natok-cli.exe 32 | ```powershell 33 | # 注册服务,自动提取管理员权限: 34 | natok-cli.exe install 35 | # 卸载服务,自动提取管理员权限: 36 | natok-cli.exe uninstall 37 | # 启停服务,自动提取管理员权限: 38 | natok-cli.exe start/stop 39 | # 启停服务,终端管理员权限 40 | net start/stop natok-cli 41 | ``` 42 | - Linux系统启动: 43 | ```shell 44 | # 授予natok-cli可执权限 45 | chmod 755 natok-cli 46 | # 启动应用 47 | nohup ./natok-cli > /dev/null 2>&1 & 48 | ``` 49 | 50 | --- 51 | **Go 1.13 及以上(推荐)** 52 | ```shell 53 | # 配置 GOPROXY 环境变量 54 | go env -w GO111MODULE=on 55 | go env -w GOPROXY=https://goproxy.io,direct 56 | ``` 57 | 58 | 构建natok-cli可执行程序 59 | 60 | ```shell 61 | # 克隆项目 62 | git clone https://github.com/natokay/go-natok-cli.git 63 | 64 | # 进入项目目录 65 | cd go-natok-cli 66 | 67 | # 更新/下载依赖 68 | go mod tidy 69 | go mod vendor 70 | 71 | # 设置目标可执行程序操作系统构架,包括 386,amd64,arm 72 | go env -w GOARCH=amd64 73 | 74 | # 设置可执行程序运行操作系统,支持 darwin,freebsd,linux,windows 75 | go env -w GOOS=windows 76 | 77 | # golang windows 程序获取管理员权限(UAC) 78 | rsrc -manifest nac.manifest -o nac.syso 79 | 80 | # cd到main.go目录,打包命令 81 | go build 82 | 83 | # 启动程序 84 | ./natok-cli.exe 85 | ``` 86 | 87 | ## 版本描述 88 | **natok:1.0.0** 89 | natok-cli与natok-server网络代理通信基本功能实现。 90 | 91 | **natok:1.1.0** 92 | natok-cli与natok-server支持windows平台注册为服务运行,可支持开机自启,保证服务畅通。 93 | 94 | **natok:1.2.0** 95 | natok-cli可与多个natok-server保持连接,支持从多个不同的natok-server来访问natok-cli,以实现更快及更优的网络通信。 96 | 97 | **natok:1.3.0** 98 | natok-cli与natok-server可支持udp网络代理。 99 | -------------------------------------------------------------------------------- /core/core_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "net" 6 | "runtime/debug" 7 | "time" 8 | ) 9 | 10 | // Message struct 消息体对象 11 | type Message struct { 12 | Type byte // 消息类型 13 | Serial string // 消息序列 14 | Net string // 网络类型 15 | Uri string // 消息头 16 | Data []byte // 消息体 17 | } 18 | 19 | // MsgHandler interface 消息处理接口 20 | type MsgHandler interface { 21 | Error(*ConnectHandler) //出错 22 | Encode(interface{}) []byte //编码 23 | Decode([]byte) (interface{}, int) //解码 24 | Receive(*ConnectHandler, interface{}) //接收 25 | } 26 | 27 | // ConnectHandler struct 通道链接载体 28 | type ConnectHandler struct { 29 | Name string //通道名称 30 | ReadTime time.Time //读取时间 31 | WriteTime time.Time //写入时间 32 | Active bool //是否活跃 33 | ReadBuf []byte //读取的内容 34 | Conn net.Conn //连接通道 35 | MsgHandler MsgHandler //消息句柄 36 | ConnHandler *ConnectHandler //连接句柄 37 | } 38 | 39 | // Write 消息写入 40 | func (c *ConnectHandler) Write(msg interface{}) { 41 | if c.MsgHandler == nil { 42 | return 43 | } 44 | data := c.MsgHandler.Encode(msg) 45 | c.WriteTime = time.Now() 46 | _, _ = c.Conn.Write(data) 47 | } 48 | 49 | // Listen 连接请求监听 50 | func (c *ConnectHandler) Listen() { 51 | defer func() { 52 | if err := recover(); err != nil { 53 | c.MsgHandler.Error(c) 54 | log.Warnf("Warn: %+v", err) 55 | debug.PrintStack() 56 | } 57 | }() 58 | 59 | if c.Conn == nil { 60 | return 61 | } 62 | 63 | c.Active = true 64 | c.ReadTime = time.Now() 65 | 66 | for c.Active { 67 | // 最大缓冲4M 68 | if c.ReadBuf != nil && len(c.ReadBuf) > MaxPacketSize { 69 | log.Error("Warn: This conn is error ! Packet max than 4M !") 70 | _ = c.Conn.Close() 71 | } 72 | 73 | // 最大包64kb 74 | buf := make([]byte, 1024*64) 75 | n, err := c.Conn.Read(buf) 76 | if err != nil || n == 0 { 77 | log.Errorf("Error: %+v", err) 78 | c.Active = false 79 | c.MsgHandler.Error(c) 80 | return 81 | } 82 | 83 | c.ReadTime = time.Now() 84 | if c.ReadBuf == nil { 85 | c.ReadBuf = buf[0:n] 86 | } else { 87 | c.ReadBuf = append(c.ReadBuf, buf[0:n]...) 88 | } 89 | 90 | for { 91 | msg, n := c.MsgHandler.Decode(c.ReadBuf) 92 | if msg == nil { 93 | break 94 | } 95 | c.MsgHandler.Receive(c, msg) 96 | c.ReadBuf = c.ReadBuf[n:] 97 | if len(c.ReadBuf) == 0 { 98 | break 99 | } 100 | } 101 | 102 | if len(c.ReadBuf) > 0 { 103 | buf := make([]byte, len(c.ReadBuf)) 104 | copy(buf, c.ReadBuf) 105 | c.ReadBuf = buf 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /core/pool_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/kataras/golog" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | // NatokPool interface NATOK连接池处理接口 11 | type NatokPool interface { 12 | Add(*PoolHandler) (*ConnectHandler, error) //添加进连接池 13 | Remove(*ConnectHandler) //移除出连接池 14 | IsActive(*ConnectHandler) bool //连接是否活跃 15 | } 16 | 17 | type ConnPooler struct { 18 | Addr string 19 | Conf *tls.Config 20 | } 21 | 22 | // PoolHandler struct 23 | type PoolHandler struct { 24 | Mu sync.Mutex //锁 25 | Pool NatokPool //池 26 | Conns []*ConnectHandler //连接 27 | } 28 | 29 | // GetConn 获取连接 30 | func (p *PoolHandler) GetConn() (*ConnectHandler, error) { 31 | p.Mu.Lock() 32 | defer p.Mu.Unlock() 33 | 34 | if len(p.Conns) == 0 { 35 | return nil, nil 36 | } 37 | conn := p.Conns[len(p.Conns)-1] 38 | p.Conns = p.Conns[:len(p.Conns)-1] 39 | if p.Pool.IsActive(conn) { 40 | return conn, nil 41 | } else { 42 | return nil, nil 43 | } 44 | } 45 | 46 | // Push 放入连接池 47 | func (p *PoolHandler) Push(conn *ConnectHandler) { 48 | p.Mu.Lock() 49 | defer p.Mu.Unlock() 50 | p.Conns = append(p.Conns, conn) 51 | } 52 | 53 | // Pull 从连接池取出 54 | func (p *PoolHandler) Pull() (*ConnectHandler, error) { 55 | for { 56 | if len(p.Conns) == 0 { 57 | conn, err := p.Pool.Add(p) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return conn, nil 62 | } else { 63 | conn, err := p.GetConn() 64 | if conn != nil { 65 | return conn, err 66 | } 67 | } 68 | } 69 | } 70 | 71 | // Add 添加进连接池 72 | func (p *ConnPooler) Add(pool *PoolHandler) (*ConnectHandler, error) { 73 | var conn net.Conn 74 | var err error 75 | 76 | if p.Conf != nil { 77 | conn, err = tls.Dial("tcp", p.Addr, p.Conf) 78 | } else { 79 | conn, err = net.Dial("tcp", p.Addr) 80 | } 81 | 82 | if err != nil { 83 | golog.Errorf("Error:+v%", err) 84 | return nil, err 85 | } 86 | 87 | natokServerHandler := &NatokServerHandler{PoolHandler: pool} 88 | connHandler := &ConnectHandler{ 89 | Active: true, 90 | Conn: conn, 91 | MsgHandler: interface{}(natokServerHandler).(MsgHandler), 92 | } 93 | natokServerHandler.ConnHandler = connHandler 94 | natokServerHandler.HeartBeat() 95 | go func() { 96 | connHandler.Listen(conn, natokServerHandler) 97 | }() 98 | return connHandler, nil 99 | } 100 | 101 | // Remove 移除出连接池 102 | func (p *ConnPooler) Remove(connHandler *ConnectHandler) { 103 | connHandler.Conn.Close() 104 | } 105 | 106 | // IsActive 连接是否活跃 107 | func (p *ConnPooler) IsActive(connHandler *ConnectHandler) bool { 108 | return connHandler.Active 109 | } 110 | -------------------------------------------------------------------------------- /core/connect_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/kataras/golog" 5 | "net" 6 | "runtime/debug" 7 | "time" 8 | ) 9 | 10 | // MsgHandler interface 消息处理接口 11 | type MsgHandler interface { 12 | Success(*ConnectHandler) //成功 13 | Error(*ConnectHandler) //出错 14 | Encode(interface{}) []byte //编码 15 | Decode([]byte) (interface{}, int) //解码 16 | Receive(*ConnectHandler, interface{}) //接收 17 | } 18 | 19 | // Message struct 消息体对象 20 | type Message struct { 21 | Type byte 22 | SerialNum uint64 23 | Uri string 24 | Data []byte 25 | } 26 | 27 | // ConnectHandler struct 通道链接载体 28 | type ConnectHandler struct { 29 | ReadTime int64 //读取时间 30 | WriteTime int64 //写入时间 31 | Active bool //是否活跃 32 | ReadBuf []byte //读取的内容 33 | Conn net.Conn //连接通道 34 | MsgHandler MsgHandler //消息句柄 35 | ConnHandler *ConnectHandler //连接句柄 36 | } 37 | 38 | // Write 消息写入 39 | func (c *ConnectHandler) Write(msg interface{}) { 40 | if c.MsgHandler == nil { 41 | return 42 | } 43 | data := c.MsgHandler.Encode(msg) 44 | c.WriteTime = time.Now().Unix() 45 | c.Conn.Write(data) 46 | } 47 | 48 | // Listen 连接请求监听 49 | func (c *ConnectHandler) Listen(conn net.Conn, msgHandler interface{}) { 50 | defer func() { 51 | if err := recover(); err != nil { 52 | c.MsgHandler.Error(c) 53 | golog.Warnf("Warn: %+v", err) 54 | debug.PrintStack() 55 | } 56 | }() 57 | 58 | if conn == nil { 59 | return 60 | } 61 | 62 | c.Conn = conn 63 | c.Active = true 64 | c.ReadTime = time.Now().Unix() 65 | c.MsgHandler = msgHandler.(MsgHandler) 66 | c.MsgHandler.Success(c) 67 | 68 | for { 69 | buf := make([]byte, 1024*64) 70 | if c.ReadBuf != nil && len(c.ReadBuf) > MaxPacketSize { 71 | golog.Error("Warn: This conn is error ! Packet max than 4M !") 72 | c.Conn.Close() 73 | } 74 | 75 | n, err := c.Conn.Read(buf) 76 | if err != nil || n == 0 { 77 | golog.Errorf("Error:%v", err) 78 | //debug.PrintStack() 79 | c.Active = false 80 | c.MsgHandler.Error(c) 81 | break 82 | } 83 | 84 | c.ReadTime = time.Now().Unix() 85 | if c.ReadBuf == nil { 86 | c.ReadBuf = buf[0:n] 87 | } else { 88 | c.ReadBuf = append(c.ReadBuf, buf[0:n]...) 89 | } 90 | 91 | for { 92 | msg, n := c.MsgHandler.Decode(c.ReadBuf) 93 | if msg == nil { 94 | break 95 | } 96 | c.MsgHandler.Receive(c, msg) 97 | c.ReadBuf = c.ReadBuf[n:] 98 | if len(c.ReadBuf) == 0 { 99 | break 100 | } 101 | } 102 | 103 | if len(c.ReadBuf) > 0 { 104 | buf := make([]byte, len(c.ReadBuf)) 105 | copy(buf, c.ReadBuf) 106 | c.ReadBuf = buf 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /conf/app_config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "gopkg.in/yaml.v2" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "runtime" 11 | "strings" 12 | ) 13 | 14 | var AppConf *AppConfig 15 | 16 | // AppConfig 应用配置 17 | type AppConfig struct { 18 | Natok Natok `yaml:"natok"` 19 | } 20 | 21 | type Natok struct { 22 | Server []Server `yaml:"server"` //服务器端 23 | CertKeyPath string `yaml:"cert-key-path"` //密钥路径 24 | CertPemPath string `yaml:"cert-pem-path"` //证书路径 25 | LogFilePath string `yaml:"log-file-path"` //日志路径 26 | LogDebugLevel bool `yaml:"log-debug-level"` //Debug日志 27 | } 28 | 29 | // Server NATOK服务配置 30 | type Server struct { 31 | InetHost string `yaml:"host"` // 服务器地址 32 | InetPort int `yaml:"port"` // 服务器端口 33 | AccessKey string `yaml:"access-key"` //访问秘钥 34 | 35 | } 36 | 37 | // AppConfig Load 加载配置 38 | func init() { 39 | baseDir := getCurrentAbPath() 40 | // 读取文件内容 41 | file, err := os.ReadFile(baseDir + "conf.yaml") 42 | if err != nil { 43 | log.Error(err) 44 | panic(err) 45 | } 46 | // 利用json转换为AppConfig 47 | appConfig := new(AppConfig) 48 | err = yaml.Unmarshal(file, appConfig) 49 | if err != nil { 50 | log.Error(err) 51 | panic(err) 52 | } 53 | conf := &appConfig.Natok 54 | compile, err := regexp.Compile("^/|^\\\\|^[a-zA-Z]:") 55 | // 密钥文件 56 | if conf.CertKeyPath != "" && !compile.MatchString(conf.CertKeyPath) { 57 | log.Infof("%s -> %s", conf.CertKeyPath, baseDir+conf.CertKeyPath) 58 | conf.CertKeyPath = baseDir + conf.CertKeyPath 59 | } 60 | // 证书文件 61 | if conf.CertPemPath != "" && !compile.MatchString(conf.CertPemPath) { 62 | log.Infof("%s -> %s", conf.CertPemPath, baseDir+conf.CertPemPath) 63 | conf.CertPemPath = baseDir + conf.CertPemPath 64 | } 65 | 66 | // 日志记录配置 67 | log.SetLevel(log.InfoLevel) 68 | log.SetFormatter(&log.TextFormatter{ 69 | FullTimestamp: true, 70 | ForceColors: true, 71 | TimestampFormat: "2006-01-02 15:04:05.000", 72 | }) 73 | // 在输出日志中添加文件名和方法信息 74 | if appConfig.Natok.LogDebugLevel { 75 | log.SetReportCaller(true) 76 | log.SetLevel(log.DebugLevel) 77 | } 78 | // 日志记录输出文件 79 | if conf.LogFilePath != "" && !compile.MatchString(conf.LogFilePath) { 80 | log.Infof("%s -> %s", conf.LogFilePath, baseDir+conf.LogFilePath) 81 | conf.LogFilePath = baseDir + conf.LogFilePath 82 | logFile, err := os.OpenFile(conf.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 83 | if err != nil { 84 | log.Fatal(err) 85 | } else { 86 | // 组合一下即可,os.Stdout代表标准输出流 87 | multiWriter := io.MultiWriter(logFile, os.Stdout) 88 | log.SetOutput(multiWriter) 89 | } 90 | } 91 | AppConf = appConfig 92 | } 93 | 94 | // 最终方案-全兼容 95 | func getCurrentAbPath() string { 96 | dir := getCurrentAbPathByExecutable() 97 | if strings.Contains(dir, getTmpDir()) { 98 | dir = getCurrentAbPathByCaller() 99 | } 100 | return dir + "/" 101 | } 102 | 103 | // 获取系统临时目录,兼容go run 104 | func getTmpDir() string { 105 | dir := os.Getenv("TEMP") 106 | if dir == "" { 107 | dir = os.Getenv("TMP") 108 | } 109 | res, _ := filepath.EvalSymlinks(dir) 110 | return res 111 | } 112 | 113 | // 获取当前执行文件绝对路径 114 | func getCurrentAbPathByExecutable() string { 115 | exePath, err := os.Executable() 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) 120 | return res 121 | } 122 | 123 | // 获取当前执行文件绝对路径(go run) 124 | func getCurrentAbPathByCaller() string { 125 | var abPath string 126 | if _, filename, _, ok := runtime.Caller(0); ok { 127 | if lst := strings.LastIndex(filename, "/conf"); lst != -1 { 128 | abPath = filename[0:lst] 129 | } 130 | } 131 | return abPath 132 | } 133 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "github.com/kardianos/service" 7 | log "github.com/sirupsen/logrus" 8 | "natok-cli/conf" 9 | "natok-cli/core" 10 | "net" 11 | "os" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | type Program struct{} 17 | 18 | func (p *Program) Start(s service.Service) error { 19 | go p.run() 20 | return nil 21 | } 22 | 23 | func (p *Program) run() { 24 | log.Info("Started natok client service") 25 | Start() 26 | } 27 | 28 | func (p *Program) Stop(s service.Service) error { 29 | log.Info("Stop natok client service") 30 | return nil 31 | } 32 | 33 | // 程序入口 34 | func main() { 35 | svcConfig := &service.Config{ 36 | Name: "natok-cli", 37 | DisplayName: "Natok Client Service", 38 | Description: "Go语言实现的内网代理客户端服务", 39 | } 40 | 41 | prg := &Program{} 42 | s, err := service.New(prg, svcConfig) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | if len(os.Args) > 1 { 48 | if os.Args[1] == "install" { 49 | if se := s.Install(); se != nil { 50 | log.Errorf("Service installation failed. %+v", se) 51 | } else { 52 | log.Info("Service installed") 53 | } 54 | return 55 | } 56 | if os.Args[1] == "uninstall" { 57 | if se := s.Uninstall(); se != nil { 58 | log.Errorf("Service uninstall failed. %+v", se) 59 | } else { 60 | log.Info("Service uninstalled") 61 | } 62 | return 63 | } 64 | if os.Args[1] == "start" { 65 | if se := s.Start(); se != nil { 66 | log.Errorf("Service start failed. %+v", se) 67 | } else { 68 | log.Info("Service startup completed") 69 | } 70 | return 71 | } 72 | if os.Args[1] == "restart" { 73 | if se := s.Restart(); se != nil { 74 | log.Errorf("Service restart failed. %+v", se) 75 | } else { 76 | log.Info("Service restart completed") 77 | } 78 | return 79 | } 80 | if os.Args[1] == "stop" { 81 | if se := s.Stop(); se != nil { 82 | log.Errorf("Service stop failed. %+v", se) 83 | } else { 84 | log.Info("Service stop completed") 85 | } 86 | return 87 | } 88 | } 89 | 90 | if err = s.Run(); err != nil { 91 | log.Fatal(err) 92 | } 93 | } 94 | 95 | // Start 启动主服务 96 | func Start() { 97 | doRun := func(server conf.Server) { 98 | addr := server.InetHost + ":" + strconv.Itoa(server.InetPort) 99 | tlsConfig := TlsConfig() 100 | poolHandler := &core.NatokHandler{ 101 | Conf: &core.NatokConnConfig{ 102 | Addr: addr, 103 | Conf: tlsConfig, 104 | }, 105 | Conns: make([]*core.ConnectHandler, 0, 10), 106 | } 107 | 108 | connHandler := &core.ConnectHandler{Name: "Main"} 109 | 110 | for { 111 | connHandler.Conn = Connect(addr, tlsConfig) 112 | natokServerHandler := &core.NatokServerHandler{ 113 | AccessKey: server.AccessKey, 114 | NatokHandler: poolHandler, 115 | ConnHandler: connHandler, 116 | } 117 | connHandler.MsgHandler = natokServerHandler 118 | 119 | natokServerHandler.HeartBeat() 120 | natokServerHandler.Auth() 121 | connHandler.Listen() 122 | } 123 | } 124 | // 调用 125 | for idx, server := range conf.AppConf.Natok.Server { 126 | go func(ser conf.Server) { doRun(ser) }(server) 127 | log.Infof("Listen: %d, %s", idx+1, server.InetHost) 128 | } 129 | } 130 | 131 | // Connect 向NATOK-SERVER发起连接 132 | func Connect(addr string, conf *tls.Config) net.Conn { 133 | retry := 0 134 | for { 135 | var conn net.Conn 136 | var err error 137 | 138 | if conf != nil { 139 | conn, err = tls.Dial("tcp", addr, conf) 140 | } else { 141 | conn, err = net.Dial("tcp", addr) 142 | } 143 | 144 | if err != nil { 145 | retry += 1 146 | if retry > 1000000 { 147 | retry = 1 148 | } 149 | if retry%30 == 0 { 150 | log.Warnf("Connection to natok server exception! retry: %d Addr: %s, Error: %+v", retry, addr, err) 151 | } 152 | time.Sleep(time.Second * 2) 153 | continue 154 | } 155 | 156 | return conn 157 | } 158 | } 159 | 160 | // TlsConfig TSL协议配置 161 | func TlsConfig() *tls.Config { 162 | tlsConf := conf.AppConf.Natok 163 | cert, err := tls.LoadX509KeyPair(tlsConf.CertPemPath, tlsConf.CertKeyPath) 164 | if err != nil { 165 | log.Error(err) 166 | } 167 | certBytes, err := os.ReadFile(tlsConf.CertPemPath) 168 | if err != nil { 169 | log.Fatal("Unable to read cert.pem") 170 | } 171 | clientCertPool := x509.NewCertPool() 172 | ok := clientCertPool.AppendCertsFromPEM(certBytes) 173 | if !ok { 174 | log.Fatal("failed to parse root certificate") 175 | } 176 | return &tls.Config{ 177 | RootCAs: clientCertPool, 178 | Certificates: []tls.Certificate{cert}, 179 | InsecureSkipVerify: true, 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /core/natok_server_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/binary" 6 | "github.com/kataras/golog" 7 | "net" 8 | "os" 9 | "time" 10 | ) 11 | 12 | // NatokServerHandler struct NATOK服务处理 13 | type NatokServerHandler struct { 14 | Chan chan struct{} 15 | AccessKey string 16 | PoolHandler *PoolHandler 17 | ConnHandler *ConnectHandler 18 | } 19 | 20 | // NatokConfig struct 地址配置 21 | type NatokConfig struct { 22 | Addr string 23 | Conf *tls.Config 24 | } 25 | 26 | // Encode 编码消息 27 | func (n *NatokServerHandler) Encode(inMsg interface{}) []byte { 28 | if inMsg == nil { 29 | return []byte{} 30 | } 31 | msg := inMsg.(Message) 32 | buf := make([]byte, 8) 33 | binary.BigEndian.PutUint64(buf, msg.SerialNum) 34 | 35 | uriBytes := []byte(msg.Uri) 36 | bodyLen := TypeSize + SerialNumSize + UriLenSize + len(uriBytes) + len(msg.Data) 37 | 38 | data := make([]byte, HeaderSize, bodyLen+HeaderSize) 39 | binary.BigEndian.PutUint32(data, uint32(bodyLen)) 40 | 41 | data = append(data, msg.Type) 42 | data = append(data, buf...) 43 | data = append(data, byte(len(uriBytes))) 44 | data = append(data, uriBytes...) 45 | data = append(data, msg.Data...) 46 | return data 47 | } 48 | 49 | // Decode 解码消息 50 | func (n *NatokServerHandler) Decode(buf []byte) (interface{}, int) { 51 | HeaderBytes := buf[0:HeaderSize] 52 | HeaderLen := binary.BigEndian.Uint32(HeaderBytes) 53 | 54 | if uint32(len(buf)) < HeaderLen+HeaderSize { 55 | return nil, 0 56 | } 57 | 58 | num := int(HeaderLen + HeaderSize) 59 | body := buf[HeaderSize:num] 60 | 61 | uriLen := uint8(body[SerialNumSize+TypeSize]) 62 | msg := Message{ 63 | Type: body[0], 64 | SerialNum: binary.BigEndian.Uint64(body[TypeSize : SerialNumSize+TypeSize]), 65 | Uri: string(body[SerialNumSize+TypeSize+UriLenSize : SerialNumSize+TypeSize+UriLenSize+uriLen]), 66 | Data: body[SerialNumSize+TypeSize+UriLenSize+uriLen:], 67 | } 68 | return msg, num 69 | } 70 | 71 | // Receive 请求接收 72 | func (n *NatokServerHandler) Receive(connHandler *ConnectHandler, msgData interface{}) { 73 | msg := msgData.(Message) 74 | //golog.Println("Received connect message:", msg.Uri, "=>", string(msg.Data)) 75 | switch msg.Type { 76 | case TypeConnect: 77 | go func() { 78 | intraServerHandler := &IntraServerHandler{ 79 | Uri: msg.Uri, 80 | natokHandler: connHandler, 81 | AccessKey: n.AccessKey, 82 | PoolHandler: n.PoolHandler, 83 | } 84 | addr := string(msg.Data) 85 | conn, err := net.Dial("tcp", addr) 86 | if err != nil { 87 | golog.Errorf("Failed to connect intranet server! %+v", err) 88 | intraServerHandler.Failure() 89 | } else { 90 | connectHandler := &ConnectHandler{Conn: conn} 91 | connectHandler.Listen(conn, intraServerHandler) 92 | } 93 | }() 94 | case TypeTransfer: 95 | if connHandler.ConnHandler != nil { 96 | connHandler.ConnHandler.Write(msg.Data) 97 | } 98 | case TypeDisconnect: 99 | if connHandler.ConnHandler != nil { 100 | connHandler.ConnHandler.Conn.Close() 101 | connHandler.ConnHandler = nil 102 | } 103 | if n.AccessKey == "" { 104 | n.PoolHandler.Push(connHandler) 105 | } 106 | case typeNoAvailablePort: 107 | golog.Warn("There are no available ports for the natok access key.") 108 | case TypeDisabledAccessKey: 109 | golog.Info("Natok access key is disabled.") 110 | n.Close(connHandler) 111 | os.Exit(1) 112 | case TypeInvalidKey: 113 | golog.Info("Natok access key is not valid.") 114 | n.Close(connHandler) 115 | os.Exit(1) 116 | case TypeIsInuseKey: 117 | golog.Info("Natok access key is in use by other natok client.") 118 | golog.Info("If you want to have exclusive natok service") 119 | golog.Info("please visit 'www.natok.cn' for more details.") 120 | n.Close(connHandler) 121 | os.Exit(1) 122 | case TypeDisabledTrialClient: 123 | golog.Info("Your natok client is overuse.") 124 | golog.Info("The trial natok access key can only be used for 20 minutes in 24 hours.") 125 | golog.Info("If you want to have exclusive natok service") 126 | golog.Info("please visit 'www.natok.cn' for more details.") 127 | n.Close(connHandler) 128 | os.Exit(1) 129 | } 130 | } 131 | 132 | // Success 认证成功 133 | func (n *NatokServerHandler) Success(connHandler *ConnectHandler) { 134 | if n.AccessKey == "" { 135 | return 136 | } 137 | msg := Message{Type: TypeAuth, Uri: n.AccessKey} 138 | connHandler.Write(msg) 139 | } 140 | 141 | // Error 错误处理 142 | func (n *NatokServerHandler) Error(connHandler *ConnectHandler) { 143 | if n.Chan != nil { 144 | close(n.Chan) 145 | } 146 | handler := connHandler.ConnHandler 147 | if handler != nil { 148 | if handler.Conn != nil { 149 | handler.Conn.Close() 150 | handler.Conn = nil 151 | } 152 | handler.ConnHandler = nil 153 | } 154 | connHandler.ConnHandler = nil 155 | connHandler.MsgHandler = nil 156 | time.Sleep(time.Second * 3) 157 | } 158 | 159 | // Close 关闭连接通道 160 | func (n *NatokServerHandler) Close(connHandler *ConnectHandler) { 161 | if n.Chan != nil { 162 | close(n.Chan) 163 | } 164 | if connHandler.ConnHandler != nil { 165 | if connHandler.ConnHandler.Conn != nil { 166 | connHandler.ConnHandler.Conn.Close() 167 | connHandler.ConnHandler.Conn = nil 168 | connHandler.ConnHandler.ConnHandler = nil 169 | } 170 | connHandler.ConnHandler = nil 171 | //handler.Conn.Close() 172 | } 173 | if n.ConnHandler != nil && n.ConnHandler.Conn != nil { 174 | n.ConnHandler.Conn.Close() 175 | n.ConnHandler.Conn = nil 176 | } 177 | n.ConnHandler = nil 178 | connHandler.MsgHandler = nil 179 | } 180 | 181 | // HeartBeat 发送心跳包 -> NATOK-SERVER 182 | func (n *NatokServerHandler) HeartBeat() { 183 | n.Chan = make(chan struct{}) 184 | go func() { 185 | for { 186 | select { 187 | case <-time.After(time.Second * HeartbeatInterval): 188 | if time.Now().Unix()-n.ConnHandler.ReadTime >= 2*TypeHeartbeat { 189 | golog.Error("Natok connection timeout") 190 | if n.ConnHandler != nil && n.ConnHandler.Conn != nil { 191 | n.ConnHandler.Conn.Close() 192 | } 193 | return 194 | } 195 | msg := Message{Type: TypeHeartbeat} 196 | n.ConnHandler.Write(msg) 197 | case <-n.Chan: 198 | return 199 | } 200 | } 201 | }() 202 | } 203 | -------------------------------------------------------------------------------- /core/natok_handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/binary" 6 | "fmt" 7 | log "github.com/sirupsen/logrus" 8 | "net" 9 | "os" 10 | "time" 11 | ) 12 | 13 | // NatokServerHandler struct NATOK服务处理 14 | type NatokServerHandler struct { 15 | Chan chan struct{} 16 | AccessKey string //密钥 17 | source string //来源 18 | target string //目标 19 | NatokHandler *NatokHandler 20 | ConnHandler *ConnectHandler 21 | } 22 | 23 | // NatokConfig struct 地址配置 24 | type NatokConfig struct { 25 | Addr string 26 | Conf *tls.Config 27 | } 28 | 29 | // NatokHandler struct // Natok句柄 30 | type NatokHandler struct { 31 | count Counter //数量 32 | Conf *NatokConnConfig //配置 33 | Conns []*ConnectHandler //连接 34 | } 35 | 36 | type NatokConnConfig struct { 37 | Addr string 38 | Conf *tls.Config 39 | } 40 | 41 | // Get 获取连接 42 | func (p *NatokConnConfig) Get() (*ConnectHandler, error) { 43 | var conn net.Conn 44 | var err error 45 | retries := 5 46 | dialer := net.Dialer{Timeout: 2 * time.Second} 47 | for tries := 0; tries <= retries; tries++ { 48 | if p.Conf != nil { 49 | conn, err = tls.DialWithDialer(&dialer, "tcp", p.Addr, p.Conf) 50 | } else { 51 | conn, err = dialer.Dial("tcp", p.Addr) 52 | } 53 | if err == nil { 54 | break 55 | } 56 | if tries > 0 { 57 | log.Warnf("Connect natok-server failed, Retries: %d/%d, Error: %+v", tries, retries, err) 58 | } 59 | time.Sleep(200 * time.Millisecond) 60 | } 61 | // 如果未连接成功 62 | if err != nil { 63 | log.Errorf("Connect natok-server failed, Error: %+v", err) 64 | return nil, err 65 | } 66 | connHandler := &ConnectHandler{ 67 | Name: "natok-server-子集", 68 | Active: true, 69 | Conn: conn, 70 | } 71 | return connHandler, nil 72 | } 73 | 74 | // Encode 编码消息 75 | func (s *NatokServerHandler) Encode(inMsg interface{}) []byte { 76 | if inMsg == nil { 77 | return []byte{} 78 | } 79 | msg := inMsg.(Message) 80 | serialBytes := []byte(msg.Serial) 81 | netBytes := []byte(msg.Net) 82 | UriBytes := []byte(msg.Uri) 83 | // byte=Uint8Size,3个string=Uint8Size*3,+data 84 | dataLen := Uint8Size + Uint8Size*3 + len(serialBytes) + len(netBytes) + len(UriBytes) + len(msg.Data) 85 | data := make([]byte, Uint32Size, Uint32Size+dataLen) 86 | binary.BigEndian.PutUint32(data, uint32(dataLen)) 87 | 88 | data = append(data, msg.Type) 89 | data = append(data, byte(len(serialBytes))) 90 | data = append(data, byte(len(netBytes))) 91 | data = append(data, byte(len(UriBytes))) 92 | data = append(data, serialBytes...) 93 | data = append(data, netBytes...) 94 | data = append(data, UriBytes...) 95 | data = append(data, msg.Data...) 96 | return data 97 | } 98 | 99 | // Decode 解码消息 100 | func (s *NatokServerHandler) Decode(buf []byte) (interface{}, int) { 101 | headerBytes := buf[0:Uint32Size] 102 | headerLen := binary.BigEndian.Uint32(headerBytes) 103 | // 来自客户端的包,校验完整性。 104 | if uint32(len(buf)) < headerLen+Uint32Size { 105 | return nil, 0 106 | } 107 | 108 | head := int(Uint32Size + headerLen) 109 | body := buf[Uint32Size:head] 110 | serialLen := int(body[Uint8Size]) 111 | netLen := int(body[Uint8Size*2]) 112 | uriLen := int(body[Uint8Size*3]) 113 | msg := Message{ 114 | Type: body[0], 115 | Serial: string(body[Uint8Size*4 : Uint8Size*4+serialLen]), 116 | Net: string(body[Uint8Size*4+serialLen : Uint8Size*4+serialLen+netLen]), 117 | Uri: string(body[Uint8Size*4+serialLen+netLen : Uint8Size*4+serialLen+netLen+uriLen]), 118 | Data: body[Uint8Size*4+serialLen+netLen+uriLen:], 119 | } 120 | return msg, head 121 | } 122 | 123 | // Receive 请求接收 124 | func (s *NatokServerHandler) Receive(connHandler *ConnectHandler, msgData interface{}) { 125 | msg := msgData.(Message) 126 | //log.Println("Received connect message:", msg.Uri, "=>", string(msg.Data)) 127 | switch msg.Type { 128 | // 连接到natok服务 129 | case TypeConnectNatok: 130 | go func() { 131 | log.Debugf("1-1 ===== From natok server message: %s %s", msg.Serial, string(msg.Data)) 132 | if natokHandler, err := s.NatokHandler.Conf.Get(); err == nil { 133 | natokServerHandler := &NatokServerHandler{ 134 | AccessKey: s.AccessKey, 135 | ConnHandler: natokHandler, 136 | } 137 | log.Debugf("1-2 =====Connect natok, Listen natok server message: %s %s", msg.Serial, string(msg.Data)) 138 | natokHandler.MsgHandler = natokServerHandler 139 | natokServerHandler.HeartBeat() 140 | natokHandler.Write(Message{Type: TypeConnectNatok, Serial: msg.Serial, Uri: s.AccessKey}) 141 | natokHandler.Listen() 142 | log.Debugf("1-3 =====Disconnect natok, Listen natok server message: %s %s", msg.Serial, string(msg.Data)) 143 | } else { 144 | log.Errorf("1-e =====Connect natok server failed, Message: %s %s, Error: %+v", msg.Serial, string(msg.Data), err) 145 | } 146 | }() 147 | // 连接到内部服务 148 | case TypeConnectIntra: 149 | go func() { 150 | network := msg.Net 151 | addr := string(msg.Data) 152 | s.source = fmt.Sprintf("%s://%s", network, addr) 153 | s.target = msg.Uri 154 | sprintf := fmt.Sprintf("%s %s -> %s", msg.Serial, s.source, s.target) 155 | log.Debugf("2-1 ===== From natok server message: %s", sprintf) 156 | if conn, err := net.Dial(network, addr); err == nil { 157 | intraHandler := &ConnectHandler{Name: network + addr, Conn: conn, Active: true, ConnHandler: connHandler} 158 | intraHandler.MsgHandler = &IntraServerHandler{ 159 | Uri: msg.Uri, 160 | AccessKey: s.AccessKey, 161 | connectHandler: connHandler, 162 | } 163 | connHandler.ConnHandler = intraHandler 164 | connHandler.Write(Message{Type: TypeConnectIntra, Serial: msg.Serial, Uri: s.AccessKey}) 165 | log.Debugf("2-2 =====Connect intranet, Listen natok server message: %s", sprintf) 166 | intraHandler.Listen() 167 | log.Debugf("2-3 =====Disconnect intranet, Listen natok server message: %s", sprintf) 168 | } else { 169 | log.Errorf("2-e =====Connect intranet server failed, Message: %s, Error: %+v", sprintf, err) 170 | } 171 | }() 172 | // 传输数据 - 转发内部服务 173 | case TypeTransfer: 174 | sprintf := fmt.Sprintf("%s %s -> %s", msg.Serial, s.source, s.target) 175 | log.Debugf("3-1 =====TypeTransfer natok server message: %s", sprintf) 176 | if conn := connHandler.ConnHandler; conn != nil { 177 | log.Debugf("3-2 =====TypeTransfer intranet server message: %s", sprintf) 178 | conn.Write(msg.Data) 179 | } 180 | // 关闭连接 - 断开内部服务 181 | case TypeDisconnect: 182 | sprintf := fmt.Sprintf("%s %s -> %s", msg.Serial, s.source, s.target) 183 | log.Debugf("4-1 =====TypeDisconnect natok server message: %s", sprintf) 184 | if conn := connHandler.ConnHandler; conn != nil { 185 | _ = conn.Conn.Close() 186 | connHandler.ConnHandler = nil 187 | } 188 | case typeNoAvailablePort: 189 | log.Warnf("Natok access key %s no available ports.", msg.Uri) 190 | case TypeDisabledAccessKey: 191 | log.Warnf("Natok access key %s is disabled.", msg.Uri) 192 | case TypeInvalidKey: 193 | log.Errorf("Natok access key %s is not valid.", msg.Uri) 194 | s.Close(connHandler) 195 | os.Exit(1) 196 | case TypeIsInuseKey: 197 | log.Warnf("Natok access key %s is in use by other natok client.", msg.Uri) 198 | s.Close(connHandler) 199 | os.Exit(1) 200 | case TypeDisabledTrialClient: 201 | log.Infof("Natok access key %s is overuse.", msg.Uri) 202 | s.Close(connHandler) 203 | os.Exit(1) 204 | } 205 | } 206 | 207 | // Auth 认证成功 208 | func (s *NatokServerHandler) Auth() { 209 | if s.AccessKey == "" { 210 | return 211 | } 212 | msg := Message{Type: TypeAuth, Serial: "1", Net: "tcp", Uri: s.AccessKey, Data: []byte("8888")} 213 | s.ConnHandler.Write(msg) 214 | } 215 | 216 | // Error 错误处理 217 | func (s *NatokServerHandler) Error(connHandler *ConnectHandler) { 218 | if s.Chan != nil { 219 | close(s.Chan) 220 | } 221 | intraHandler := connHandler.ConnHandler 222 | if intraHandler != nil { 223 | if conn := intraHandler.Conn; conn != nil { 224 | _ = conn.Close() 225 | } 226 | intraHandler.ConnHandler = nil 227 | } 228 | connHandler.ConnHandler = nil 229 | connHandler.MsgHandler = nil 230 | time.Sleep(time.Second * 3) 231 | } 232 | 233 | // Close 关闭连接通道 234 | func (s *NatokServerHandler) Close(connHandler *ConnectHandler) { 235 | if s.Chan != nil { 236 | close(s.Chan) 237 | } 238 | if intraHandler := connHandler.ConnHandler; intraHandler != nil { 239 | if intraHandler.Conn != nil { 240 | intraHandler.Active = false 241 | _ = intraHandler.Conn.Close() 242 | intraHandler.Conn = nil 243 | intraHandler.ConnHandler = nil 244 | } 245 | connHandler.ConnHandler = nil 246 | } 247 | if connHandler.Conn != nil { 248 | connHandler.Active = false 249 | _ = connHandler.Conn.Close() 250 | connHandler.Conn = nil 251 | } 252 | s.ConnHandler = nil 253 | connHandler.MsgHandler = nil 254 | } 255 | 256 | // HeartBeat 发送心跳包 -> NATOK-SERVER 257 | func (s *NatokServerHandler) HeartBeat() { 258 | s.Chan = make(chan struct{}) 259 | go func() { 260 | for { 261 | now := time.Now() 262 | select { 263 | case <-time.After(10 * time.Second): 264 | // 若通道在30s内未收到过数据,则发送一次心跳包。 265 | if now.Sub(s.ConnHandler.ReadTime) >= 30*time.Second { 266 | msg := Message{Type: TypeHeartbeat, Uri: s.AccessKey} 267 | s.ConnHandler.Write(msg) 268 | } 269 | case <-s.Chan: 270 | return 271 | } 272 | } 273 | }() 274 | } 275 | -------------------------------------------------------------------------------- /grid-snake.svg: -------------------------------------------------------------------------------- 1 | Generated with https://github.com/Platane/snk --------------------------------------------------------------------------------