Press "Enter" to skip to content

基于SSH的双内网链路打通

我现在可以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)
}
发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注