Added stream proxy UDP support

+ Added UDP support #147 (wip)
+ Updated structure for proxy storage
+ Renamed TCPprox module to streamproxy
+ Added multi selection for white / blacklist #176
This commit is contained in:
Toby Chui
2024-06-07 01:12:42 +08:00
parent 136d1ecafb
commit c6f7f37aaf
15 changed files with 663 additions and 990 deletions

View File

@@ -1,9 +1,10 @@
package tcpprox
package streamproxy
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"imuslab.com/zoraxy/mod/utils"
)
@@ -22,13 +23,13 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
return
}
portA, err := utils.PostPara(r, "porta")
listenAddr, err := utils.PostPara(r, "listenAddr")
if err != nil {
utils.SendErrorResponse(w, "first address cannot be empty")
return
}
portB, err := utils.PostPara(r, "portb")
proxyAddr, err := utils.PostPara(r, "proxyAddr")
if err != nil {
utils.SendErrorResponse(w, "second address cannot be empty")
return
@@ -44,27 +45,17 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
}
}
modeValue := ProxyMode_Transport
mode, err := utils.PostPara(r, "mode")
if err != nil || mode == "" {
utils.SendErrorResponse(w, "no mode given")
} else if mode == "listen" {
modeValue = ProxyMode_Listen
} else if mode == "transport" {
modeValue = ProxyMode_Transport
} else if mode == "starter" {
modeValue = ProxyMode_Starter
} else {
utils.SendErrorResponse(w, "invalid mode given. Only support listen / transport / starter")
}
useTCP, _ := utils.PostBool(r, "useTCP")
useUDP, _ := utils.PostBool(r, "useUDP")
//Create the target config
newConfigUUID := m.NewConfig(&ProxyRelayOptions{
Name: name,
PortA: portA,
PortB: portB,
Timeout: timeout,
Mode: modeValue,
Name: name,
ListeningAddr: strings.TrimSpace(listenAddr),
ProxyAddr: strings.TrimSpace(proxyAddr),
Timeout: timeout,
UseTCP: useTCP,
UseUDP: useUDP,
})
js, _ := json.Marshal(newConfigUUID)
@@ -80,22 +71,10 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
}
newName, _ := utils.PostPara(r, "name")
newPortA, _ := utils.PostPara(r, "porta")
newPortB, _ := utils.PostPara(r, "portb")
newModeStr, _ := utils.PostPara(r, "mode")
newMode := -1
if newModeStr != "" {
if newModeStr == "listen" {
newMode = 0
} else if newModeStr == "transport" {
newMode = 1
} else if newModeStr == "starter" {
newMode = 2
} else {
utils.SendErrorResponse(w, "invalid new mode value")
return
}
}
listenAddr, _ := utils.PostPara(r, "listenAddr")
proxyAddr, _ := utils.PostPara(r, "proxyAddr")
useTCP, _ := utils.PostBool(r, "useTCP")
useUDP, _ := utils.PostBool(r, "useUDP")
newTimeoutStr, _ := utils.PostPara(r, "timeout")
newTimeout := -1
@@ -108,7 +87,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
}
// Call the EditConfig method to modify the configuration
err = m.EditConfig(configUUID, newName, newPortA, newPortB, newMode, newTimeout)
err = m.EditConfig(configUUID, newName, listenAddr, proxyAddr, useTCP, useUDP, newTimeout)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
@@ -158,6 +137,7 @@ func (m *Manager) HandleStopProxy(w http.ResponseWriter, r *http.Request) {
}
if !targetProxyConfig.IsRunning() {
targetProxyConfig.Running = false
utils.SendErrorResponse(w, "target proxy service is not running")
return
}
@@ -180,6 +160,7 @@ func (m *Manager) HandleRemoveProxy(w http.ResponseWriter, r *http.Request) {
}
if targetProxyConfig.IsRunning() {
targetProxyConfig.Running = false
utils.SendErrorResponse(w, "Service is running")
return
}
@@ -209,25 +190,3 @@ func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(targetConfig)
utils.SendJSONResponse(w, string(js))
}
func (m *Manager) HandleConfigValidate(w http.ResponseWriter, r *http.Request) {
uuid, err := utils.GetPara(r, "uuid")
if err != nil {
utils.SendErrorResponse(w, "invalid uuid given")
return
}
targetConfig, err := m.GetConfigByUUID(uuid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
err = targetConfig.ValidateConfigs()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@@ -1,7 +1,8 @@
package tcpprox
package streamproxy
import (
"errors"
"log"
"net"
"sync"
"sync/atomic"
@@ -18,34 +19,31 @@ import (
connection
*/
const (
ProxyMode_Listen = 0
ProxyMode_Transport = 1
ProxyMode_Starter = 2
ProxyMode_UDP = 3
)
type ProxyRelayOptions struct {
Name string
PortA string
PortB string
Timeout int
Mode int
Name string
ListeningAddr string
ProxyAddr string
Timeout int
UseTCP bool
UseUDP bool
}
type ProxyRelayConfig struct {
UUID string //A UUIDv4 representing this config
Name string //Name of the config
Running bool //If the service is running
PortA string //Ports A (config depends on mode)
PortB string //Ports B (config depends on mode)
Mode int //Operation Mode
Running bool //Status, read only
AutoStart bool //If the service suppose to started automatically
ListeningAddress string //Listening Address, usually 127.0.0.1:port
ProxyTargetAddr string //Proxy target address
UseTCP bool //Enable TCP proxy
UseUDP bool //Enable UDP proxy
Timeout int //Timeout for connection in sec
stopChan chan bool //Stop channel to stop the listener
tcpStopChan chan bool //Stop channel for TCP listener
udpStopChan chan bool //Stop channel for UDP listener
aTobAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from A to B
bToaAccumulatedByteTransfer atomic.Int64 //Accumulated byte transfer from B to A
parent *Manager `json:"-"`
udpClientMap sync.Map //map storing the UDP client-server connections
parent *Manager `json:"-"`
}
type Options struct {
@@ -60,11 +58,11 @@ type Manager struct {
Configs []*ProxyRelayConfig
//Realtime Statistics
Connections int //currently connected connect counts
UDPClientMap sync.Map //map storing the UDP client-server connections
Connections int //currently connected connect counts
}
func NewTCProxy(options *Options) *Manager {
func NewStreamProxy(options *Options) *Manager {
options.Database.NewTable("tcprox")
//Load relay configs from db
@@ -108,16 +106,17 @@ func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
thisConfig := ProxyRelayConfig{
UUID: configUUID,
Name: config.Name,
Running: false,
PortA: config.PortA,
PortB: config.PortB,
Mode: config.Mode,
ListeningAddress: config.ListeningAddr,
ProxyTargetAddr: config.ProxyAddr,
UseTCP: config.UseTCP,
UseUDP: config.UseUDP,
Timeout: config.Timeout,
stopChan: nil,
tcpStopChan: nil,
udpStopChan: nil,
aTobAccumulatedByteTransfer: aAcc,
bToaAccumulatedByteTransfer: bAcc,
parent: m,
udpClientMap: sync.Map{},
parent: m,
}
m.Configs = append(m.Configs, &thisConfig)
m.SaveConfigToDatabase()
@@ -135,7 +134,7 @@ func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayConfig, error)
}
// Edit the config based on config UUID, leave empty for unchange fields
func (m *Manager) EditConfig(configUUID string, newName string, newPortA string, newPortB string, newMode int, newTimeout int) error {
func (m *Manager) EditConfig(configUUID string, newName string, newListeningAddr string, newProxyAddr string, useTCP bool, useUDP bool, newTimeout int) error {
// Find the config with the specified UUID
foundConfig, err := m.GetConfigByUUID(configUUID)
if err != nil {
@@ -146,18 +145,16 @@ func (m *Manager) EditConfig(configUUID string, newName string, newPortA string,
if newName != "" {
foundConfig.Name = newName
}
if newPortA != "" {
foundConfig.PortA = newPortA
if newListeningAddr != "" {
foundConfig.ListeningAddress = newListeningAddr
}
if newPortB != "" {
foundConfig.PortB = newPortB
}
if newMode != -1 {
if newMode > 2 || newMode < 0 {
return errors.New("invalid mode given")
}
foundConfig.Mode = newMode
if newProxyAddr != "" {
foundConfig.ProxyTargetAddr = newProxyAddr
}
foundConfig.UseTCP = useTCP
foundConfig.UseUDP = useUDP
if newTimeout != -1 {
if newTimeout < 0 {
return errors.New("invalid timeout value given")
@@ -165,13 +162,6 @@ func (m *Manager) EditConfig(configUUID string, newName string, newPortA string,
foundConfig.Timeout = newTimeout
}
/*
err = foundConfig.ValidateConfigs()
if err != nil {
return err
}
*/
m.SaveConfigToDatabase()
return nil
@@ -192,3 +182,78 @@ func (m *Manager) RemoveConfig(configUUID string) error {
func (m *Manager) SaveConfigToDatabase() {
m.Options.Database.Write("tcprox", "rules", m.Configs)
}
/*
Config Functions
*/
// Start a proxy if stopped
func (c *ProxyRelayConfig) Start() error {
if c.IsRunning() {
c.Running = true
return errors.New("proxy already running")
}
// Create a stopChan to control the loop
tcpStopChan := make(chan bool)
c.tcpStopChan = tcpStopChan
udpStopChan := make(chan bool)
c.udpStopChan = udpStopChan
//Start the proxy service
if c.UseUDP {
go func() {
if !c.UseTCP {
//By default running state shows TCP proxy. If TCP is not in use, UDP is shown instead
c.Running = true
}
err := c.ForwardUDP(c.ListeningAddress, c.ProxyTargetAddr, udpStopChan)
if err != nil {
if !c.UseTCP {
c.Running = false
}
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
}
}()
}
if c.UseTCP {
go func() {
//Default to transport mode
c.Running = true
err := c.Port2host(c.ListeningAddress, c.ProxyTargetAddr, tcpStopChan)
if err != nil {
c.Running = false
log.Println("[TCP] Error starting stream proxy " + c.Name + "(" + c.UUID + "): " + err.Error())
}
}()
}
//Successfully spawned off the proxy routine
return nil
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) IsRunning() bool {
return c.tcpStopChan != nil || c.udpStopChan != nil
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() {
log.Println("[PROXY] Stopping Stream Proxy " + c.Name)
if c.udpStopChan != nil {
c.udpStopChan <- true
c.udpStopChan = nil
}
if c.tcpStopChan != nil {
c.tcpStopChan <- true
c.tcpStopChan = nil
}
log.Println("[PROXY] Stopped Stream Proxy " + c.Name)
c.Running = false
}

View File

@@ -1,10 +1,10 @@
package tcpprox_test
package streamproxy_test
import (
"testing"
"time"
"imuslab.com/zoraxy/mod/tcpprox"
"imuslab.com/zoraxy/mod/streamproxy"
)
func TestPort2Port(t *testing.T) {
@@ -12,7 +12,7 @@ func TestPort2Port(t *testing.T) {
stopChan := make(chan bool)
// Create a ProxyRelayConfig with dummy values
config := &tcpprox.ProxyRelayConfig{
config := &streamproxy.ProxyRelayConfig{
Timeout: 1,
}
@@ -36,7 +36,7 @@ func TestPort2Port(t *testing.T) {
time.Sleep(1 * time.Second)
// If the goroutine is still running, it means it did not stop as expected
if config.Running {
if config.IsRunning() {
t.Errorf("port2port did not stop as expected")
}

View File

@@ -0,0 +1,146 @@
package streamproxy
import (
"errors"
"io"
"log"
"net"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
func isValidIP(ip string) bool {
parsedIP := net.ParseIP(ip)
return parsedIP != nil
}
func isValidPort(port string) bool {
portInt, err := strconv.Atoi(port)
if err != nil {
return false
}
if portInt < 1 || portInt > 65535 {
return false
}
return true
}
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
n, err := io.Copy(conn1, conn2)
if err != nil {
return
}
accumulator.Add(n) //Add to accumulator
conn1.Close()
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
wg.Done()
}
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg, aTob)
go connCopy(conn2, conn1, &wg, bToa)
//blocking when the wg is locked
wg.Wait()
}
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
conn, err := listener.Accept()
if err != nil {
return nil, err
}
//Check if connection in blacklist or whitelist
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
if !c.parent.Options.AccessControlHandler(conn) {
time.Sleep(300 * time.Millisecond)
conn.Close()
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
}
}
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
return conn, err
}
func startListener(address string) (net.Listener, error) {
log.Println("[+]", "try to start server on:["+address+"]")
server, err := net.Listen("tcp", address)
if err != nil {
return nil, errors.New("listen address [" + address + "] faild")
}
log.Println("[√]", "start listen at address:["+address+"]")
return server, nil
}
/*
Forwarder Functions
*/
/*
portA -> server
server -> portB
*/
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
listenerStartingAddr := allowPort
if isValidPort(allowPort) {
//number only, e.g. 8080
listenerStartingAddr = "0.0.0.0:" + allowPort
} else if strings.HasPrefix(allowPort, ":") && isValidPort(allowPort[1:]) {
//port number starting with :, e.g. :8080
listenerStartingAddr = "0.0.0.0" + allowPort
}
server, err := startListener(listenerStartingAddr)
if err != nil {
return err
}
targetAddress = strings.TrimSpace(targetAddress)
//Start stop handler
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
server.Close()
}()
//Start blocking loop for accepting connections
for {
conn, err := c.accept(server)
if err != nil {
if errors.Is(err, net.ErrClosed) {
//Terminate by stop chan. Exit listener loop
return nil
}
//Connection error. Retry
continue
}
go func(targetAddress string) {
log.Println("[+]", "start connect host:["+targetAddress+"]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(time.Duration(c.Timeout) * time.Second)
return
}
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}(targetAddress)
}
}

View File

@@ -0,0 +1,157 @@
package streamproxy
import (
"errors"
"log"
"net"
"strings"
"time"
)
/*
UDP Proxy Module
*/
// Information maintained for each client/server connection
type udpClientServerConn struct {
ClientAddr *net.UDPAddr // Address of the client
ServerConn *net.UDPConn // UDP connection to server
}
// Generate a new connection by opening a UDP connection to the server
func createNewUDPConn(srvAddr, cliAddr *net.UDPAddr) *udpClientServerConn {
conn := new(udpClientServerConn)
conn.ClientAddr = cliAddr
srvudp, err := net.DialUDP("udp", nil, srvAddr)
if err != nil {
return nil
}
conn.ServerConn = srvudp
return conn
}
// Start listener, return inbound lisener and proxy target UDP address
func initUDPConnections(listenAddr string, targetAddress string) (*net.UDPConn, *net.UDPAddr, error) {
// Set up Proxy
saddr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
return nil, nil, err
}
inboundConn, err := net.ListenUDP("udp", saddr)
if err != nil {
return nil, nil, err
}
log.Println("[UDP] Proxy listening on " + listenAddr)
outboundConn, err := net.ResolveUDPAddr("udp", targetAddress)
if err != nil {
return nil, nil, err
}
return inboundConn, outboundConn, nil
}
// Go routine which manages connection from server to single client
func (c *ProxyRelayConfig) RunUDPConnectionRelay(conn *udpClientServerConn, lisenter *net.UDPConn) {
var buffer [1500]byte
for {
// Read from server
n, err := conn.ServerConn.Read(buffer[0:])
if err != nil {
if errors.Is(err, net.ErrClosed) {
return
}
continue
}
// Relay it to client
_, err = lisenter.WriteToUDP(buffer[0:n], conn.ClientAddr)
if err != nil {
continue
}
}
}
// Close all connections that waiting for read from server
func (c *ProxyRelayConfig) CloseAllUDPConnections() {
c.udpClientMap.Range(func(clientAddr, clientServerConn interface{}) bool {
conn := clientServerConn.(*udpClientServerConn)
conn.ServerConn.Close()
return true
})
}
func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan bool) error {
//By default the incoming listen Address is int
//We need to add the loopback address into it
if isValidPort(address1) {
//Port number only. Missing the : in front
address1 = ":" + address1
}
if strings.HasPrefix(address1, ":") {
//Prepend 127.0.0.1 to the address
address1 = "127.0.0.1" + address1
}
lisener, targetAddr, err := initUDPConnections(address1, address2)
if err != nil {
return err
}
go func() {
//Stop channel receiver
for {
select {
case <-stopChan:
//Stop signal received
//Stop server -> client forwarder
c.CloseAllUDPConnections()
//Stop client -> server forwarder
//Force close, will terminate ReadFromUDP for inbound listener
lisener.Close()
return
default:
time.Sleep(100 * time.Millisecond)
}
}
}()
var buffer [1500]byte
for {
n, cliaddr, err := lisener.ReadFromUDP(buffer[0:])
if err != nil {
if errors.Is(err, net.ErrClosed) {
//Proxy stopped
return nil
}
continue
}
c.aTobAccumulatedByteTransfer.Add(int64(n))
saddr := cliaddr.String()
rawConn, found := c.udpClientMap.Load(saddr)
var conn *udpClientServerConn
if !found {
conn = createNewUDPConn(targetAddr, cliaddr)
if conn == nil {
continue
}
c.udpClientMap.Store(saddr, conn)
log.Println("[UDP] Created new connection for client " + saddr)
// Fire up routine to manage new connection
go c.RunUDPConnectionRelay(conn, lisener)
} else {
log.Println("[UDP] Found connection for client " + saddr)
conn = rawConn.(*udpClientServerConn)
}
// Relay to server
_, err = conn.ServerConn.Write(buffer[0:n])
if err != nil {
continue
}
}
}

View File

@@ -1,348 +0,0 @@
package tcpprox
import (
"errors"
"io"
"log"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
)
func isValidIP(ip string) bool {
parsedIP := net.ParseIP(ip)
return parsedIP != nil
}
func isValidPort(port string) bool {
portInt, err := strconv.Atoi(port)
if err != nil {
return false
}
if portInt < 1 || portInt > 65535 {
return false
}
return true
}
func isReachable(target string) bool {
timeout := time.Duration(2 * time.Second) // Set the timeout value as per your requirement
conn, err := net.DialTimeout("tcp", target, timeout)
if err != nil {
return false
}
defer conn.Close()
return true
}
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup, accumulator *atomic.Int64) {
n, err := io.Copy(conn1, conn2)
if err != nil {
//Add to accumulator
accumulator.Add(n)
}
conn1.Close()
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
wg.Done()
}
func forward(conn1 net.Conn, conn2 net.Conn, aTob *atomic.Int64, bToa *atomic.Int64) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg, aTob)
go connCopy(conn2, conn1, &wg, bToa)
//blocking when the wg is locked
wg.Wait()
}
func (c *ProxyRelayConfig) accept(listener net.Listener) (net.Conn, error) {
conn, err := listener.Accept()
if err != nil {
return nil, err
}
//Check if connection in blacklist or whitelist
if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
if !c.parent.Options.AccessControlHandler(conn) {
time.Sleep(300 * time.Millisecond)
conn.Close()
log.Println("[x]", "Connection from "+addr.IP.String()+" rejected by access control policy")
return nil, errors.New("Connection from " + addr.IP.String() + " rejected by access control policy")
}
}
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
return conn, err
}
func startListener(address string) (net.Listener, error) {
log.Println("[+]", "try to start server on:["+address+"]")
server, err := net.Listen("tcp", address)
if err != nil {
return nil, errors.New("listen address [" + address + "] faild")
}
log.Println("[√]", "start listen at address:["+address+"]")
return server, nil
}
/*
Config Functions
*/
// Config validator
func (c *ProxyRelayConfig) ValidateConfigs() error {
if c.Mode == ProxyMode_Transport {
//Port2Host: PortA int, PortB string
if !isValidPort(c.PortA) {
return errors.New("first address must be a valid port number")
}
if !isReachable(c.PortB) {
return errors.New("second address is unreachable")
}
return nil
} else if c.Mode == ProxyMode_Listen {
//Port2Port: Both port are port number
if !isValidPort(c.PortA) {
return errors.New("first address is not a valid port number")
}
if !isValidPort(c.PortB) {
return errors.New("second address is not a valid port number")
}
return nil
} else if c.Mode == ProxyMode_Starter {
//Host2Host: Both have to be hosts
if !isReachable(c.PortA) {
return errors.New("first address is unreachable")
}
if !isReachable(c.PortB) {
return errors.New("second address is unreachable")
}
return nil
} else {
return errors.New("invalid mode given")
}
}
// Start a proxy if stopped
func (c *ProxyRelayConfig) Start() error {
if c.Running {
return errors.New("proxy already running")
}
// Create a stopChan to control the loop
stopChan := make(chan bool)
c.stopChan = stopChan
//Validate configs
err := c.ValidateConfigs()
if err != nil {
return err
}
//Start the proxy service
go func() {
c.Running = true
if c.Mode == ProxyMode_Transport {
err = c.Port2host(c.PortA, c.PortB, stopChan)
} else if c.Mode == ProxyMode_Listen {
err = c.Port2port(c.PortA, c.PortB, stopChan)
} else if c.Mode == ProxyMode_Starter {
err = c.Host2host(c.PortA, c.PortB, stopChan)
} else if c.Mode == ProxyMode_UDP {
err = c.ForwardUDP(c.PortA, c.PortB, stopChan)
}
if err != nil {
c.Running = false
log.Println("Error starting proxy service " + c.Name + "(" + c.UUID + "): " + err.Error())
}
}()
//Successfully spawned off the proxy routine
return nil
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) IsRunning() bool {
return c.Running || c.stopChan != nil
}
// Stop a running proxy if running
func (c *ProxyRelayConfig) Stop() {
if c.Running || c.stopChan != nil {
c.stopChan <- true
time.Sleep(300 * time.Millisecond)
c.stopChan = nil
c.Running = false
}
}
/*
Forwarder Functions
*/
/*
portA -> server
portB -> server
*/
func (c *ProxyRelayConfig) Port2port(port1 string, port2 string, stopChan chan bool) error {
//Trim the Prefix of : if exists
listen1, err := startListener("0.0.0.0:" + port1)
if err != nil {
return err
}
listen2, err := startListener("0.0.0.0:" + port2)
if err != nil {
return err
}
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
c.Running = true
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Port to Port forwarder")
c.Running = false
listen1.Close()
listen2.Close()
}()
for {
conn1, err := c.accept(listen1)
if err != nil {
if !c.Running {
return nil
}
continue
}
conn2, err := c.accept(listen2)
if err != nil {
if !c.Running {
return nil
}
continue
}
if conn1 == nil || conn2 == nil {
log.Println("[x]", "accept client faild. retry in ", c.Timeout, " seconds. ")
time.Sleep(time.Duration(c.Timeout) * time.Second)
continue
}
go forward(conn1, conn2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}
}
/*
portA -> server
server -> portB
*/
func (c *ProxyRelayConfig) Port2host(allowPort string, targetAddress string, stopChan chan bool) error {
server, err := startListener("0.0.0.0:" + allowPort)
if err != nil {
return err
}
//Start stop handler
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Port to Host forwarder")
c.Running = false
server.Close()
}()
//Start blocking loop for accepting connections
for {
conn, err := c.accept(server)
if conn == nil || err != nil {
if !c.Running {
//Terminate by stop chan. Exit listener loop
return nil
}
//Connection error. Retry
continue
}
go func(targetAddress string) {
log.Println("[+]", "start connect host:["+targetAddress+"]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", c.Timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(time.Duration(c.Timeout) * time.Second)
return
}
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
forward(target, conn, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}(targetAddress)
}
}
/*
server -> portA
server -> portB
*/
func (c *ProxyRelayConfig) Host2host(address1, address2 string, stopChan chan bool) error {
c.Running = true
go func() {
<-stopChan
log.Println("[x]", "Received stop signal. Exiting Host to Host forwarder")
c.Running = false
}()
for c.Running {
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
var host1, host2 net.Conn
var err error
for {
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
host1, err = d.Dial("tcp", address1)
if err == nil {
log.Println("[→]", "connect ["+address1+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", c.Timeout, " seconds. ")
time.Sleep(time.Duration(c.Timeout) * time.Second)
}
if !c.Running {
return nil
}
}
for {
d := net.Dialer{Timeout: time.Duration(c.Timeout)}
host2, err = d.Dial("tcp", address2)
if err == nil {
log.Println("[→]", "connect ["+address2+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", c.Timeout, " seconds. ")
time.Sleep(time.Duration(c.Timeout) * time.Second)
}
if !c.Running {
return nil
}
}
go forward(host1, host2, &c.aTobAccumulatedByteTransfer, &c.bToaAccumulatedByteTransfer)
}
return nil
}

View File

@@ -1,289 +0,0 @@
package tcpprox
import (
"fmt"
"io"
"log"
"net"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const timeout = 5
func main() {
//log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
log.SetFlags(log.Ldate | log.Lmicroseconds)
printWelcome()
args := os.Args
argc := len(os.Args)
if argc <= 2 {
printHelp()
os.Exit(0)
}
//TODO:support UDP protocol
/*var logFileError error
if argc > 5 && args[4] == "-log" {
logPath := args[5] + "/" + time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
logPath += args[1] + "-" + strings.Replace(args[2], ":", "_", -1) + "-" + args[3] + ".log"
logPath = strings.Replace(logPath, `\`, "/", -1)
logPath = strings.Replace(logPath, "//", "/", -1)
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
if logFileError != nil {
log.Fatalln("[x]", "log file path error.", logFileError.Error())
}
log.Println("[√]", "open test log file success. path:", logPath)
}*/
switch args[1] {
case "-listen":
if argc < 3 {
log.Fatalln(`-listen need two arguments, like "nb -listen 1997 2017".`)
}
port1 := checkPort(args[2])
port2 := checkPort(args[3])
log.Println("[√]", "start to listen port:", port1, "and port:", port2)
port2port(port1, port2)
break
case "-tran":
if argc < 3 {
log.Fatalln(`-tran need two arguments, like "nb -tran 1997 192.168.1.2:3389".`)
}
port := checkPort(args[2])
var remoteAddress string
if checkIp(args[3]) {
remoteAddress = args[3]
}
split := strings.SplitN(remoteAddress, ":", 2)
log.Println("[√]", "start to transmit address:", remoteAddress, "to address:", split[0]+":"+port)
port2host(port, remoteAddress)
break
case "-slave":
if argc < 3 {
log.Fatalln(`-slave need two arguments, like "nb -slave 127.0.0.1:3389 8.8.8.8:1997".`)
}
var address1, address2 string
checkIp(args[2])
if checkIp(args[2]) {
address1 = args[2]
}
checkIp(args[3])
if checkIp(args[3]) {
address2 = args[3]
}
log.Println("[√]", "start to connect address:", address1, "and address:", address2)
host2host(address1, address2)
break
default:
printHelp()
}
}
func printWelcome() {
fmt.Println("+----------------------------------------------------------------+")
fmt.Println("| Welcome to use NATBypass Ver1.0.0 . |")
fmt.Println("| Code by cw1997 at 2017-10-19 03:59:51 |")
fmt.Println("| If you have some problem when you use the tool, |")
fmt.Println("| please submit issue at : https://github.com/cw1997/NATBypass . |")
fmt.Println("+----------------------------------------------------------------+")
fmt.Println()
// sleep one second because the fmt is not thread-safety.
// if not to do this, fmt.Print will print after the log.Print.
time.Sleep(time.Second)
}
func printHelp() {
fmt.Println(`usage: "-listen port1 port2" example: "nb -listen 1997 2017" `)
fmt.Println(` "-tran port1 ip:port2" example: "nb -tran 1997 192.168.1.2:3389" `)
fmt.Println(` "-slave ip1:port1 ip2:port2" example: "nb -slave 127.0.0.1:3389 8.8.8.8:1997" `)
fmt.Println(`============================================================`)
fmt.Println(`optional argument: "-log logpath" . example: "nb -listen 1997 2017 -log d:/nb" `)
fmt.Println(`log filename format: Y_m_d_H_i_s-agrs1-args2-args3.log`)
fmt.Println(`============================================================`)
fmt.Println(`if you want more help, please read "README.md". `)
}
func checkPort(port string) string {
PortNum, err := strconv.Atoi(port)
if err != nil {
log.Fatalln("[x]", "port should be a number")
}
if PortNum < 1 || PortNum > 65535 {
log.Fatalln("[x]", "port should be a number and the range is [1,65536)")
}
return port
}
func checkIp(address string) bool {
ipAndPort := strings.Split(address, ":")
if len(ipAndPort) != 2 {
log.Fatalln("[x]", "address error. should be a string like [ip:port]. ")
}
ip := ipAndPort[0]
port := ipAndPort[1]
checkPort(port)
pattern := `^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$`
ok, err := regexp.MatchString(pattern, ip)
if err != nil || !ok {
log.Fatalln("[x]", "ip error. ")
}
return ok
}
func port2port(port1 string, port2 string) {
listen1 := start_server("0.0.0.0:" + port1)
listen2 := start_server("0.0.0.0:" + port2)
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
for {
conn1 := accept(listen1)
conn2 := accept(listen2)
if conn1 == nil || conn2 == nil {
log.Println("[x]", "accept client faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
continue
}
forward(conn1, conn2)
}
}
func port2host(allowPort string, targetAddress string) {
server := start_server("0.0.0.0:" + allowPort)
for {
conn := accept(server)
if conn == nil {
continue
}
//println(targetAddress)
go func(targetAddress string) {
log.Println("[+]", "start connect host:["+targetAddress+"]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address ["+targetAddress+"] faild. retry in ", timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:["+conn.LocalAddr().String()+"] and remote:["+conn.RemoteAddr().String()+"]")
time.Sleep(timeout * time.Second)
return
}
log.Println("[→]", "connect target address ["+targetAddress+"] success.")
forward(target, conn)
}(targetAddress)
}
}
func host2host(address1, address2 string) {
for {
log.Println("[+]", "try to connect host:["+address1+"] and ["+address2+"]")
var host1, host2 net.Conn
var err error
for {
host1, err = net.Dial("tcp", address1)
if err == nil {
log.Println("[→]", "connect ["+address1+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address1+"] faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
}
}
for {
host2, err = net.Dial("tcp", address2)
if err == nil {
log.Println("[→]", "connect ["+address2+"] success.")
break
} else {
log.Println("[x]", "connect target address ["+address2+"] faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
}
}
forward(host1, host2)
}
}
func start_server(address string) net.Listener {
log.Println("[+]", "try to start server on:["+address+"]")
server, err := net.Listen("tcp", address)
if err != nil {
log.Fatalln("[x]", "listen address ["+address+"] faild.")
}
log.Println("[√]", "start listen at address:["+address+"]")
return server
/*defer server.Close()
for {
conn, err := server.Accept()
log.Println("accept a new client. remote address:[" + conn.RemoteAddr().String() +
"], local address:[" + conn.LocalAddr().String() + "]")
if err != nil {
log.Println("accept a new client faild.", err.Error())
continue
}
//go recvConnMsg(conn)
}*/
}
func accept(listener net.Listener) net.Conn {
conn, err := listener.Accept()
if err != nil {
log.Println("[x]", "accept connect ["+conn.RemoteAddr().String()+"] faild.", err.Error())
return nil
}
log.Println("[√]", "accept a new client. remote address:["+conn.RemoteAddr().String()+"], local address:["+conn.LocalAddr().String()+"]")
return conn
}
func forward(conn1 net.Conn, conn2 net.Conn) {
log.Printf("[+] start transmit. [%s],[%s] <-> [%s],[%s] \n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg)
go connCopy(conn2, conn1, &wg)
//blocking when the wg is locked
wg.Wait()
}
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup) {
//TODO:log, record the data from conn1 and conn2.
logFile := openLog(conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
if logFile != nil {
w := io.MultiWriter(conn1, logFile)
io.Copy(w, conn2)
} else {
io.Copy(conn1, conn2)
}
conn1.Close()
log.Println("[←]", "close the connect at local:["+conn1.LocalAddr().String()+"] and remote:["+conn1.RemoteAddr().String()+"]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:["+conn2.LocalAddr().String()+"] and remote:["+conn2.RemoteAddr().String()+"]")
wg.Done()
}
func openLog(address1, address2, address3, address4 string) *os.File {
args := os.Args
argc := len(os.Args)
var logFileError error
var logFile *os.File
if argc > 5 && args[4] == "-log" {
address1 = strings.Replace(address1, ":", "_", -1)
address2 = strings.Replace(address2, ":", "_", -1)
address3 = strings.Replace(address3, ":", "_", -1)
address4 = strings.Replace(address4, ":", "_", -1)
timeStr := time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
logPath := args[5] + "/" + timeStr + args[1] + "-" + address1 + "_" + address2 + "-" + address3 + "_" + address4 + ".log"
logPath = strings.Replace(logPath, `\`, "/", -1)
logPath = strings.Replace(logPath, "//", "/", -1)
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
if logFileError != nil {
log.Fatalln("[x]", "log file path error.", logFileError.Error())
}
log.Println("[√]", "open test log file success. path:", logPath)
}
return logFile
}

View File

@@ -1,89 +0,0 @@
package tcpprox
import (
"log"
"net"
)
/*
UDP Proxy Module
*/
// Information maintained for each client/server connection
type udpClientServerConn struct {
ClientAddr *net.UDPAddr // Address of the client
ServerConn *net.UDPConn // UDP connection to server
}
// Generate a new connection by opening a UDP connection to the server
func createNewUDPConn(srvAddr, cliAddr *net.UDPAddr) *udpClientServerConn {
conn := new(udpClientServerConn)
conn.ClientAddr = cliAddr
srvudp, err := net.DialUDP("udp", nil, srvAddr)
if err != nil {
return nil
}
conn.ServerConn = srvudp
return conn
}
// Start listener, return inbound lisener and proxy target UDP address
func initUDPConnections(listenAddr string, targetAddress string) (*net.UDPConn, *net.UDPAddr, error) {
// Set up Proxy
saddr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
return nil, nil, err
}
inboundConn, err := net.ListenUDP("udp", saddr)
if err != nil {
return nil, nil, err
}
log.Println("Proxy serving on port %s\n", listenAddr)
outboundConn, err := net.ResolveUDPAddr("udp", targetAddress)
if err != nil {
return nil, nil, err
}
return inboundConn, outboundConn, nil
}
func (c *ProxyRelayConfig) ForwardUDP(address1, address2 string, stopChan chan bool) error {
lisener, targetAddr, err := initUDPConnections(address1, address2)
if err != nil {
return err
}
var buffer [1500]byte
for {
n, cliaddr, err := lisener.ReadFromUDP(buffer[0:])
if err != nil {
continue
}
c.aTobAccumulatedByteTransfer.Add(int64(n))
saddr := cliaddr.String()
dlock()
conn, found := ClientDict[saddr]
if !found {
conn = createNewUDPConn(targetAddr, cliaddr)
if conn == nil {
dunlock()
continue
}
ClientDict[saddr] = conn
dunlock()
Vlogf(2, "Created new connection for client %s\n", saddr)
// Fire up routine to manage new connection
go RunConnection(conn)
} else {
Vlogf(5, "Found connection for client %s\n", saddr)
dunlock()
}
// Relay to server
_, err = conn.ServerConn.Write(buffer[0:n])
if checkreport(1, err) {
continue
}
}
}

View File

@@ -68,9 +68,9 @@ func PostBool(r *http.Request, key string) (bool, error) {
x = strings.TrimSpace(x)
if x == "1" || strings.ToLower(x) == "true" {
if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" {
return true, nil
} else if x == "0" || strings.ToLower(x) == "false" {
} else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" {
return false, nil
}