Files
simple-go-tcp-proxy/simple-tcp-proxy.go
2020-02-23 23:59:37 +08:00

163 lines
3.7 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
)
const ConfigFileName = "tcp_listen_config.json"
type ConnErrorType int32
const (
ConnClosed ConnErrorType = 0
ConnReset ConnErrorType = 1
ConnUnknownError ConnErrorType = 99999
)
type TcpListen struct {
Listen string `json:"listen"`
Backend string `json:"backend"`
}
type TcpListenConfig struct {
TcpListens []TcpListen `json:"tcpListens"`
}
// {
// "tcpListens": [
// {
// "listen": ":8080",
// "backend": "11.22.33.44:8080"
// }
// ]
// }
// Modified from: https://blog.csdn.net/fyxichen/article/details/51505542
func main() {
tcpListenConfig, err := readConfigFile()
if err != nil {
fmt.Printf("[ERROR] Error in load config file: %v\n", err)
printHelpMessage()
return
}
if len(tcpListenConfig.TcpListens) == 0 {
fmt.Printf("[ERROR] Not find any mapping in config\n")
printHelpMessage()
return
}
for _, tcpListen := range tcpListenConfig.TcpListens {
go serverTcpListen(tcpListen)
}
exitChan := make(chan bool, 1)
<-exitChan // never end, wait kill or Ctrl+C
}
func serverTcpListen(tcpListen TcpListen) {
lis, err := net.Listen("tcp", tcpListen.Listen)
if err != nil {
fmt.Printf("[ERROR] Listen on port %s error: %s\n", tcpListen.Listen, err)
return
}
defer lis.Close()
fmt.Printf("[INFO] Listen TCP at: %v --> %v\n", tcpListen.Listen, tcpListen.Backend)
for {
conn, err := lis.Accept()
if err != nil {
fmt.Printf("[ERROR] Listen error: %v\n", err)
continue
}
fmt.Printf("[INFO] TCP connection from %v, local: %v\n", conn.RemoteAddr(), conn.LocalAddr())
go handleTcpRequest(tcpListen, conn)
}
}
func handleTcpRequest(tcpListen TcpListen, sconn net.Conn) {
defer sconn.Close()
ip := tcpListen.Backend
dconn, err := net.Dial("tcp", ip)
if err != nil {
fmt.Printf("[ERROR] Connect to %v failed:%v\n", ip, err)
return
}
exitChan := make(chan bool, 1)
twoDirectionCopyConn(sconn, dconn, ip, exitChan)
<-exitChan
dconn.Close()
}
func twoDirectionCopyConn(conn1 net.Conn, conn2 net.Conn, ip string, exitChan chan bool) {
go copyConn(conn1, conn2, ip, exitChan)
go copyConn(conn2, conn1, ip, exitChan)
}
func copyConn(dconn net.Conn, sconn net.Conn, ip string, exitChan chan bool) {
if _, err := io.Copy(dconn, sconn); err != nil {
printConnError(ip, err)
}
exitChan <- true
}
func printConnError(ip string, err error) {
connError := sortConnError(err)
if connError == ConnClosed {
fmt.Printf("[INFO] Connection closed: %v\n", ip)
} else {
fmt.Printf("[WARN] Receive data from %v failed:%v\n", ip, err)
}
}
func sortConnError(err error) ConnErrorType {
errStr := fmt.Sprintf("%v", err)
if strings.Contains(errStr, "use of closed network connection") {
return ConnClosed
}
if strings.Contains(errStr, "connection reset by peer") {
return ConnReset
}
return ConnUnknownError
}
func printHelpMessage() {
tcpListenConfig := TcpListenConfig{
TcpListens: []TcpListen{
TcpListen{
Listen: ":8080",
Backend: "127.0.0.1:9090",
},
},
}
if tcpListenConfigJSON, err := json.Marshal(tcpListenConfig); err == nil {
var out bytes.Buffer
if err := json.Indent(&out, tcpListenConfigJSON, "", " "); err == nil {
fmt.Printf("[INFO] Sample JSON config [%s]:\n%s\n", ConfigFileName, string(out.Bytes()))
}
}
}
func readConfigFile() (*TcpListenConfig, error) {
fmt.Printf("[INFO] Load config: %s\n", ConfigFileName)
configFile, err := os.Open(ConfigFileName)
if err != nil {
return nil, err
}
defer configFile.Close()
configFileBytes, err := ioutil.ReadAll(configFile)
if err != nil {
return nil, err
}
var tcpListenConfig TcpListenConfig
if err = json.Unmarshal(configFileBytes, &tcpListenConfig); err != nil {
return nil, err
}
return &tcpListenConfig, nil
}