├── CHANGELOG ├── Images ├── mutil_forward.png ├── portforward_framework.png ├── restricted_forward.png └── simple_forward.png ├── LICENSE ├── README.md ├── build.sh ├── forward.go ├── go.mod ├── log.go ├── main.go ├── tcp.go └── udp.go /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Released] 8 | 9 | ## [0.5.0] - 2020-10-22 10 | ### Added 11 | - Refactoring source code strucure for StarLink 12 | 13 | ## [Unreleased] 14 | 15 | ## [0.5.1] - 2021-04-23 16 | ### Fixed 17 | - In listen-listen mode, actively close the previous socket when there are 18 | multiple connections to the same end 19 | 20 | ## [0.4.1] - 2020-10-16 21 | ### Added 22 | - Add multi-channel support of conn-conn mode 23 | ### Fixed 24 | - Modify timeout of unsuccessfully established channel in listen-listen 25 | work mode from 16s => 120s 26 | 27 | ## [0.4] - 2020-09-25 28 | ### Added 29 | - Add multi-channel support 30 | ### Changed 31 | - Optimize code structure and implementation 32 | 33 | ## [0.3] - 2020-09-20 34 | ### Added 35 | - Refactoring the project framework into a three-layer structure: 36 | work_mode / protocol / socket 37 | 38 | ## [0.2] - 2020-09-10 39 | ### Added 40 | - Refactoring project framework 41 | 42 | ## [0.1] - 2020-08-19 43 | ### Added 44 | - Initialize project 45 | -------------------------------------------------------------------------------- /Images/mutil_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knownsec/PortForward/968da050095b33f199c2e25a1967dd624be8369c/Images/mutil_forward.png -------------------------------------------------------------------------------- /Images/portforward_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knownsec/PortForward/968da050095b33f199c2e25a1967dd624be8369c/Images/portforward_framework.png -------------------------------------------------------------------------------- /Images/restricted_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knownsec/PortForward/968da050095b33f199c2e25a1967dd624be8369c/Images/restricted_forward.png -------------------------------------------------------------------------------- /Images/simple_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knownsec/PortForward/968da050095b33f199c2e25a1967dd624be8369c/Images/simple_forward.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Knownsec, Inc. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PortForward 2 | 3 | ## 0x00 前言 4 | `PortForward` 是使用 Golang 进行开发的端口转发工具,解决在某些场景下 `内外网无法互通`的问题。 5 | 6 | `PortForward` 的功能如下: 7 | 8 | 1. 支持 tcp/udp 协议层的端口转发 9 | 2. 支持级联 10 | 3. 支持正向/反向连接模式 11 | 4. 支持多通道 12 | 5. 支持 ipv6 13 | 14 | 本文对 `PortForward` 进行了详细的介绍。 15 | 16 | 目录: 17 | 18 | 1. 使用说明 19 | 2. 工作场景 20 | 3. 源码结构 21 | 4. 逻辑结构 22 | 5. 端口转发的实现 23 | 6. udp的knock报文 24 | 7. udp的超时设置 25 | 8. listen-listen的超时设置 26 | 9. 多通路的实现 27 | 10. issue 28 | 11. contributions 29 | 30 | ## 0x01 使用说明 31 | **1.使用** 32 | 33 | Usage: 34 | ./portforward [proto] [sock1] [sock2] 35 | Option: 36 | proto the port forward with protocol(tcp/udp) 37 | sock format: [method:address:port] 38 | method the sock mode(listen/conn) 39 | Example: 40 | tcp conn:192.168.1.1:3389 conn:192.168.1.10:23333 41 | udp listen:192.168.1.3:5353 conn:8.8.8.8:53 42 | tcp listen:[fe80::1%lo0]:8888 conn:[fe80::1%lo0]:7777 43 | 44 | version: 0.5.0(build-20201022) 45 | 46 | **2.编译** 47 | 48 | Golang 1.12及以上 49 | GO111MODULE=on 50 | 51 | git clone https://github.com/knownsec/Portforward.git 52 | ./build.sh 53 | 54 | 55 | ## 0x02 工作场景 56 | 这里列举了一些 `PortForward` 的工作场景,如下: 57 | 58 | **2.1 简单模式** 59 |
60 | 61 |
[图1.简单转发模式] 62 |
63 | 64 | **2.2 受限主机转发** 65 |
66 | 67 |
[图2.受限主机转发模式图] 68 |
69 | 70 | **2.3 级联端口转发** 71 |
72 | 73 |
[图3.级联端口转发] 74 |
75 | 76 | 77 | ## 0x03 源码结构 78 | 79 | . 80 | ├── CHANGELOG 81 | ├── Images // images resource 82 | ├── README.md 83 | ├── build.sh // compile script 84 | ├── forward.go // portforward main logic 85 | ├── go.mod 86 | ├── log.go // log module 87 | ├── main.go // main, parse arguments 88 | ├── tcp.go // tcp layer 89 | └── udp.go // udp layer 90 | 91 | 92 | ## 0x04 逻辑结构 93 | `PortForward` 支持 `TCP` , `UDP` 协议层的端口转发,代码抽象后逻辑结构框架如下: 94 |
95 | 96 |
[图4.整体框架] 97 |
98 | 99 | 100 | ## 0x05 端口转发的实现 101 | 端口转发程序作为网络传输的中间人,无非就是将两端的 socket 对象进行联通,数据就可以通过这条「链路」进行传输了。 102 | 103 | 按照这样的思路,我们从需求开始分析和抽象,可以这么认为:无论是作为 `tcp` 还是 `udp` 运行,无论是作为 `connect` 还是 `listen` 运行,最终都将获得两个 socket,其中一个连向原服务,另一个与客户端连接;最终将这两端的 socket 联通即可实现端口转发。 104 | 105 | 在 Golang 中我们采用了 `io.Copy()` 来联通两个 socket,但是 `io.Copy` 必须要求对象实现了 `io.Reader/io.Writer` 接口,`tcp` 的 socket 可以直接支持,而 `udp` 的 socket 需要我们进行封装。 106 | 107 | 108 | ## 0x06 udp的knock报文 109 | 在 `udp` 的 `connect` 模式下,我们在连接服务器成功后,立即发送了一个 `knock` 报文,如下: 110 | 111 | conn, err := net.DialTimeout("udp", ... 112 | _, err = conn.Write([]byte("\x00")) 113 | 114 | 其作用是通知远程 `udp` 服务器我们已经连上了(`udp` 创建连接后,仅在本地操作系统层进行了注册,只有当发送一个报文到对端后,远程服务器才能感知到新连接),当我们在 `udp` 的 `conn-conn` 模式下运行时,这个报文是必须的。 115 | 116 | 117 | ## 0x07 udp的超时设置 118 | 在 `udp` 的实现中,我们为所有的 `udp` 连接 socket 对象都设置了超时时间(`tcp` 中不需要),这是因为在 `udp` 中,socket 对象无法感知对端退出,如果不设置超时时间,将会一直在 `conn.Read()` 阻塞下去。 119 | 120 | 我们设置了 `udp` 超时时间为 60 秒,当 60 秒无数据传输,本次建立的虚拟通信链路将销毁,端口转发程序将重新创建新的通信链路。 121 | 122 | 123 | ## 0x08 listen-listen的超时设置 124 | 对于 `listen-listen` 模式,需要等待两端的客户端都连上端口转发程序后,才能将两个 socket 进行联通。 125 | 126 | 为此我们在此处设置了 120 秒的超时时间,也就是说当其中一端有客户端连接后,另一端在 120 秒内没有连接,我们就销毁这个未成功建立的通信链路;用户重新连接即可。 127 | 128 | >如果没有这个超时,可能某些场景遗留了某个连接,将造成后续的通信链路错位。 129 | 130 | 131 | ## 0x09 多通路的实现 132 | 多通路可以支持同时发起多个连接,这里我们以 `tcp` 作为例子来说明。为了处理这种情况,我们的处理方式是: 133 | 134 | 1. `listen-conn`: 每当 listen 服务器接收到新连接后,与远端创建新的连接,并将两个 socket 进行联通。 135 | 2. `listen-listen`: (好像没有实际场景)两端的 listen 服务器接收到新连接后,将两个 socket 进行联通。 136 | 3. `conn-conn`: 创建 sock1 的连接,当 sock1 端收到数据,创建与 sock2 的连接,将两个 socket 进行联通;随后继续创建 sock1 连接(预留)。 137 | 138 | >我们在 `udp` 中也加入了多通路的支持,和 `tcp` 基本类似,但由于 `udp` 是无连接的,我们不能像 `tcp` 直接联通两个 socket 对象。我们在 `udp listen` 服务器中维护了一个临时表,使用 `ip:port` 作为标志,以描述各个通信链路的联通情况,依据此进行流量的分发。 139 | 140 | 141 | ## 0x0A issue 142 | **1.udp的映射表未清空** 143 | udp多通路中的映射表没有对无效数据进行清理,长时间运行可能造成内存占用 144 | 145 | **2.http服务的 host 字段影响** 146 | 当转发 http 服务时,由于常见的客户端在访问时自动将 `host` 设置为访问目标(端口转发程序的地址),我们直接对流量进行转发,那么 `host` 字段一定是错误的,某些 http 服务器对该字段进行了校验,所以无法正常转发。 147 | 148 | >比如端口转发 `www.baidu.com` 149 | 150 | **3.不支持多端口通信** 151 | 这里当然无法处理多端口通信的服务,我们仅做了一个端口的转发,多个端口的情况无法直接处理,比如 `FTP` 服务,其分别使用 20 端口(数据传输)和 21 端口(命令传输)。 152 | 153 | 但还是要单独说一句,对于大多数 `udp` 服务的实现,是 `udp` 服务器收到了一个新报文,对此进行处理后,然后使用新的 socket 将响应数据发给对端,比如 `TFTP` 服务: 154 | 155 | 1.client 发起请求 [get aaa] 156 | src: 55123 => dst: 69 157 | 2.tftp 收到报文,进行处理,返回响应: 158 | src: 61234 => dst: 55123 159 | 160 | 这种多端口通信我们也是无法处理的,因为在目前多通路的实现下,上述流程中第 2 步过后,对于端口转发程序来说,后续的报文无法确定转发给 69 端口还是 61234 端口。 161 | 162 | 163 | ## 0x0B contributions 164 | 165 | [r0oike@knownsec 404](https://github.com/r0oike) 166 | [fenix@knownsec 404](https://github.com/13ph03nix) 167 | [0x7F@knownsec 404](https://github.com/0x7Fancy) 168 | 169 |
170 | 171 | ------------------------------ 172 | References: 173 | 内网渗透之端口转发、映射、代理: 174 | 内网渗透之端口转发与代理工具总结: 175 | sensepost/reGeorg: 176 | idlefire/ew: 177 | 178 | knownsec404 179 | 2020.10.22 -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | go build -o portforward . 2 | -------------------------------------------------------------------------------- /forward.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Filename: forward.go 3 | * Description: the PortForward main logic implement, Three working modes are 4 | * provided: 5 | * 1.Conn<=>Conn: dial A remote server, dial B remote server, connected 6 | * 2.Listen<=>Conn: listen local server, dial remote server, connected 7 | * 3.Listen<=>Listen: listen A local server, listen B local server, connected 8 | * Author: knownsec404 9 | * Time: 2020.09.23 10 | */ 11 | 12 | package main 13 | 14 | import ( 15 | "io" 16 | "net" 17 | "time" 18 | ) 19 | 20 | // 21 | const PORTFORWARD_PROTO_NIL uint8 = 0x00 22 | const PORTFORWARD_PROTO_TCP uint8 = 0x10 23 | const PORTFORWARD_PROTO_UDP uint8 = 0x20 24 | // 25 | const PORTFORWARD_SOCK_NIL uint8 = 0x00 26 | const PORTFORWARD_SOCK_LISTEN uint8 = 0x01 27 | const PORTFORWARD_SOCK_CONN uint8 = 0x02 28 | 29 | // the PortForward network interface 30 | type Conn interface { 31 | // Read reads data from the connection. 32 | Read(b []byte) (n int, err error) 33 | // Write writes data to the connection. 34 | Write(b []byte) (n int, err error) 35 | // Close closes the connection. 36 | Close() (error) 37 | // RemoteAddr returns the remote network address. 38 | RemoteAddr() (net.Addr) 39 | } 40 | 41 | // the PortForward launch arguemnt 42 | type Args struct { 43 | Protocol uint8 44 | // sock1 45 | Method1 uint8 46 | Addr1 string 47 | // sock2 48 | Method2 uint8 49 | Addr2 string 50 | } 51 | 52 | var stop chan bool = nil 53 | 54 | /********************************************************************** 55 | * @Function: Launch(args Args) 56 | * @Description: launch PortForward working mode by arguments 57 | * @Parameter: args Args, the launch arguments 58 | * @Return: nil 59 | **********************************************************************/ 60 | func Launch(args Args) { 61 | // initialize stop channel, the maximum number of coroutines that 62 | // need to be managed is 2 (listen-listen) 63 | stop = make(chan bool, 3) 64 | 65 | // 66 | if args.Method1 == PORTFORWARD_SOCK_CONN && 67 | args.Method2 == PORTFORWARD_SOCK_CONN { 68 | // sock1 conn, sock2 conn 69 | ConnConn(args.Protocol, args.Addr1, args.Addr2) 70 | } else if args.Method1 == PORTFORWARD_SOCK_CONN && 71 | args.Method2 == PORTFORWARD_SOCK_LISTEN { 72 | // sock1 conn, sock2 listen 73 | ListenConn(args.Protocol, args.Addr2, args.Addr1) 74 | } else if args.Method1 == PORTFORWARD_SOCK_LISTEN && 75 | args.Method2 == PORTFORWARD_SOCK_CONN { 76 | // sock1 listen, sock2 conn 77 | ListenConn(args.Protocol, args.Addr1, args.Addr2) 78 | } else if args.Method1 == PORTFORWARD_SOCK_LISTEN && 79 | args.Method2 == PORTFORWARD_SOCK_LISTEN { 80 | // sock1 listen , sock2 listen 81 | ListenListen(args.Protocol, args.Addr1, args.Addr2) 82 | } else { 83 | LogError("unknown forward method") 84 | return 85 | } 86 | } 87 | 88 | 89 | /********************************************************************** 90 | * @Function: Shutdown() 91 | * @Description: the shutdown PortForward 92 | * @Parameter: nil 93 | * @Return: nil 94 | **********************************************************************/ 95 | func Shutdown() { 96 | stop <- true 97 | } 98 | 99 | 100 | /********************************************************************** 101 | * @Function: ListenConn(proto uint8, addr1 string, addr2 string) 102 | * @Description: "Listen<=>Conn" working mode 103 | * @Parameter: proto uint8, the tcp or udp protocol setting 104 | * @Parameter: addr1 string, the address1 "ip:port" string 105 | * @Parameter: addr2 string, the address2 "ip:port" string 106 | * @Return: nil 107 | **********************************************************************/ 108 | func ListenConn(proto uint8, addr1 string, addr2 string) { 109 | // get sock launch function by protocol 110 | sockfoo1 := ListenTCP 111 | if proto == PORTFORWARD_PROTO_UDP { 112 | sockfoo1 = ListenUDP 113 | } 114 | sockfoo2 := ConnTCP 115 | if proto == PORTFORWARD_PROTO_UDP { 116 | sockfoo2 = ConnUDP 117 | } 118 | 119 | // launch socket1 listen 120 | clientc := make(chan Conn) 121 | quit := make(chan bool, 1) 122 | LogInfo("listen A point with sock1 [%s]", addr1) 123 | go sockfoo1(addr1, clientc, quit) 124 | 125 | var count int = 1 126 | for { 127 | // socket1 listen & quit signal 128 | var sock1 Conn = nil 129 | select { 130 | case <-stop: 131 | quit <- true 132 | return 133 | case sock1 = <-clientc: 134 | if sock1 == nil { 135 | // set stop flag when error happend 136 | stop <- true 137 | continue 138 | } 139 | } 140 | LogInfo("A point(link%d) [%s] is ready", count, sock1.RemoteAddr()) 141 | // socket2 dial 142 | LogInfo("dial B point with sock2 [%s]", addr2) 143 | sock2, err := sockfoo2(addr2) 144 | if err != nil { 145 | sock1.Close() 146 | LogError("%s", err) 147 | continue 148 | } 149 | LogInfo("B point(sock2) is ready") 150 | 151 | // connect with sockets 152 | go ConnectSock(count, sock1, sock2) 153 | count += 1 154 | } // end for 155 | } 156 | 157 | 158 | /********************************************************************** 159 | * @Function: ListenListen(proto uint8, addr1 string, addr2 string) 160 | * @Description: the "Listen<=>Listen" working mode 161 | * @Parameter: proto uint8, the tcp or udp protocol setting 162 | * @Parameter: addr1 string, the address1 "ip:port" string 163 | * @Parameter: addr2 string, the address2 "ip:port" string 164 | * @Return: nil 165 | **********************************************************************/ 166 | func ListenListen(proto uint8, addr1 string, addr2 string) { 167 | release := func(s1 Conn, s2 Conn) { 168 | if s1 != nil { 169 | s1.Close() 170 | } 171 | if s2 != nil { 172 | s2.Close() 173 | } 174 | } 175 | 176 | // get sock launch function by protocol 177 | sockfoo := ListenTCP 178 | if proto == PORTFORWARD_PROTO_UDP { 179 | sockfoo = ListenUDP 180 | } 181 | 182 | // launch socket1 listen 183 | clientc1 := make(chan Conn) 184 | quit1 := make(chan bool, 1) 185 | LogInfo("listen A point with sock1 [%s]", addr1) 186 | go sockfoo(addr1, clientc1, quit1) 187 | // launch socket2 listen 188 | clientc2 := make(chan Conn) 189 | quit2 := make(chan bool, 1) 190 | LogInfo("listen B point with sock2 [%s]", addr2) 191 | go sockfoo(addr2, clientc2, quit2) 192 | 193 | var sock1 Conn = nil 194 | var sock2 Conn = nil 195 | var count int = 1 196 | for { 197 | select { 198 | case <-stop: 199 | quit1 <- true 200 | quit2 <- true 201 | release(sock1, sock2) 202 | return 203 | case c1 := <-clientc1: 204 | if c1 == nil { 205 | // set stop flag when error happend 206 | stop <- true 207 | continue 208 | } 209 | // close the last pending sock1 210 | if sock1 != nil { 211 | sock1.Close() 212 | } 213 | sock1 = c1 214 | LogInfo("A point(link%d) [%s] is ready", count, sock1.RemoteAddr()) 215 | case c2 := <-clientc2: 216 | if c2 == nil { 217 | // set stop flag when error happend 218 | stop <- true 219 | continue 220 | } 221 | // close the last pending sock2 222 | if sock2 != nil { 223 | sock2.Close() 224 | } 225 | sock2 = c2 226 | LogInfo("B point(link%d) [%s] is ready", count, sock2.RemoteAddr()) 227 | case <-time.After(120 * time.Second): 228 | if sock1 != nil { 229 | LogWarn("A point(%s) socket wait timeout, reset", sock1.RemoteAddr()) 230 | } 231 | if sock2 != nil { 232 | LogWarn("B point(%s) socket wait timeout, reset", sock2.RemoteAddr()) 233 | } 234 | release(sock1, sock2) 235 | continue 236 | } 237 | 238 | // wait another socket ready 239 | if sock1 == nil || sock2 == nil { 240 | continue 241 | } 242 | 243 | // the two socket is ready, connect with sockets 244 | go ConnectSock(count, sock1, sock2) 245 | count += 1 246 | // reset sock1 & sock2 247 | sock1 = nil 248 | sock2 = nil 249 | } // end for 250 | } 251 | 252 | 253 | /********************************************************************** 254 | * @Function: ConnConn(proto uint8, addr1 string, addr2 string) 255 | * @Description: the "Conn<=>Conn" working mode 256 | * @Parameter: proto uint8, the tcp or udp protocol setting 257 | * @Parameter: addr1 string, the address1 "ip:port" string 258 | * @Parameter: addr2 string, the address2 "ip:port" string 259 | * @Return: nil 260 | **********************************************************************/ 261 | func ConnConn(proto uint8, addr1 string, addr2 string) { 262 | // get sock launch function by protocol 263 | sockfoo := ConnTCP 264 | if proto == PORTFORWARD_PROTO_UDP { 265 | sockfoo = ConnUDP 266 | } 267 | 268 | var count int = 1 269 | for { 270 | select { 271 | case <-stop: 272 | return 273 | default: 274 | } 275 | 276 | // socket1 dial 277 | LogInfo("dial A point with sock1 [%s]", addr1) 278 | sock1, err := sockfoo(addr1) 279 | if err != nil { 280 | LogError("%s", err) 281 | time.Sleep(16 * time.Second) 282 | continue 283 | } 284 | LogInfo("A point(sock1) is ready") 285 | 286 | // waiting for the first message sent by the A point(sock1) 287 | buf := make([]byte, 32 * 1024) 288 | n, err := sock1.Read(buf) 289 | if err != nil { 290 | LogError("A point: %s", err) 291 | time.Sleep(16 * time.Second) 292 | continue 293 | } 294 | buf = buf[:n] 295 | 296 | // socket2 dial 297 | LogInfo("dial B point with sock2 [%s]", addr2) 298 | sock2, err := sockfoo(addr2) 299 | if err != nil { 300 | sock1.Close() 301 | LogError("%s", err) 302 | time.Sleep(16 * time.Second) 303 | continue 304 | } 305 | LogInfo("B point(sock2) is ready") 306 | 307 | // first pass in the first message above 308 | _, err = sock2.Write(buf) 309 | if err != nil { 310 | LogError("B point: %s", err) 311 | time.Sleep(16 * time.Second) 312 | continue 313 | } 314 | 315 | // connect with sockets 316 | go ConnectSock(count, sock1, sock2) 317 | count += 1 318 | } // end for 319 | } 320 | 321 | 322 | /********************************************************************** 323 | * @Function: ConnectSock(id int, sock1 Conn, sock2 Conn) 324 | * @Description: connect two sockets, if an error occurs, the socket will 325 | * be closed so that the coroutine can exit normally 326 | * @Parameter: id int, the communication link id 327 | * @Parameter: sock1 Conn, the first socket object 328 | * @Parameter: sock2 Conn, the second socket object 329 | * @Return: nil 330 | **********************************************************************/ 331 | func ConnectSock(id int, sock1 Conn, sock2 Conn) { 332 | exit := make(chan bool, 1) 333 | 334 | // 335 | go func() { 336 | _, err := io.Copy(sock1, sock2) 337 | if err != nil { 338 | LogError("ConnectSock%d(A=>B): %s", id, err) 339 | } else { 340 | LogInfo("ConnectSock%d(A=>B) exited", id) 341 | } 342 | exit <- true 343 | }() 344 | 345 | // 346 | go func() { 347 | _, err := io.Copy(sock2, sock1) 348 | if err != nil { 349 | LogError("ConnectSock%d(B=>A): %s", id, err) 350 | } else { 351 | LogInfo("ConnectSock%d(B=>A) exited", id) 352 | } 353 | exit <- true 354 | }() 355 | 356 | // exit when close either end 357 | <-exit 358 | // close all socket, so that "io.Copy" can exit 359 | sock1.Close() 360 | sock2.Close() 361 | } 362 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Filename: log.go 3 | * Description: the log information format 4 | * Author: knownsec404 5 | * Time: 2020.08.17 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | 16 | const ( 17 | LOG_LEVEL_NONE uint32 = 0 18 | LOG_LEVEL_FATAL uint32 = 1 19 | LOG_LEVEL_ERROR uint32 = 2 20 | LOG_LEVEL_WARN uint32 = 3 21 | LOG_LEVEL_INFO uint32 = 4 22 | LOG_LEVEL_DEBUG uint32 = 5 23 | ) 24 | 25 | var LOG_LEVEL uint32 = LOG_LEVEL_DEBUG 26 | 27 | /********************************************************************** 28 | * @Function: LogFatal(format string, a ...interface{}) 29 | * @Description: log infomations with fatal level 30 | * @Parameter: format string, the format string template 31 | * @Parameter: a ...interface{}, the value 32 | * @Return: nil 33 | **********************************************************************/ 34 | func LogFatal(format string, a ...interface{}) { 35 | if LOG_LEVEL < LOG_LEVEL_FATAL { 36 | return 37 | } 38 | msg := fmt.Sprintf(format, a...) 39 | msg = fmt.Sprintf("[%s] [FATAL] %s", getCurrentTime(), msg) 40 | fmt.Println(msg) 41 | } 42 | 43 | 44 | /********************************************************************** 45 | * @Function: LogError(format string, a ...interface{}) 46 | * @Description: log infomations with error level 47 | * @Parameter: format string, the format string template 48 | * @Parameter: a ...interface{}, the value 49 | * @Return: nil 50 | **********************************************************************/ 51 | func LogError(format string, a ...interface{}) { 52 | if LOG_LEVEL < LOG_LEVEL_WARN { 53 | return 54 | } 55 | msg := fmt.Sprintf(format, a...) 56 | msg = fmt.Sprintf("[%s] [ERROR] %s", getCurrentTime(), msg) 57 | fmt.Println(msg) 58 | } 59 | 60 | 61 | /********************************************************************** 62 | * @Function: LogWarn(format string, a ...interface{}) 63 | * @Description: log infomations with warn level 64 | * @Parameter: format string, the format string template 65 | * @Parameter: a ...interface{}, the value 66 | * @Return: nil 67 | **********************************************************************/ 68 | func LogWarn(format string, a ...interface{}) { 69 | if LOG_LEVEL < LOG_LEVEL_WARN { 70 | return 71 | } 72 | msg := fmt.Sprintf(format, a...) 73 | msg = fmt.Sprintf("[%s] [WARN] %s", getCurrentTime(), msg) 74 | fmt.Println(msg) 75 | } 76 | 77 | 78 | /********************************************************************** 79 | * @Function: LogInfo(format string, a ...interface{}) 80 | * @Description: log infomations with info level 81 | * @Parameter: format string, the format string template 82 | * @Parameter: a ...interface{}, the value 83 | * @Return: nil 84 | **********************************************************************/ 85 | func LogInfo(format string, a ...interface{}) { 86 | if LOG_LEVEL < LOG_LEVEL_INFO { 87 | return 88 | } 89 | msg := fmt.Sprintf(format, a...) 90 | msg = fmt.Sprintf("[%s] [INFO] %s", getCurrentTime(), msg) 91 | fmt.Println(msg) 92 | } 93 | 94 | 95 | /********************************************************************** 96 | * @Function: LogDebug(format string, a ...interface{}) 97 | * @Description: log infomations with debug level 98 | * @Parameter: format string, the format string template 99 | * @Parameter: a ...interface{}, the value 100 | * @Return: nil 101 | **********************************************************************/ 102 | func LogDebug(format string, a ...interface{}) { 103 | if LOG_LEVEL < LOG_LEVEL_DEBUG { 104 | return 105 | } 106 | msg := fmt.Sprintf(format, a...) 107 | msg = fmt.Sprintf("[%s] [DEBUG] %s", getCurrentTime(), msg) 108 | fmt.Println(msg) 109 | } 110 | 111 | 112 | /********************************************************************** 113 | * @Function: getCurrentTime() (string) 114 | * @Description: get current time as log format string 115 | * @Parameter: nil 116 | * @Return: string, the current time string 117 | **********************************************************************/ 118 | func getCurrentTime() (string) { 119 | return time.Now().Format("01-02|15:04:05") 120 | } 121 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Filename: main.go 3 | * Description: the PortForward main entry point 4 | * It supports tcp/udp protocol layer traffic forwarding, forward/reverse 5 | * creation of forwarding links, and multi-level cascading use. 6 | * Author: knownsec404 7 | * Time: 2020.09.02 8 | */ 9 | 10 | package main 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "os" 16 | "strings" 17 | ) 18 | 19 | const VERSION string = "version: 0.5.0(build-20201022)" 20 | 21 | /********************************************************************** 22 | * @Function: main() 23 | * @Description: the PortForward entry point, parse command-line argument 24 | * @Parameter: nil 25 | * @Return: nil 26 | **********************************************************************/ 27 | func main() { 28 | if len(os.Args) != 4 { 29 | usage() 30 | return 31 | } 32 | proto := os.Args[1] 33 | sock1 := os.Args[2] 34 | sock2 := os.Args[3] 35 | 36 | // parse and check argument 37 | protocol := PORTFORWARD_PROTO_TCP 38 | if strings.ToUpper(proto) == "TCP" { 39 | protocol = PORTFORWARD_PROTO_TCP 40 | } else if strings.ToUpper(proto) == "UDP" { 41 | protocol = PORTFORWARD_PROTO_UDP 42 | } else { 43 | fmt.Printf("unknown protocol [%s]\n", proto) 44 | return 45 | } 46 | 47 | m1, a1, err := parseSock(sock1) 48 | if err != nil { 49 | fmt.Println(err) 50 | return 51 | } 52 | m2, a2, err := parseSock(sock2) 53 | if err != nil { 54 | fmt.Println(err) 55 | return 56 | } 57 | 58 | // launch 59 | args := Args{ 60 | Protocol: protocol, 61 | Method1: m1, 62 | Addr1: a1, 63 | Method2: m2, 64 | Addr2: a2, 65 | } 66 | Launch(args) 67 | } 68 | 69 | 70 | /********************************************************************** 71 | * @Function: parseSock(sock string) (uint8, string, error) 72 | * @Description: parse and check sock string 73 | * @Parameter: sock string, the sock string from command-line 74 | * @Return: (uint8, string, error), the method, address and error 75 | **********************************************************************/ 76 | func parseSock(sock string) (uint8, string, error) { 77 | // split "method" and "address" 78 | items := strings.SplitN(sock, ":", 2) 79 | if len(items) != 2 { 80 | return PORTFORWARD_SOCK_NIL, "", 81 | errors.New("host format must [method:address:port]") 82 | } 83 | 84 | method := items[0] 85 | address := items[1] 86 | // check the method field 87 | if strings.ToUpper(method) == "LISTEN" { 88 | return PORTFORWARD_SOCK_LISTEN, address, nil 89 | } else if strings.ToUpper(method) == "CONN" { 90 | return PORTFORWARD_SOCK_CONN, address, nil 91 | } else { 92 | errmsg := fmt.Sprintf("unknown method [%s]", method) 93 | return PORTFORWARD_SOCK_NIL, "", errors.New(errmsg) 94 | } 95 | } 96 | 97 | 98 | /********************************************************************** 99 | * @Function: usage() 100 | * @Description: the PortForward usage 101 | * @Parameter: nil 102 | * @Return: nil 103 | **********************************************************************/ 104 | func usage() { 105 | fmt.Println("Usage:") 106 | fmt.Println(" ./portforward [proto] [sock1] [sock2]") 107 | fmt.Println("Option:") 108 | fmt.Println(" proto the port forward with protocol(tcp/udp)") 109 | fmt.Println(" sock format: [method:address:port]") 110 | fmt.Println(" method the sock mode(listen/conn)") 111 | fmt.Println("Example:") 112 | fmt.Println(" tcp conn:192.168.1.1:3389 conn:192.168.1.10:23333") 113 | fmt.Println(" udp listen:192.168.1.3:5353 conn:8.8.8.8:53") 114 | fmt.Println(" tcp listen:[fe80::1%lo0]:8888 conn:[fe80::1%lo0]:7777") 115 | fmt.Println() 116 | fmt.Println(VERSION) 117 | } 118 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Filename: tcp.go 3 | * Description: the PortForward tcp layer implement 4 | * Author: knownsec404 5 | * Time: 2020.09.23 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "net" 12 | "time" 13 | ) 14 | 15 | 16 | /********************************************************************** 17 | * @Function: ListenTCP(address string, clientc chan Conn, quit chan bool) 18 | * @Description: listen local tcp service, and accept client connection, 19 | * initialize connection and return by channel. 20 | * @Parameter: address string, the local listen address 21 | * @Parameter: clientc chan Conn, new client connection channel 22 | * @Parameter: quit chan bool, the quit signal channel 23 | * @Return: nil 24 | **********************************************************************/ 25 | func ListenTCP(address string, clientc chan Conn, quit chan bool) { 26 | addr, err := net.ResolveTCPAddr("tcp", address) 27 | if err != nil { 28 | LogError("tcp listen error, %s", err) 29 | clientc <- nil 30 | return 31 | } 32 | serv, err := net.ListenTCP("tcp", addr) 33 | if err != nil { 34 | LogError("tcp listen error, %s", err) 35 | clientc <- nil 36 | return 37 | } 38 | // the "conn" has been ready, close "serv" 39 | defer serv.Close() 40 | 41 | for { 42 | // check quit 43 | select { 44 | case <-quit: 45 | return 46 | default: 47 | } 48 | 49 | // set "Accept" timeout, for check "quit" signal 50 | serv.SetDeadline(time.Now().Add(16 * time.Second)) 51 | conn, err := serv.Accept() 52 | if err != nil { 53 | if err, ok := err.(net.Error); ok && err.Timeout() { 54 | continue 55 | } 56 | // others error 57 | LogError("tcp listen error, %s", err) 58 | clientc <- nil 59 | break 60 | } 61 | 62 | // new client is connected 63 | clientc <- conn 64 | } // end for 65 | } 66 | 67 | 68 | /********************************************************************** 69 | * @Function: ConnTCP(address string) (Conn, error) 70 | * @Description: dial to remote server, and return tcp connection 71 | * @Parameter: address string, the remote server address that needs to be dialed 72 | * @Return: (Conn, error), the tcp connection and error 73 | **********************************************************************/ 74 | func ConnTCP(address string) (Conn, error) { 75 | conn, err := net.DialTimeout("tcp", address, 10 * time.Second) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return conn, nil 81 | } 82 | -------------------------------------------------------------------------------- /udp.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Filename: udp.go 3 | * Description: the PortForward udp layer implement. 4 | * Author: knownsec404 5 | * Time: 2020.09.23 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "errors" 12 | "net" 13 | "time" 14 | ) 15 | 16 | // as UDP client Conn 17 | type UDPDistribute struct { 18 | Established bool 19 | Conn *(net.UDPConn) 20 | RAddr net.Addr 21 | Cache chan []byte 22 | } 23 | 24 | 25 | /********************************************************************** 26 | * @Function: NewUDPDistribute(conn *(net.UDPConn), addr net.Addr) (*UDPDistribute) 27 | * @Description: initialize UDPDistribute structure (as UDP client Conn) 28 | * @Parameter: conn *(net.UDPConn), the udp connection object 29 | * @Parameter: addr net.Addr, the udp client remote adddress 30 | * @Return: *UDPDistribute, the new UDPDistribute structure pointer 31 | **********************************************************************/ 32 | func NewUDPDistribute(conn *(net.UDPConn), addr net.Addr) (*UDPDistribute) { 33 | return &UDPDistribute{ 34 | Established: true, 35 | Conn: conn, 36 | RAddr: addr, 37 | Cache: make(chan []byte, 16), 38 | } 39 | } 40 | 41 | 42 | /********************************************************************** 43 | * @Function: (this *UDPDistribute) Close() (error) 44 | * @Description: set "Established" flag is false, the UDP service will 45 | * cleaned up according to certain conditions. 46 | * @Parameter: nil 47 | * @Return: error, the error 48 | **********************************************************************/ 49 | func (this *UDPDistribute) Close() (error) { 50 | this.Established = false 51 | return nil 52 | } 53 | 54 | 55 | /********************************************************************** 56 | * @Function: (this *UDPDistribute) Read(b []byte) (n int, err error) 57 | * @Description: read data from connection, due to the udp implementation of 58 | * PortForward, read here will only produce a timeout error and closed error 59 | * (compared to the normal net.Conn object) 60 | * @Parameter: b []byte, the buffer for receive data 61 | * @Return: (n int, err error), the length of the data read and error 62 | **********************************************************************/ 63 | func (this *UDPDistribute) Read(b []byte) (n int, err error) { 64 | if !this.Established { 65 | return 0, errors.New("udp distrubute has closed") 66 | } 67 | 68 | select { 69 | case <-time.After(16 * time.Second): 70 | return 0, errors.New("udp distrubute read timeout") 71 | case data := <-this.Cache: 72 | n := len(data) 73 | copy(b, data) 74 | return n, nil 75 | } 76 | } 77 | 78 | 79 | /********************************************************************** 80 | * @Function: (this *UDPDistribute) Write(b []byte) (n int, err error) 81 | * @Description: write data to connection by "WriteTo()" 82 | * @Parameter: b []byte, the data to be sent 83 | * @Return: (n int, err error), the length of the data write and error 84 | **********************************************************************/ 85 | func (this *UDPDistribute) Write(b []byte) (n int, err error) { 86 | if !this.Established { 87 | return 0, errors.New("udp distrubute has closed") 88 | } 89 | return this.Conn.WriteTo(b, this.RAddr) 90 | } 91 | 92 | 93 | /********************************************************************** 94 | * @Function: (this *UDPDistribute) RemoteAddr() (net.Addr) 95 | * @Description: get remote address 96 | * @Parameter: nil 97 | * @Return: net.Addr, the remote address 98 | **********************************************************************/ 99 | func (this *UDPDistribute) RemoteAddr() (net.Addr) { 100 | return this.RAddr 101 | } 102 | 103 | 104 | /********************************************************************** 105 | * @Function: ListenUDP(address string, clientc chan Conn, quit chan bool) 106 | * @Description: listen local udp service, and accept client connection, 107 | * initialize connection and return by channel. 108 | * since udp is running as a service, it only obtains remote data through 109 | * the Read* function cluster (different from tcp), so we need a temporary 110 | * table to record, so that we can use the temporary table to determine 111 | * whether to forward or create a new link 112 | * @Parameter: address string, the local listen address 113 | * @Parameter: clientc chan Conn, new client connection channel 114 | * @Parameter: quit chan bool, the quit signal channel 115 | * @Return: nil 116 | **********************************************************************/ 117 | func ListenUDP(address string, clientc chan Conn, quit chan bool) { 118 | addr, err := net.ResolveUDPAddr("udp", address) 119 | if err != nil { 120 | LogError("udp listen error, %s", err) 121 | clientc <- nil 122 | return 123 | } 124 | serv, err := net.ListenUDP("udp", addr) 125 | if err != nil { 126 | LogError("udp listen error, %s", err) 127 | clientc <- nil 128 | return 129 | } 130 | defer serv.Close() 131 | 132 | // the udp distrubute table 133 | table := make(map[string]*UDPDistribute) 134 | // NOTICE: 135 | // in the process of running, the table will generate invalid historical 136 | // data, we have not cleaned it up(These invalid historical data will not 137 | // affect our normal operation). 138 | // 139 | // if you want to deal with invalid historical data, the best way is to 140 | // launch a new coroutine to handle net.Read, and another coroutine to 141 | // handle the connection status, but additional logic to exit the 142 | // coroutine is needed. Currently we think it is unnecessary. 143 | // 144 | // under the current code logic: if a udp connection exits, it will timeout 145 | // to trigger "Close()", and finally set "Established" to false, but there 146 | // is no logic to check this state to clean up, so invalid historical data 147 | // is generated (of course, we can traverse to clean up? every packet? no) 148 | // 149 | // PS: you can see that we called "delete()" in the following logic, but 150 | // this is only for processing. when a new udp client hits invalid 151 | // historical data, we need to return a new connection so that PortForward 152 | // can create a communication link. 153 | 154 | for { 155 | // check quit 156 | select { 157 | case <-quit: 158 | return 159 | default: 160 | } 161 | 162 | // set timeout, for check "quit" signal 163 | serv.SetDeadline(time.Now().Add(16 * time.Second)) 164 | 165 | // just new 32*1024, in the outer layer we used "io.Copy()", which 166 | // can only handle the size of 32*1024 167 | buf := make([]byte, 32 * 1024) 168 | n, addr, err := serv.ReadFrom(buf) 169 | if err != nil { 170 | if err, ok := err.(net.Error); ok && err.Timeout() { 171 | continue 172 | } 173 | LogError("udp listen error, %s", err) 174 | clientc <- nil 175 | return 176 | } 177 | buf = buf[:n] 178 | 179 | // if the address in table, we distrubute message 180 | if d, ok := table[addr.String()]; ok { 181 | if d.Established { 182 | // it is established, distrubute message 183 | d.Cache <- buf 184 | continue 185 | } else { 186 | // we remove it when the connnection has expired 187 | delete(table, addr.String()) 188 | } 189 | } 190 | // if the address not in table, we create new connection object 191 | conn := NewUDPDistribute(serv, addr) 192 | table[addr.String()] = conn 193 | conn.Cache <- buf 194 | clientc <- conn 195 | } // end for 196 | } 197 | 198 | 199 | /********************************************************************** 200 | * @Function: ConnUDP(address string) (Conn, error) 201 | * @Description: dial to remote server, and return udp connection 202 | * @Parameter: address string, the remote server address that needs to be dialed 203 | * @Return: (Conn, error), the udp connection and error 204 | **********************************************************************/ 205 | func ConnUDP(address string) (Conn, error) { 206 | conn, err := net.DialTimeout("udp", address, 10 * time.Second) 207 | if err != nil { 208 | return nil, err 209 | } 210 | 211 | // send one byte(knock) to server, get "established" udp connection 212 | _, err = conn.Write([]byte("\x00")) 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | // due to the characteristics of udp, when the udp server exits, we will 218 | // not receive any signal, it will be blocked at conn.Read(); 219 | // here we set a timeout for udp 220 | conn.SetDeadline(time.Now().Add(60 * time.Second)) 221 | return conn, nil 222 | } 223 | --------------------------------------------------------------------------------