[ Cmii ] [ Octopus ] - add tcp portforwarding part
This commit is contained in:
44
port_forwarding/CHANGELOG
Normal file
44
port_forwarding/CHANGELOG
Normal file
@@ -0,0 +1,44 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Released]
|
||||
|
||||
## [0.5.0] - 2020-10-22
|
||||
### Added
|
||||
- Refactoring source code strucure for StarLink
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.5.1] - 2021-04-23
|
||||
### Fixed
|
||||
- In listen-listen mode, actively close the previous socket when there are
|
||||
multiple connections to the same end
|
||||
|
||||
## [0.4.1] - 2020-10-16
|
||||
### Added
|
||||
- Add multi-channel support of conn-conn mode
|
||||
### Fixed
|
||||
- Modify timeout of unsuccessfully established channel in listen-listen
|
||||
work mode from 16s => 120s
|
||||
|
||||
## [0.4] - 2020-09-25
|
||||
### Added
|
||||
- Add multi-channel support
|
||||
### Changed
|
||||
- Optimize code structure and implementation
|
||||
|
||||
## [0.3] - 2020-09-20
|
||||
### Added
|
||||
- Refactoring the project framework into a three-layer structure:
|
||||
work_mode / protocol / socket
|
||||
|
||||
## [0.2] - 2020-09-10
|
||||
### Added
|
||||
- Refactoring project framework
|
||||
|
||||
## [0.1] - 2020-08-19
|
||||
### Added
|
||||
- Initialize project
|
||||
BIN
port_forwarding/Images/mutil_forward.png
Normal file
BIN
port_forwarding/Images/mutil_forward.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
port_forwarding/Images/portforward_framework.png
Normal file
BIN
port_forwarding/Images/portforward_framework.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
port_forwarding/Images/restricted_forward.png
Normal file
BIN
port_forwarding/Images/restricted_forward.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 214 KiB |
BIN
port_forwarding/Images/simple_forward.png
Normal file
BIN
port_forwarding/Images/simple_forward.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
21
port_forwarding/LICENSE
Normal file
21
port_forwarding/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Knownsec, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
190
port_forwarding/README.md
Normal file
190
port_forwarding/README.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# PortForward
|
||||
|
||||
## 0x00 前言
|
||||
|
||||
`PortForward` 是使用 Golang 进行开发的端口转发工具,解决在某些场景下 `内外网无法互通`的问题。
|
||||
|
||||
`PortForward` 的功能如下:
|
||||
|
||||
1. 支持 tcp/udp 协议层的端口转发
|
||||
2. 支持级联
|
||||
3. 支持正向/反向连接模式
|
||||
4. 支持多通道
|
||||
5. 支持 ipv6
|
||||
|
||||
本文对 `PortForward` 进行了详细的介绍。
|
||||
|
||||
目录:
|
||||
|
||||
1. 使用说明
|
||||
2. 工作场景
|
||||
3. 源码结构
|
||||
4. 逻辑结构
|
||||
5. 端口转发的实现
|
||||
6. udp的knock报文
|
||||
7. udp的超时设置
|
||||
8. listen-listen的超时设置
|
||||
9. 多通路的实现
|
||||
10. issue
|
||||
11. contributions
|
||||
|
||||
## 0x01 使用说明
|
||||
|
||||
**1.使用**
|
||||
|
||||
Usage:
|
||||
./portforward [proto] [sock1] [sock2]
|
||||
Option:
|
||||
proto the port forward with protocol(tcp/udp)
|
||||
sock format: [method:address:port]
|
||||
method the sock mode(listen/conn)
|
||||
Example:
|
||||
tcp conn:192.168.1.1:3389 conn:192.168.1.10:23333
|
||||
udp listen:192.168.1.3:5353 conn:8.8.8.8:53
|
||||
tcp listen:[fe80::1%lo0]:8888 conn:[fe80::1%lo0]:7777
|
||||
|
||||
version: 0.5.0(build-20201022)
|
||||
|
||||
**2.编译**
|
||||
|
||||
Golang 1.12及以上
|
||||
GO111MODULE=on
|
||||
|
||||
git clone https://github.com/knownsec/Portforward.git
|
||||
./build.sh
|
||||
|
||||
## 0x02 工作场景
|
||||
|
||||
这里列举了一些 `PortForward` 的工作场景,如下:
|
||||
|
||||
**2.1 简单模式**
|
||||
<div align="center">
|
||||
<img src="./Images/simple_forward.png" width="500">
|
||||
</br>[图1.简单转发模式]
|
||||
</div>
|
||||
|
||||
**2.2 受限主机转发**
|
||||
<div align="center">
|
||||
<img src="./Images/restricted_forward.png" width="500">
|
||||
</br>[图2.受限主机转发模式图]
|
||||
</div>
|
||||
|
||||
**2.3 级联端口转发**
|
||||
<div align="center">
|
||||
<img src="./Images/mutil_forward.png" width="500">
|
||||
</br>[图3.级联端口转发]
|
||||
</div>
|
||||
|
||||
## 0x03 源码结构
|
||||
|
||||
.
|
||||
├── CHANGELOG
|
||||
├── Images // images resource
|
||||
├── README.md
|
||||
├── build.sh // compile script
|
||||
├── forward.go // portforward main logic
|
||||
├── go.mod
|
||||
├── log.go // log module
|
||||
├── main.go // main, parse arguments
|
||||
├── tcp.go // tcp layer
|
||||
└── udp.go // udp layer
|
||||
|
||||
## 0x04 逻辑结构
|
||||
|
||||
`PortForward` 支持 `TCP` , `UDP` 协议层的端口转发,代码抽象后逻辑结构框架如下:
|
||||
<div align="center">
|
||||
<img src="./Images/portforward_framework.png" width="500">
|
||||
</br>[图4.整体框架]
|
||||
</div>
|
||||
|
||||
## 0x05 端口转发的实现
|
||||
|
||||
端口转发程序作为网络传输的中间人,无非就是将两端的 socket 对象进行联通,数据就可以通过这条「链路」进行传输了。
|
||||
|
||||
按照这样的思路,我们从需求开始分析和抽象,可以这么认为:无论是作为 `tcp` 还是 `udp` 运行,无论是作为 `connect` 还是 `listen`
|
||||
运行,最终都将获得两个 socket,其中一个连向原服务,另一个与客户端连接;最终将这两端的 socket 联通即可实现端口转发。
|
||||
|
||||
在 Golang 中我们采用了 `io.Copy()` 来联通两个 socket,但是 `io.Copy` 必须要求对象实现了 `io.Reader/io.Writer` 接口,`tcp`
|
||||
的 socket 可以直接支持,而 `udp` 的 socket 需要我们进行封装。
|
||||
|
||||
## 0x06 udp的knock报文
|
||||
|
||||
在 `udp` 的 `connect` 模式下,我们在连接服务器成功后,立即发送了一个 `knock` 报文,如下:
|
||||
|
||||
conn, err := net.DialTimeout("udp", ...
|
||||
_, err = conn.Write([]byte("\x00"))
|
||||
|
||||
其作用是通知远程 `udp` 服务器我们已经连上了(`udp`
|
||||
创建连接后,仅在本地操作系统层进行了注册,只有当发送一个报文到对端后,远程服务器才能感知到新连接),当我们在 `udp`
|
||||
的 `conn-conn` 模式下运行时,这个报文是必须的。
|
||||
|
||||
## 0x07 udp的超时设置
|
||||
|
||||
在 `udp` 的实现中,我们为所有的 `udp` 连接 socket 对象都设置了超时时间(`tcp` 中不需要),这是因为在 `udp` 中,socket
|
||||
对象无法感知对端退出,如果不设置超时时间,将会一直在 `conn.Read()` 阻塞下去。
|
||||
|
||||
我们设置了 `udp` 超时时间为 60 秒,当 60 秒无数据传输,本次建立的虚拟通信链路将销毁,端口转发程序将重新创建新的通信链路。
|
||||
|
||||
## 0x08 listen-listen的超时设置
|
||||
|
||||
对于 `listen-listen` 模式,需要等待两端的客户端都连上端口转发程序后,才能将两个 socket 进行联通。
|
||||
|
||||
为此我们在此处设置了 120 秒的超时时间,也就是说当其中一端有客户端连接后,另一端在 120 秒内没有连接,我们就销毁这个未成功建立的通信链路;用户重新连接即可。
|
||||
|
||||
> 如果没有这个超时,可能某些场景遗留了某个连接,将造成后续的通信链路错位。
|
||||
|
||||
## 0x09 多通路的实现
|
||||
|
||||
多通路可以支持同时发起多个连接,这里我们以 `tcp` 作为例子来说明。为了处理这种情况,我们的处理方式是:
|
||||
|
||||
1. `listen-conn`: 每当 listen 服务器接收到新连接后,与远端创建新的连接,并将两个 socket 进行联通。
|
||||
2. `listen-listen`: (好像没有实际场景)两端的 listen 服务器接收到新连接后,将两个 socket 进行联通。
|
||||
3. `conn-conn`: 创建 sock1 的连接,当 sock1 端收到数据,创建与 sock2 的连接,将两个 socket 进行联通;随后继续创建 sock1
|
||||
连接(预留)。
|
||||
|
||||
> 我们在 `udp` 中也加入了多通路的支持,和 `tcp` 基本类似,但由于 `udp` 是无连接的,我们不能像 `tcp` 直接联通两个 socket
|
||||
> 对象。我们在 `udp listen` 服务器中维护了一个临时表,使用 `ip:port` 作为标志,以描述各个通信链路的联通情况,依据此进行流量的分发。
|
||||
|
||||
## 0x0A issue
|
||||
|
||||
**1.udp的映射表未清空**
|
||||
udp多通路中的映射表没有对无效数据进行清理,长时间运行可能造成内存占用
|
||||
|
||||
**2.http服务的 host 字段影响**
|
||||
当转发 http 服务时,由于常见的客户端在访问时自动将 `host` 设置为访问目标(端口转发程序的地址)
|
||||
,我们直接对流量进行转发,那么 `host` 字段一定是错误的,某些 http 服务器对该字段进行了校验,所以无法正常转发。
|
||||
|
||||
> 比如端口转发 `www.baidu.com`
|
||||
|
||||
**3.不支持多端口通信**
|
||||
这里当然无法处理多端口通信的服务,我们仅做了一个端口的转发,多个端口的情况无法直接处理,比如 `FTP` 服务,其分别使用 20 端口(
|
||||
数据传输)和 21 端口(命令传输)。
|
||||
|
||||
但还是要单独说一句,对于大多数 `udp` 服务的实现,是 `udp` 服务器收到了一个新报文,对此进行处理后,然后使用新的 socket
|
||||
将响应数据发给对端,比如 `TFTP` 服务:
|
||||
|
||||
1.client 发起请求 [get aaa]
|
||||
src: 55123 => dst: 69
|
||||
2.tftp 收到报文,进行处理,返回响应:
|
||||
src: 61234 => dst: 55123
|
||||
|
||||
这种多端口通信我们也是无法处理的,因为在目前多通路的实现下,上述流程中第 2 步过后,对于端口转发程序来说,后续的报文无法确定转发给
|
||||
69 端口还是 61234 端口。
|
||||
|
||||
## 0x0B contributions
|
||||
|
||||
[r0oike@knownsec 404](https://github.com/r0oike)
|
||||
[fenix@knownsec 404](https://github.com/13ph03nix)
|
||||
[0x7F@knownsec 404](https://github.com/0x7Fancy)
|
||||
|
||||
</br>
|
||||
|
||||
------------------------------
|
||||
References:
|
||||
内网渗透之端口转发、映射、代理: <https://xz.aliyun.com/t/6349>
|
||||
内网渗透之端口转发与代理工具总结: <https://www.freebuf.com/articles/web/170970.html>
|
||||
sensepost/reGeorg: <https://github.com/sensepost/reGeorg>
|
||||
idlefire/ew: <https://github.com/idlefire/ew>
|
||||
|
||||
knownsec404
|
||||
2020.10.22
|
||||
1
port_forwarding/build.sh
Normal file
1
port_forwarding/build.sh
Normal file
@@ -0,0 +1 @@
|
||||
go build -o portforward .
|
||||
356
port_forwarding/forward.go
Normal file
356
port_forwarding/forward.go
Normal file
@@ -0,0 +1,356 @@
|
||||
/**
|
||||
* Filename: forward.go
|
||||
* Description: the PortForward main logic implement, Three working modes are
|
||||
* provided:
|
||||
* 1.Conn<=>Conn: dial A remote server, dial B remote server, connected
|
||||
* 2.Listen<=>Conn: listen local server, dial remote server, connected
|
||||
* 3.Listen<=>Listen: listen A local server, listen B local server, connected
|
||||
* Author: knownsec404
|
||||
* Time: 2020.09.23
|
||||
*/
|
||||
|
||||
package port_forwarding
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const PORTFORWARD_PROTO_NIL uint8 = 0x00
|
||||
const PORTFORWARD_PROTO_TCP uint8 = 0x10
|
||||
const PORTFORWARD_PROTO_UDP uint8 = 0x20
|
||||
|
||||
const PORTFORWARD_SOCK_NIL uint8 = 0x00
|
||||
const PORTFORWARD_SOCK_LISTEN uint8 = 0x01
|
||||
const PORTFORWARD_SOCK_CONN uint8 = 0x02
|
||||
|
||||
// the PortForward network interface
|
||||
type Conn interface {
|
||||
// Read reads data from the connection.
|
||||
Read(b []byte) (n int, err error)
|
||||
// Write writes data to the connection.
|
||||
Write(b []byte) (n int, err error)
|
||||
// Close closes the connection.
|
||||
Close() error
|
||||
// RemoteAddr returns the remote network address.
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
|
||||
// the PortForward launch arguemnt
|
||||
type Args struct {
|
||||
Protocol uint8
|
||||
// sock1
|
||||
Method1 uint8
|
||||
Addr1 string
|
||||
// sock2
|
||||
Method2 uint8
|
||||
Addr2 string
|
||||
}
|
||||
|
||||
var stop chan bool = nil
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: Launch(args Args)
|
||||
* @Description: launch PortForward working mode by arguments
|
||||
* @Parameter: args Args, the launch arguments
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func Launch(args Args) {
|
||||
// initialize stop channel, the maximum number of coroutines that
|
||||
// need to be managed is 2 (listen-listen)
|
||||
stop = make(chan bool, 3)
|
||||
|
||||
//
|
||||
if args.Method1 == PORTFORWARD_SOCK_CONN &&
|
||||
args.Method2 == PORTFORWARD_SOCK_CONN {
|
||||
// sock1 conn, sock2 conn
|
||||
ConnConn(args.Protocol, args.Addr1, args.Addr2)
|
||||
} else if args.Method1 == PORTFORWARD_SOCK_CONN &&
|
||||
args.Method2 == PORTFORWARD_SOCK_LISTEN {
|
||||
// sock1 conn, sock2 listen
|
||||
ListenConn(args.Protocol, args.Addr2, args.Addr1)
|
||||
} else if args.Method1 == PORTFORWARD_SOCK_LISTEN &&
|
||||
args.Method2 == PORTFORWARD_SOCK_CONN {
|
||||
// sock1 listen, sock2 conn
|
||||
ListenConn(args.Protocol, args.Addr1, args.Addr2)
|
||||
} else if args.Method1 == PORTFORWARD_SOCK_LISTEN &&
|
||||
args.Method2 == PORTFORWARD_SOCK_LISTEN {
|
||||
// sock1 listen , sock2 listen
|
||||
ListenListen(args.Protocol, args.Addr1, args.Addr2)
|
||||
} else {
|
||||
LogError("unknown forward method")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: Shutdown()
|
||||
* @Description: the shutdown PortForward
|
||||
* @Parameter: nil
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func Shutdown() {
|
||||
stop <- true
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ListenConn(proto uint8, addr1 string, addr2 string)
|
||||
* @Description: "Listen<=>Conn" working mode
|
||||
* @Parameter: proto uint8, the tcp or udp protocol setting
|
||||
* @Parameter: addr1 string, the address1 "ip:port" string
|
||||
* @Parameter: addr2 string, the address2 "ip:port" string
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func ListenConn(proto uint8, addr1 string, addr2 string) {
|
||||
// get sock launch function by protocol
|
||||
sockfoo1 := ListenTCP
|
||||
if proto == PORTFORWARD_PROTO_UDP {
|
||||
sockfoo1 = ListenUDP
|
||||
}
|
||||
sockfoo2 := ConnTCP
|
||||
if proto == PORTFORWARD_PROTO_UDP {
|
||||
sockfoo2 = ConnUDP
|
||||
}
|
||||
|
||||
// launch socket1 listen
|
||||
clientc := make(chan Conn)
|
||||
quit := make(chan bool, 1)
|
||||
LogInfo("listen A point with sock1 [%s]", addr1)
|
||||
go sockfoo1(addr1, clientc, quit)
|
||||
|
||||
var count int = 1
|
||||
for {
|
||||
// socket1 listen & quit signal
|
||||
var sock1 Conn = nil
|
||||
select {
|
||||
case <-stop:
|
||||
quit <- true
|
||||
return
|
||||
case sock1 = <-clientc:
|
||||
if sock1 == nil {
|
||||
// set stop flag when error happend
|
||||
stop <- true
|
||||
continue
|
||||
}
|
||||
}
|
||||
LogInfo("A point(link%d) [%s] is ready", count, sock1.RemoteAddr())
|
||||
|
||||
// socket2 dial
|
||||
LogInfo("dial B point with sock2 [%s]", addr2)
|
||||
sock2, err := sockfoo2(addr2)
|
||||
if err != nil {
|
||||
sock1.Close()
|
||||
LogError("%s", err)
|
||||
continue
|
||||
}
|
||||
LogInfo("B point(sock2) is ready")
|
||||
|
||||
// connect with sockets
|
||||
go ConnectSock(count, sock1, sock2)
|
||||
count += 1
|
||||
} // end for
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ListenListen(proto uint8, addr1 string, addr2 string)
|
||||
* @Description: the "Listen<=>Listen" working mode
|
||||
* @Parameter: proto uint8, the tcp or udp protocol setting
|
||||
* @Parameter: addr1 string, the address1 "ip:port" string
|
||||
* @Parameter: addr2 string, the address2 "ip:port" string
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func ListenListen(proto uint8, addr1 string, addr2 string) {
|
||||
release := func(s1 Conn, s2 Conn) {
|
||||
if s1 != nil {
|
||||
s1.Close()
|
||||
}
|
||||
if s2 != nil {
|
||||
s2.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// get sock launch function by protocol
|
||||
sockfoo := ListenTCP
|
||||
if proto == PORTFORWARD_PROTO_UDP {
|
||||
sockfoo = ListenUDP
|
||||
}
|
||||
|
||||
// launch socket1 listen
|
||||
clientc1 := make(chan Conn)
|
||||
quit1 := make(chan bool, 1)
|
||||
LogInfo("listen A point with sock1 [%s]", addr1)
|
||||
go sockfoo(addr1, clientc1, quit1)
|
||||
// launch socket2 listen
|
||||
clientc2 := make(chan Conn)
|
||||
quit2 := make(chan bool, 1)
|
||||
LogInfo("listen B point with sock2 [%s]", addr2)
|
||||
go sockfoo(addr2, clientc2, quit2)
|
||||
|
||||
var sock1 Conn = nil
|
||||
var sock2 Conn = nil
|
||||
var count int = 1
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
quit1 <- true
|
||||
quit2 <- true
|
||||
release(sock1, sock2)
|
||||
return
|
||||
case c1 := <-clientc1:
|
||||
if c1 == nil {
|
||||
// set stop flag when error happend
|
||||
stop <- true
|
||||
continue
|
||||
}
|
||||
// close the last pending sock1
|
||||
if sock1 != nil {
|
||||
sock1.Close()
|
||||
}
|
||||
sock1 = c1
|
||||
LogInfo("A point(link%d) [%s] is ready", count, sock1.RemoteAddr())
|
||||
case c2 := <-clientc2:
|
||||
if c2 == nil {
|
||||
// set stop flag when error happend
|
||||
stop <- true
|
||||
continue
|
||||
}
|
||||
// close the last pending sock2
|
||||
if sock2 != nil {
|
||||
sock2.Close()
|
||||
}
|
||||
sock2 = c2
|
||||
LogInfo("B point(link%d) [%s] is ready", count, sock2.RemoteAddr())
|
||||
case <-time.After(120 * time.Second):
|
||||
if sock1 != nil {
|
||||
LogWarn("A point(%s) socket wait timeout, reset", sock1.RemoteAddr())
|
||||
}
|
||||
if sock2 != nil {
|
||||
LogWarn("B point(%s) socket wait timeout, reset", sock2.RemoteAddr())
|
||||
}
|
||||
release(sock1, sock2)
|
||||
continue
|
||||
}
|
||||
|
||||
// wait another socket ready
|
||||
if sock1 == nil || sock2 == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// the two socket is ready, connect with sockets
|
||||
go ConnectSock(count, sock1, sock2)
|
||||
count += 1
|
||||
// reset sock1 & sock2
|
||||
sock1 = nil
|
||||
sock2 = nil
|
||||
} // end for
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ConnConn(proto uint8, addr1 string, addr2 string)
|
||||
* @Description: the "Conn<=>Conn" working mode
|
||||
* @Parameter: proto uint8, the tcp or udp protocol setting
|
||||
* @Parameter: addr1 string, the address1 "ip:port" string
|
||||
* @Parameter: addr2 string, the address2 "ip:port" string
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func ConnConn(proto uint8, addr1 string, addr2 string) {
|
||||
// get sock launch function by protocol
|
||||
sockfoo := ConnTCP
|
||||
if proto == PORTFORWARD_PROTO_UDP {
|
||||
sockfoo = ConnUDP
|
||||
}
|
||||
|
||||
var count int = 1
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// socket1 dial
|
||||
LogInfo("dial A point with sock1 [%s]", addr1)
|
||||
sock1, err := sockfoo(addr1)
|
||||
if err != nil {
|
||||
LogError("%s", err)
|
||||
time.Sleep(16 * time.Second)
|
||||
continue
|
||||
}
|
||||
LogInfo("A point(sock1) is ready")
|
||||
|
||||
// waiting for the first message sent by the A point(sock1)
|
||||
buf := make([]byte, 32*1024)
|
||||
n, err := sock1.Read(buf)
|
||||
if err != nil {
|
||||
LogError("A point: %s", err)
|
||||
time.Sleep(16 * time.Second)
|
||||
continue
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
// socket2 dial
|
||||
LogInfo("dial B point with sock2 [%s]", addr2)
|
||||
sock2, err := sockfoo(addr2)
|
||||
if err != nil {
|
||||
sock1.Close()
|
||||
LogError("%s", err)
|
||||
time.Sleep(16 * time.Second)
|
||||
continue
|
||||
}
|
||||
LogInfo("B point(sock2) is ready")
|
||||
|
||||
// first pass in the first message above
|
||||
_, err = sock2.Write(buf)
|
||||
if err != nil {
|
||||
LogError("B point: %s", err)
|
||||
time.Sleep(16 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// connect with sockets
|
||||
go ConnectSock(count, sock1, sock2)
|
||||
count += 1
|
||||
} // end for
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ConnectSock(id int, sock1 Conn, sock2 Conn)
|
||||
* @Description: connect two sockets, if an error occurs, the socket will
|
||||
* be closed so that the coroutine can exit normally
|
||||
* @Parameter: id int, the communication link id
|
||||
* @Parameter: sock1 Conn, the first socket object
|
||||
* @Parameter: sock2 Conn, the second socket object
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func ConnectSock(id int, sock1 Conn, sock2 Conn) {
|
||||
exit := make(chan bool, 1)
|
||||
|
||||
//
|
||||
go func() {
|
||||
_, err := io.Copy(sock1, sock2)
|
||||
if err != nil {
|
||||
LogError("ConnectSock%d(A=>B): %s", id, err)
|
||||
} else {
|
||||
LogInfo("ConnectSock%d(A=>B) exited", id)
|
||||
}
|
||||
exit <- true
|
||||
}()
|
||||
|
||||
//
|
||||
go func() {
|
||||
_, err := io.Copy(sock2, sock1)
|
||||
if err != nil {
|
||||
LogError("ConnectSock%d(B=>A): %s", id, err)
|
||||
} else {
|
||||
LogInfo("ConnectSock%d(B=>A) exited", id)
|
||||
}
|
||||
exit <- true
|
||||
}()
|
||||
|
||||
// exit when close either end
|
||||
<-exit
|
||||
// close all socket, so that "io.Copy" can exit
|
||||
sock1.Close()
|
||||
sock2.Close()
|
||||
}
|
||||
3
port_forwarding/go.mod
Normal file
3
port_forwarding/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module port_forwarding
|
||||
|
||||
go 1.20
|
||||
114
port_forwarding/log.go
Normal file
114
port_forwarding/log.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Filename: log.go
|
||||
* Description: the log information format
|
||||
* Author: knownsec404
|
||||
* Time: 2020.08.17
|
||||
*/
|
||||
|
||||
package port_forwarding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LOG_LEVEL_NONE uint32 = 0
|
||||
LOG_LEVEL_FATAL uint32 = 1
|
||||
LOG_LEVEL_ERROR uint32 = 2
|
||||
LOG_LEVEL_WARN uint32 = 3
|
||||
LOG_LEVEL_INFO uint32 = 4
|
||||
LOG_LEVEL_DEBUG uint32 = 5
|
||||
)
|
||||
|
||||
var LOG_LEVEL uint32 = LOG_LEVEL_DEBUG
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: LogFatal(format string, a ...interface{})
|
||||
* @Description: log infomations with fatal level
|
||||
* @Parameter: format string, the format string template
|
||||
* @Parameter: a ...interface{}, the value
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func LogFatal(format string, a ...interface{}) {
|
||||
if LOG_LEVEL < LOG_LEVEL_FATAL {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
msg = fmt.Sprintf("[%s] [FATAL] %s", getCurrentTime(), msg)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: LogError(format string, a ...interface{})
|
||||
* @Description: log infomations with error level
|
||||
* @Parameter: format string, the format string template
|
||||
* @Parameter: a ...interface{}, the value
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func LogError(format string, a ...interface{}) {
|
||||
if LOG_LEVEL < LOG_LEVEL_WARN {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
msg = fmt.Sprintf("[%s] [ERROR] %s", getCurrentTime(), msg)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: LogWarn(format string, a ...interface{})
|
||||
* @Description: log infomations with warn level
|
||||
* @Parameter: format string, the format string template
|
||||
* @Parameter: a ...interface{}, the value
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func LogWarn(format string, a ...interface{}) {
|
||||
if LOG_LEVEL < LOG_LEVEL_WARN {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
msg = fmt.Sprintf("[%s] [WARN] %s", getCurrentTime(), msg)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: LogInfo(format string, a ...interface{})
|
||||
* @Description: log infomations with info level
|
||||
* @Parameter: format string, the format string template
|
||||
* @Parameter: a ...interface{}, the value
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func LogInfo(format string, a ...interface{}) {
|
||||
if LOG_LEVEL < LOG_LEVEL_INFO {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
msg = fmt.Sprintf("[%s] [INFO] %s", getCurrentTime(), msg)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: LogDebug(format string, a ...interface{})
|
||||
* @Description: log infomations with debug level
|
||||
* @Parameter: format string, the format string template
|
||||
* @Parameter: a ...interface{}, the value
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func LogDebug(format string, a ...interface{}) {
|
||||
if LOG_LEVEL < LOG_LEVEL_DEBUG {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
msg = fmt.Sprintf("[%s] [DEBUG] %s", getCurrentTime(), msg)
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: getCurrentTime() (string)
|
||||
* @Description: get current time as log format string
|
||||
* @Parameter: nil
|
||||
* @Return: string, the current time string
|
||||
**********************************************************************/
|
||||
func getCurrentTime() string {
|
||||
return time.Now().Format("01-02|15:04:05")
|
||||
}
|
||||
115
port_forwarding/main.go
Normal file
115
port_forwarding/main.go
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Filename: main.go
|
||||
* Description: the PortForward main entry point
|
||||
* It supports tcp/udp protocol layer traffic forwarding, forward/reverse
|
||||
* creation of forwarding links, and multi-level cascading use.
|
||||
* Author: knownsec404
|
||||
* Time: 2020.09.02
|
||||
*/
|
||||
|
||||
package port_forwarding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const VERSION string = "version: 0.5.0(build-20201022)"
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: main()
|
||||
* @Description: the PortForward entry point, parse command-line argument
|
||||
* @Parameter: nil
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func main() {
|
||||
if len(os.Args) != 4 {
|
||||
usage()
|
||||
return
|
||||
}
|
||||
proto := os.Args[1]
|
||||
sock1 := os.Args[2]
|
||||
sock2 := os.Args[3]
|
||||
|
||||
// parse and check argument
|
||||
protocol := PORTFORWARD_PROTO_TCP
|
||||
if strings.ToUpper(proto) == "TCP" {
|
||||
protocol = PORTFORWARD_PROTO_TCP
|
||||
} else if strings.ToUpper(proto) == "UDP" {
|
||||
protocol = PORTFORWARD_PROTO_UDP
|
||||
} else {
|
||||
fmt.Printf("unknown protocol [%s]\n", proto)
|
||||
return
|
||||
}
|
||||
|
||||
m1, a1, err := parseSock(sock1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
m2, a2, err := parseSock(sock2)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// launch
|
||||
args := Args{
|
||||
Protocol: protocol,
|
||||
Method1: m1,
|
||||
Addr1: a1,
|
||||
Method2: m2,
|
||||
Addr2: a2,
|
||||
}
|
||||
Launch(args)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: parseSock(sock string) (uint8, string, error)
|
||||
* @Description: parse and check sock string
|
||||
* @Parameter: sock string, the sock string from command-line
|
||||
* @Return: (uint8, string, error), the method, address and error
|
||||
**********************************************************************/
|
||||
func parseSock(sock string) (uint8, string, error) {
|
||||
// split "method" and "address"
|
||||
items := strings.SplitN(sock, ":", 2)
|
||||
if len(items) != 2 {
|
||||
return PORTFORWARD_SOCK_NIL, "",
|
||||
errors.New("host format must [method:address:port]")
|
||||
}
|
||||
|
||||
method := items[0]
|
||||
address := items[1]
|
||||
// check the method field
|
||||
if strings.ToUpper(method) == "LISTEN" {
|
||||
return PORTFORWARD_SOCK_LISTEN, address, nil
|
||||
} else if strings.ToUpper(method) == "CONN" {
|
||||
return PORTFORWARD_SOCK_CONN, address, nil
|
||||
} else {
|
||||
errmsg := fmt.Sprintf("unknown method [%s]", method)
|
||||
return PORTFORWARD_SOCK_NIL, "", errors.New(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: usage()
|
||||
* @Description: the PortForward usage
|
||||
* @Parameter: nil
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func usage() {
|
||||
fmt.Println("Usage:")
|
||||
fmt.Println(" ./portforward [proto] [sock1] [sock2]")
|
||||
fmt.Println("Option:")
|
||||
fmt.Println(" proto the port forward with protocol(tcp/udp)")
|
||||
fmt.Println(" sock format: [method:address:port]")
|
||||
fmt.Println(" method the sock mode(listen/conn)")
|
||||
fmt.Println("Example:")
|
||||
fmt.Println(" tcp conn:192.168.1.1:3389 conn:192.168.1.10:23333")
|
||||
fmt.Println(" udp listen:192.168.1.3:5353 conn:8.8.8.8:53")
|
||||
fmt.Println(" tcp listen:[fe80::1%lo0]:8888 conn:[fe80::1%lo0]:7777")
|
||||
fmt.Println()
|
||||
fmt.Println(VERSION)
|
||||
}
|
||||
79
port_forwarding/tcp.go
Normal file
79
port_forwarding/tcp.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Filename: tcp.go
|
||||
* Description: the PortForward tcp layer implement
|
||||
* Author: knownsec404
|
||||
* Time: 2020.09.23
|
||||
*/
|
||||
|
||||
package port_forwarding
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ListenTCP(address string, clientc chan Conn, quit chan bool)
|
||||
* @Description: listen local tcp service, and accept client connection,
|
||||
* initialize connection and return by channel.
|
||||
* @Parameter: address string, the local listen address
|
||||
* @Parameter: clientc chan Conn, new client connection channel
|
||||
* @Parameter: quit chan bool, the quit signal channel
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func ListenTCP(address string, clientc chan Conn, quit chan bool) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", address)
|
||||
if err != nil {
|
||||
LogError("tcp listen error, %s", err)
|
||||
clientc <- nil
|
||||
return
|
||||
}
|
||||
serv, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
LogError("tcp listen error, %s", err)
|
||||
clientc <- nil
|
||||
return
|
||||
}
|
||||
// the "conn" has been ready, close "serv"
|
||||
defer serv.Close()
|
||||
|
||||
for {
|
||||
// check quit
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// set "Accept" timeout, for check "quit" signal
|
||||
serv.SetDeadline(time.Now().Add(16 * time.Second))
|
||||
conn, err := serv.Accept()
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
continue
|
||||
}
|
||||
// others error
|
||||
LogError("tcp listen error, %s", err)
|
||||
clientc <- nil
|
||||
break
|
||||
}
|
||||
|
||||
// new client is connected
|
||||
clientc <- conn
|
||||
} // end for
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ConnTCP(address string) (Conn, error)
|
||||
* @Description: dial to remote server, and return tcp connection
|
||||
* @Parameter: address string, the remote server address that needs to be dialed
|
||||
* @Return: (Conn, error), the tcp connection and error
|
||||
**********************************************************************/
|
||||
func ConnTCP(address string) (Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", address, 10*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
79
port_forwarding/tcp_learn/socket_learn.go
Normal file
79
port_forwarding/tcp_learn/socket_learn.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package tcp_learn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleConnection(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading:", err)
|
||||
break
|
||||
}
|
||||
if n > 0 {
|
||||
fmt.Printf("Received data from %s: %s\n", conn.RemoteAddr(), string(buf[:n]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TcpServer() {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:8080")
|
||||
if err != nil {
|
||||
|
||||
log.Printf("Error accepting connection: %v", err)
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating listener: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
listener.SetDeadline(time.Now().Add(2 * time.Minute))
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Printf("Error accepting connection: %v", err)
|
||||
return
|
||||
}
|
||||
go handleConnection(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TcpClient() chan bool {
|
||||
|
||||
exit := make(chan bool, 1)
|
||||
tick := time.Tick(time.Second)
|
||||
timeOut := time.After(time.Minute)
|
||||
|
||||
conn, err := net.Dial("tcp", "localhost:8080")
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating listener: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeOut:
|
||||
fmt.Println("timeout exit!")
|
||||
exit <- true
|
||||
return exit
|
||||
case <-tick:
|
||||
|
||||
_, err := conn.Write([]byte("Hello from tcp client !"))
|
||||
if err != nil {
|
||||
log.Fatalf("Error write tcp : %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exit
|
||||
}
|
||||
23
port_forwarding/tcp_learn/socket_learn_test.go
Normal file
23
port_forwarding/tcp_learn/socket_learn_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package tcp_learn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTcpServer(t *testing.T) {
|
||||
|
||||
var exit chan bool
|
||||
|
||||
go func() {
|
||||
TcpServer()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
exit = TcpClient()
|
||||
}()
|
||||
|
||||
<-exit
|
||||
}
|
||||
215
port_forwarding/udp.go
Normal file
215
port_forwarding/udp.go
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Filename: udp.go
|
||||
* Description: the PortForward udp layer implement.
|
||||
* Author: knownsec404
|
||||
* Time: 2020.09.23
|
||||
*/
|
||||
|
||||
package port_forwarding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// as UDP client Conn
|
||||
type UDPDistribute struct {
|
||||
Established bool
|
||||
Conn *(net.UDPConn)
|
||||
RAddr net.Addr
|
||||
Cache chan []byte
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: NewUDPDistribute(conn *(net.UDPConn), addr net.Addr) (*UDPDistribute)
|
||||
* @Description: initialize UDPDistribute structure (as UDP client Conn)
|
||||
* @Parameter: conn *(net.UDPConn), the udp connection object
|
||||
* @Parameter: addr net.Addr, the udp client remote adddress
|
||||
* @Return: *UDPDistribute, the new UDPDistribute structure pointer
|
||||
**********************************************************************/
|
||||
func NewUDPDistribute(conn *(net.UDPConn), addr net.Addr) *UDPDistribute {
|
||||
return &UDPDistribute{
|
||||
Established: true,
|
||||
Conn: conn,
|
||||
RAddr: addr,
|
||||
Cache: make(chan []byte, 16),
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: (this *UDPDistribute) Close() (error)
|
||||
* @Description: set "Established" flag is false, the UDP service will
|
||||
* cleaned up according to certain conditions.
|
||||
* @Parameter: nil
|
||||
* @Return: error, the error
|
||||
**********************************************************************/
|
||||
func (this *UDPDistribute) Close() error {
|
||||
this.Established = false
|
||||
return nil
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: (this *UDPDistribute) Read(b []byte) (n int, err error)
|
||||
* @Description: read data from connection, due to the udp implementation of
|
||||
* PortForward, read here will only produce a timeout error and closed error
|
||||
* (compared to the normal net.Conn object)
|
||||
* @Parameter: b []byte, the buffer for receive data
|
||||
* @Return: (n int, err error), the length of the data read and error
|
||||
**********************************************************************/
|
||||
func (this *UDPDistribute) Read(b []byte) (n int, err error) {
|
||||
if !this.Established {
|
||||
return 0, errors.New("udp distrubute has closed")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(16 * time.Second):
|
||||
return 0, errors.New("udp distrubute read timeout")
|
||||
case data := <-this.Cache:
|
||||
n := len(data)
|
||||
copy(b, data)
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: (this *UDPDistribute) Write(b []byte) (n int, err error)
|
||||
* @Description: write data to connection by "WriteTo()"
|
||||
* @Parameter: b []byte, the data to be sent
|
||||
* @Return: (n int, err error), the length of the data write and error
|
||||
**********************************************************************/
|
||||
func (this *UDPDistribute) Write(b []byte) (n int, err error) {
|
||||
if !this.Established {
|
||||
return 0, errors.New("udp distrubute has closed")
|
||||
}
|
||||
return this.Conn.WriteTo(b, this.RAddr)
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: (this *UDPDistribute) RemoteAddr() (net.Addr)
|
||||
* @Description: get remote address
|
||||
* @Parameter: nil
|
||||
* @Return: net.Addr, the remote address
|
||||
**********************************************************************/
|
||||
func (this *UDPDistribute) RemoteAddr() net.Addr {
|
||||
return this.RAddr
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ListenUDP(address string, clientc chan Conn, quit chan bool)
|
||||
* @Description: listen local udp service, and accept client connection,
|
||||
* initialize connection and return by channel.
|
||||
* since udp is running as a service, it only obtains remote data through
|
||||
* the Read* function cluster (different from tcp), so we need a temporary
|
||||
* table to record, so that we can use the temporary table to determine
|
||||
* whether to forward or create a new link
|
||||
* @Parameter: address string, the local listen address
|
||||
* @Parameter: clientc chan Conn, new client connection channel
|
||||
* @Parameter: quit chan bool, the quit signal channel
|
||||
* @Return: nil
|
||||
**********************************************************************/
|
||||
func ListenUDP(address string, clientc chan Conn, quit chan bool) {
|
||||
addr, err := net.ResolveUDPAddr("udp", address)
|
||||
if err != nil {
|
||||
LogError("udp listen error, %s", err)
|
||||
clientc <- nil
|
||||
return
|
||||
}
|
||||
serv, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
LogError("udp listen error, %s", err)
|
||||
clientc <- nil
|
||||
return
|
||||
}
|
||||
defer serv.Close()
|
||||
|
||||
// the udp distrubute table
|
||||
table := make(map[string]*UDPDistribute)
|
||||
// NOTICE:
|
||||
// in the process of running, the table will generate invalid historical
|
||||
// data, we have not cleaned it up(These invalid historical data will not
|
||||
// affect our normal operation).
|
||||
//
|
||||
// if you want to deal with invalid historical data, the best way is to
|
||||
// launch a new coroutine to handle net.Read, and another coroutine to
|
||||
// handle the connection status, but additional logic to exit the
|
||||
// coroutine is needed. Currently we think it is unnecessary.
|
||||
//
|
||||
// under the current code logic: if a udp connection exits, it will timeout
|
||||
// to trigger "Close()", and finally set "Established" to false, but there
|
||||
// is no logic to check this state to clean up, so invalid historical data
|
||||
// is generated (of course, we can traverse to clean up? every packet? no)
|
||||
//
|
||||
// PS: you can see that we called "delete()" in the following logic, but
|
||||
// this is only for processing. when a new udp client hits invalid
|
||||
// historical data, we need to return a new connection so that PortForward
|
||||
// can create a communication link.
|
||||
|
||||
for {
|
||||
// check quit
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// set timeout, for check "quit" signal
|
||||
serv.SetDeadline(time.Now().Add(16 * time.Second))
|
||||
|
||||
// just new 32*1024, in the outer layer we used "io.Copy()", which
|
||||
// can only handle the size of 32*1024
|
||||
buf := make([]byte, 32*1024)
|
||||
n, addr, err := serv.ReadFrom(buf)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
continue
|
||||
}
|
||||
LogError("udp listen error, %s", err)
|
||||
clientc <- nil
|
||||
return
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
// if the address in table, we distrubute message
|
||||
if d, ok := table[addr.String()]; ok {
|
||||
if d.Established {
|
||||
// it is established, distrubute message
|
||||
d.Cache <- buf
|
||||
continue
|
||||
} else {
|
||||
// we remove it when the connnection has expired
|
||||
delete(table, addr.String())
|
||||
}
|
||||
}
|
||||
// if the address not in table, we create new connection object
|
||||
conn := NewUDPDistribute(serv, addr)
|
||||
table[addr.String()] = conn
|
||||
conn.Cache <- buf
|
||||
clientc <- conn
|
||||
} // end for
|
||||
}
|
||||
|
||||
/**********************************************************************
|
||||
* @Function: ConnUDP(address string) (Conn, error)
|
||||
* @Description: dial to remote server, and return udp connection
|
||||
* @Parameter: address string, the remote server address that needs to be dialed
|
||||
* @Return: (Conn, error), the udp connection and error
|
||||
**********************************************************************/
|
||||
func ConnUDP(address string) (Conn, error) {
|
||||
conn, err := net.DialTimeout("udp", address, 10*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// send one byte(knock) to server, get "established" udp connection
|
||||
_, err = conn.Write([]byte("\x00"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// due to the characteristics of udp, when the udp server exits, we will
|
||||
// not receive any signal, it will be blocked at conn.Read();
|
||||
// here we set a timeout for udp
|
||||
conn.SetDeadline(time.Now().Add(60 * time.Second))
|
||||
return conn, nil
|
||||
}
|
||||
Reference in New Issue
Block a user