├── 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 |
--------------------------------------------------------------------------------