我现在可以ssh请求到目标内网的一台机器,想要实现的诉求如下:
1.将目标内网的任意地址和端口暴露到本地的端口
2.将本地内网的任意地址和端口暴露到目标ssh服务
3.在本地监听一个socket5代理,可以通过代理访问目标内网的网络
4.在目标服务器监听一个socket5代理,可以通过代理访问本地内网的网络
实现只要ssh通,双边网络即可互通。
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/armon/go-socks5"
"golang.org/x/crypto/ssh"
)
var (
sshTunnelRunning int32 // 0 = stopped, 1 = running
closeTunnels chan bool
)
func localProxy(config Config) {
// 启动socks5代理
conf := &socks5.Config{}
server, err := socks5.New(conf)
if err != nil {
fmt.Println("启动socks5代理失败:", err)
return
}
go func() {
if err := server.ListenAndServe("tcp", config.LocalProxyPort); err != nil {
fmt.Println("socks5代理运行失败:", err)
return
}
}()
}
type Tunnel struct {
Type string `json:"type"`
Local string `json:"local"`
Remote string `json:"remote"`
}
type Config struct {
Address string `json:"address"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
LocalProxyPort string `json:"localProxyPort"`
CheckPort string `json:"checkPort"`
Tunnels []Tunnel `json:"tunnels"`
}
func createSSHSession(config Config, done chan bool, wg *sync.WaitGroup) {
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", config.Address, config.Port), &ssh.ClientConfig{
User: config.Username,
Auth: []ssh.AuthMethod{ssh.Password(config.Password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // You should not use this in production code.
Timeout: 5 * time.Second,
})
if err != nil {
log.Printf("failed to dial: %v", err)
done <- true
return
}
defer client.Close()
for _, tunnel := range config.Tunnels {
wg.Add(1)
switch tunnel.Type {
case "LOCAL":
go startLocalTunnel(client, tunnel.Local, tunnel.Remote, done, wg)
case "REMOTE":
go startRemoteTunnel(client, tunnel.Remote, tunnel.Local, done, wg)
case "DYNAMIC":
go startDynamicTunnel(client, tunnel.Local, done, wg)
default:
log.Printf("Unsupported tunnel type: %s", tunnel.Type)
wg.Done()
}
}
// Keep the session running
<-done
}
func startLocalTunnel(client *ssh.Client, localAddr, remoteAddr string, done chan bool, wg *sync.WaitGroup) {
defer wg.Done()
log.Printf("Creating local tunnel: %s -> %s\n", localAddr, remoteAddr)
localListener, err := net.Listen("tcp", localAddr)
if err != nil {
log.Printf("Failed to set up local listener on %s: %v", localAddr, err)
done <- true
return
}
defer localListener.Close()
go func() {
<-closeTunnels
localListener.Close()
}()
for {
localConn, err := localListener.Accept()
if err != nil {
log.Printf("Failed to accept local connection: %v", err)
done <- true
return
}
remoteConn, err := client.Dial("tcp", remoteAddr)
if err != nil {
log.Printf("Failed to dial remote address %s: %v", remoteAddr, err)
localConn.Close()
done <- true
return
}
go handleConnection(localConn, remoteConn)
}
}
func startRemoteTunnel(client *ssh.Client, remoteAddr, localAddr string, done chan bool, wg *sync.WaitGroup) {
defer wg.Done()
log.Printf("Creating remote tunnel: %s -> %s\n", remoteAddr, localAddr)
remoteListener, err := client.Listen("tcp", remoteAddr)
if err != nil {
log.Printf("Failed to set up remote listener on %s: %v", remoteAddr, err)
done <- true
return
}
defer remoteListener.Close()
go func() {
<-closeTunnels
remoteListener.Close()
}()
for {
remoteConn, err := remoteListener.Accept()
if err != nil {
log.Printf("Failed to accept remote connection: %v", err)
done <- true
return
}
localConn, err := net.Dial("tcp", localAddr)
if err != nil {
log.Printf("Failed to dial local address %s: %v", localAddr, err)
remoteConn.Close()
done <- true
return
}
go handleConnection(remoteConn, localConn)
}
}
func startDynamicTunnel(client *ssh.Client, localAddr string, done chan bool, wg *sync.WaitGroup) {
defer wg.Done()
conf := &socks5.Config{
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
return client.Dial(network, addr)
},
}
server, err := socks5.New(conf)
if err != nil {
log.Printf("Failed to create SOCKS5 server: %v", err)
done <- true
return
}
localListener, err := net.Listen("tcp", localAddr)
if err != nil {
log.Printf("Failed to set up local listener on %s: %v", localAddr, err)
done <- true
return
}
defer localListener.Close()
go func() {
<-closeTunnels
localListener.Close()
}()
log.Printf("SOCKS5 proxy listening on %s", localAddr)
if err := server.Serve(localListener); err != nil {
log.Printf("SOCKS5 server error: %v", err)
done <- true
return
}
}
func handleConnection(localConn, remoteConn net.Conn) {
defer localConn.Close()
defer remoteConn.Close()
go io.Copy(localConn, remoteConn)
io.Copy(remoteConn, localConn)
}
func sshTunnel(config Config) {
done := make(chan bool)
var wg sync.WaitGroup
closeTunnels = make(chan bool)
go createSSHSession(config, done, &wg)
atomic.StoreInt32(&sshTunnelRunning, 1)
<-done
close(closeTunnels) // Notify all tunnels to close
wg.Wait() // Ensure all tunnels are closed before restarting
atomic.StoreInt32(&sshTunnelRunning, 0)
fmt.Println("Session closed, attempting to reconnect in 5 seconds...")
time.Sleep(5 * time.Second)
}
func monitorTunnel(config Config) {
for {
time.Sleep(10 * time.Second)
if !testLocalProxy(config.CheckPort) {
log.Println("Local proxy is down, restarting SSH tunnel...")
go sshTunnel(config)
}
}
}
func testLocalProxy(port string) bool {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%s", port), 5*time.Second)
if err != nil {
return false
}
conn.Close()
return true
}
func loadConfig(filename string) (*Config, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
func main() {
config, err := loadConfig("config.json")
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
localProxy(*config)
go sshTunnel(*config)
monitorTunnel(*config)
}