diff --git a/README.md b/README.md index e432b15..36f7953 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # portunnel -A port forwarding tool with GoLang \ No newline at end of file +A port forwarding tool with GoLang + +## Usage + +1. Compile the source code +2. Configure like example config +3. Run it! \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..921a4db --- /dev/null +++ b/config.json @@ -0,0 +1,16 @@ +{ + "forwardings": [ + { + "local_address": "127.0.0.1", + "local_port": 9000, + "remote_address": "example.com", + "remote_port": 80 + }, + { + "local_address": "[::1]", + "local_port": 9001, + "remote_address": "example.com", + "remote_port": 80 + } + ] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..949c326 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module git.ghink.net/bigsk/portunnel + +go 1.18 + +require github.com/fsnotify/fsnotify v1.6.0 + +require golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..60d7d6e --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go new file mode 100644 index 0000000..3087a83 --- /dev/null +++ b/main.go @@ -0,0 +1,185 @@ +package main + +import ( + "encoding/json" + "fmt" + "net" + "os" + "os/signal" + "syscall" + "time" + + "github.com/fsnotify/fsnotify" +) + +type Config struct { + Forwardings []Forwarding `json:"forwardings"` +} + +type Forwarding struct { + LocalAddress string `json:"local_address"` + LocalPort int `json:"local_port"` + RemoteAddress string `json:"remote_address"` + RemotePort int `json:"remote_port"` +} + +func main() { + // Read configuration file + configFile := "config.json" + config, err := readConfig(configFile) + if err != nil { + fmt.Printf("Failed to read config file: %v\n", err) + return + } + + // Start forwarding task + for _, f := range config.Forwardings { + go runForwarding(f) + } + + // Listen for configuration file changes + watcher, err := fsnotify.NewWatcher() + if err != nil { + fmt.Printf("Failed to create watcher: %v\n", err) + return + } + defer watcher.Close() + + if err := watcher.Add(configFile); err != nil { + fmt.Printf("Failed to watch config file: %v\n", err) + return + } + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + + for { + select { + case event := <-watcher.Events: + if event.Op&fsnotify.Write == fsnotify.Write { + fmt.Printf("Config file changed, reloading...\n") + + // Wait for the file system to complete the write operation + time.Sleep(100 * time.Millisecond) + + // Re-read configuration file + newConfig, err := readConfig(configFile) + if err != nil { + fmt.Printf("Failed to read config file: %v\n", err) + continue + } + + // Stop old forwarding task + for _, f := range config.Forwardings { + stopForwarding(f) + } + + // Start a new forwarding task + for _, f := range newConfig.Forwardings { + go runForwarding(f) + } + + config = newConfig + } + case err := <-watcher.Errors: + fmt.Printf("Watcher error: %v\n", err) + case sig := <-sigCh: + fmt.Printf("Received signal %v, shutting down...\n", sig) + // Stop all forwarding tasks + for _, f := range config.Forwardings { + stopForwarding(f) + } + return + } + } +} + +func readConfig(filename string) (Config, error) { + file, err := os.Open(filename) + if err != nil { + return Config{}, err + } + defer file.Close() + + config := Config{} + decoder := json.NewDecoder(file) + if err := decoder.Decode(&config); err != nil { + return Config{}, err + } + + return config, nil +} + +func runForwarding(f Forwarding) { + localAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", f.LocalAddress, f.LocalPort)) + if err != nil { + fmt.Printf("Failed to resolve local address: %v\n", err) + return + } + + remoteAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", f.RemoteAddress, f.RemotePort)) + if err != nil { + fmt.Printf("Failed to resolve remote address: %v\n", err) + return + } + listener, err := net.ListenTCP("tcp", localAddr) + if err != nil { + fmt.Printf("Failed to listen on local address: %v\n", err) + return + } + defer listener.Close() + + fmt.Printf("Started forwarding %s:%d to %s:%d\n", f.LocalAddress, f.LocalPort, f.RemoteAddress, f.RemotePort) + + for { + conn, err := listener.Accept() + if err != nil { + fmt.Printf("Failed to accept connection: %v\n", err) + continue + } + + go func() { + defer conn.Close() + + remoteConn, err := net.DialTCP("tcp", nil, remoteAddr) + if err != nil { + fmt.Printf("Failed to connect to remote address: %v\n", err) + return + } + defer remoteConn.Close() + + fmt.Printf("Forwarding %s -> %s\n", conn.RemoteAddr(), remoteConn.RemoteAddr()) + + // Forward data from a local connection to a remote connection + go copyConn(conn, remoteConn) + + // Forward data from remote connections back to local connections + copyConn(remoteConn, conn) + }() + } +} + +func stopForwarding(f Forwarding) { + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: net.ParseIP(f.LocalAddress), + Port: f.LocalPort, + }) + if err != nil { + fmt.Printf("Failed to stop forwarding %s:%d: %v\n", f.LocalAddress, f.LocalPort, err) + return + } + conn.Close() +} + +func copyConn(src net.Conn, dst net.Conn) { + buf := make([]byte, 1024) + for { + n, err := src.Read(buf) + if err != nil { + return + } + if _, err := dst.Write(buf[:n]); err != nil { + return + } + } +}