package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"sync"
"time"
"golang.org/x/crypto/ssh"
"github.com/armon/go-socks5"
"ssh-client/tunnel"
)
var (
sshClient *ssh.Client
clientMux sync.Mutex
)
func main() {
// Define command-line flags
server := flag.String("server", " ", "SSH server address")
port := flag.String("port", " ", "SSH server port")
user := flag.String("user", " ", "SSH username")
pass := flag.String("pass", " ", "SSH password")
listen := flag.String("listen", "localhost:1080", "Local address to listen on")
proxyHost := flag.String("proxyHost", " ", "HTTP proxy host")
proxyPort := flag.Int("proxyPort", , "HTTP proxy port")
// Parse command-line flags
flag.Parse()
sshConfig := &ssh.ClientConfig{
User: *user,
Auth: []ssh.AuthMethod{
ssh.Password(*pass),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
sshHost := fmt.Sprintf("%s:%s", *server, *port)
var err error
// Create HTTP proxy
httpProxy, err := tunnel.NewHttpProxy(*proxyHost, *proxyPort)
if err != nil {
log.Fatalf("Failed to create HTTP proxy: %s", err)
}
// Initial SSH connection
sshClient, err = connectToSSH(sshHost, sshConfig, httpProxy)
if err != nil {
log.Fatalf("Failed to establish initial SSH connection: %s", err)
}
defer httpProxy.Close()
listener, err := net.Listen("tcp", *listen)
if err != nil {
log.Fatalf("Failed to listen on %s: %s", *listen, err)
}
defer listener.Close()
log.Printf("SOCKS5 proxy listening on %s", *listen)
socksConf := &socks5.Config{
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialThroughSSH(ctx, network, addr, sshHost, sshConfig, httpProxy)
},
}
socksServer, err := socks5.New(socksConf)
if err != nil {
log.Fatalf("Failed to create SOCKS5 server: %s", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("Failed to accept connection: %s", err)
continue
}
go func() {
defer conn.Close()
if err := socksServer.ServeConn(conn); err != nil {
log.Printf("Failed to serve connection: %s", err)
}
}()
}
}
func connectToSSH(sshHost string, sshConfig *ssh.ClientConfig, proxy *tunnel.HttpProxy) (*ssh.Client, error) {
for {
proxyConn, err := proxy.OpenConnection(sshHost, 0, 10000, 10000)
if err != nil {
log.Printf("Failed to connect to SSH server via proxy: %s. Retrying...", err)
time.Sleep(2 * time.Second)
continue
}
clientConn, chans, reqs, err := ssh.NewClientConn(proxyConn, sshHost, sshConfig)
if err == nil {
log.Printf("Successfully connected to SSH server at %s via proxy", sshHost)
return ssh.NewClient(clientConn, chans, reqs), nil
}
log.Printf("Failed to dial SSH: %s. Retrying...", err)
proxyConn.Close()
//time.Sleep(2 * time.Second)
}
}
func dialThroughSSH(ctx context.Context, network, addr, sshHost string, sshConfig *ssh.ClientConfig, proxy *tunnel.HttpProxy) (net.Conn, error) {
clientMux.Lock()
defer clientMux.Unlock()
conn, err := sshClient.Dial(network, addr)
if err == nil {
return conn, nil
}
// Reconnect if the connection is lost
log.Printf("SSH connection lost: %s. Reconnecting...", err)
sshClient, err = connectToSSH(sshHost, sshConfig, proxy)
if err != nil {
return nil, err
}
return sshClient.Dial(network, addr)
}