Golangでssh port forwardingをする
Pocket

自前で作ってるGolang製のsshクライアントにssh port forwardingを追加したくなったので、まずはサンプルコードを書いてみることにした。
StackOverflowにちょうどいいサンプルがあったので参考にさせてもらったところ、ターミナルセッションとは別にsshの接続をする必要がありそうだ。ポートフォワード自体はio.Copyで対応できるようだ。

Sponsored Links

以下、サンプル(こちらにもおいてある)。

● ssh_term_portforward.go

package main
import (
"fmt"
"io"
"net"
"os"
"os/signal"
"syscall"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
var (
host       = "targethost"
port       = "22"
user       = "user"
pass       = "password"
localPort  = "localhost:10022"
remotePort = "localhost:22"
)
func forward(localConn net.Conn, config *ssh.ClientConfig) {
// Setup sshClientConn (type *ssh.ClientConn)
sshClientConn, err := ssh.Dial("tcp", host+":"+port, config)
if err != nil {
fmt.Println("ssh.Dial failed: %s", err)
}
// Setup sshConn (type net.Conn)
sshConn, err := sshClientConn.Dial("tcp", remotePort)
// Copy localConn.Reader to sshConn.Writer
go func() {
_, err = io.Copy(sshConn, localConn)
if err != nil {
fmt.Println("io.Copy failed: %v", err)
}
}()
// Copy sshConn.Reader to localConn.Writer
go func() {
_, err = io.Copy(localConn, sshConn)
if err != nil {
fmt.Println("io.Copy failed: %v", err)
}
}()
}
func main() {
// Create sshClientConfig
sshConfig := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(pass),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Setup localListener (type net.Listener)
localListener, err := net.Listen("tcp", localPort)
if err != nil {
fmt.Println("net.Listen failed: %v", err)
} else {
go func() {
for {
// Setup localConn (type net.Conn)
localConn, err := localListener.Accept()
if err != nil {
fmt.Println("listen.Accept failed: %v", err)
}
go forward(localConn, sshConfig)
}
}()
}
// SSH connect.
client, err := ssh.Dial("tcp", host+":"+port, sshConfig)
// Create Session
session, err := client.NewSession()
defer session.Close()
// キー入力を接続先が認識できる形式に変換する(ここがキモ)
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
fmt.Println(err)
}
defer terminal.Restore(fd, state)
// ターミナルサイズの取得
w, h, err := terminal.GetSize(fd)
if err != nil {
fmt.Println(err)
}
modes := ssh.TerminalModes{
ssh.ECHO:          1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
err = session.RequestPty("xterm", h, w, modes)
if err != nil {
fmt.Println(err)
}
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
err = session.Shell()
if err != nil {
fmt.Println(err)
}
// ターミナルサイズの変更検知・処理
signal_chan := make(chan os.Signal, 1)
signal.Notify(signal_chan, syscall.SIGWINCH)
go func() {
for {
s := <-signal_chan
switch s {
case syscall.SIGWINCH:
fd := int(os.Stdout.Fd())
w, h, _ = terminal.GetSize(fd)
session.WindowChange(h, w)
}
}
}()
err = session.Wait()
if err != nil {
fmt.Println(err)
}
}

 

ローカルのポートが空いてない場合は、ポートフォワードは行わずにそのまま接続するようにしている。
そのへんは、用途に合わせて書き換えてやればいいだろう。

 

【参考】

 

Pocket

Written by blacknon

インフラエンジニア(…のつもり)。 仕事で使うならクライアントはWindowsよりはUNIXの方が好き。 大体いつも眠い。

Leave a Comment

メールアドレスが公開されることはありません。

*