英文:
Simple SSH port forward in Golang
问题
我正在尝试使用Go创建(并稍后关闭)一个简单的TCP端口转发SSH。我对Golang和静态类型语言都不太熟悉(之前使用Ruby)。
在终端中,我只需运行ssh -L 9000:localhost:9999 user@server.com
就可以实现我需要的功能。我想用Go以编程的方式实现同样的功能。
我尝试使用这个示例作为起点,并使用这个最近的测试来尝试理解该怎么做,但现在我有一堆混乱的代码,似乎这实际上是一件非常简单的事情。
非常感谢任何帮助!:-)
英文:
I'm trying to create (and later close) a simple TCP port forward over SSH with Go. I'm new to Golang and statically typed languages. (Coming from Ruby.)
In a terminal I would simply run ssh -L 9000:localhost:9999 user@server.com
and this accomplishes what I need. I want to do the same, programmatically with Go.
I have tried using this example as a starting point and this recent test to try to understand what to do, but now I have a pile of confusing jumbled code when it seems like this is actually a very simple thing to do.
Any help would be very much appreciated!
答案1
得分: 22
我终于弄清楚如何做到这一点了,我从IRC频道的schmichael那里得到了一些提示。感谢大家!
编辑:稍作解释:
我遇到的主要问题是我没有意识到需要设置一个本地的net.Listener
(而不仅仅是本地的net.Conn
)来接收本地请求并在转发字节之前创建net.Conn
。
此外,存在端口转发和反向端口转发,我之前没有详细考虑到常规端口转发也会发送字节回来,所以我还没有实现将远程读取器复制到本地写入器,但这是非常必要的。
以下是试图概括此代码的要点:
- 监听本地端口9000。
- 当从本地端口9000尝试读取时(
listener.Accept()
), - 接受连接并返回本地的
io.Reader
和io.Writer
, - 连接到远程服务器,并
- 连接到远程端口9999,返回一个
io.Reader
和io.Writer
。 - 不断将本地的
io.Reader
字节复制到远程的io.Writer
, - 不断将远程的
io.Reader
字节复制到本地的io.Writer
。
以下是代码:
package main
// 从本地端口9000转发到远程端口9999
import (
"io"
"log"
"net"
"golang.org/x/crypto/ssh"
)
var (
username = "root"
password = "password"
serverAddrString = "192.168.1.100:22"
localAddrString = "localhost:9000"
remoteAddrString = "localhost:9999"
)
func forward(localConn net.Conn, config *ssh.ClientConfig) {
// 设置sshClientConn(类型为*ssh.ClientConn)
sshClientConn, err := ssh.Dial("tcp", serverAddrString, config)
if err != nil {
log.Fatalf("ssh.Dial失败:%s", err)
}
// 设置sshConn(类型为net.Conn)
sshConn, err := sshClientConn.Dial("tcp", remoteAddrString)
// 将localConn.Reader复制到sshConn.Writer
go func() {
_, err = io.Copy(sshConn, localConn)
if err != nil {
log.Fatalf("io.Copy失败:%v", err)
}
}()
// 将sshConn.Reader复制到localConn.Writer
go func() {
_, err = io.Copy(localConn, sshConn)
if err != nil {
log.Fatalf("io.Copy失败:%v", err)
}
}()
}
func main() {
// 设置SSH配置(类型为*ssh.ClientConfig)
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
}
// 设置localListener(类型为net.Listener)
localListener, err := net.Listen("tcp", localAddrString)
if err != nil {
log.Fatalf("net.Listen失败:%v", err)
}
for {
// 设置localConn(类型为net.Conn)
localConn, err := localListener.Accept()
if err != nil {
log.Fatalf("listen.Accept失败:%v", err)
}
go forward(localConn, config)
}
}
英文:
I finally figured out how to do this, I got hints from schmichael in an IRC channel. Thanks to all!
EDIT: A little explanation:
A big part of the problem I was having was that I did not realize a local net.Listener
(not just a local net.Conn
) needed setup to receive a local request and create the net.Conn
before forwarding the bytes.
Also, there exist both port forwards and reverse port forwards and I hadn't previously thought in detail about the fact that a regular port forward also sends bytes back, so copying the remote reader to local writer was not something I had implemented, yet it's very much needed.
Here is an attempt to relate the essence of what this code does:
- Listen on local port 9000.
- Upon attempted read from local port 9000: (
listener.Accept()
), - Accept connection and return a local
io.Reader
andio.Writer
and, - Connect to remote server and,
- Connect to remote port 9999 returning a
io.Reader
andio.Writer
. - Continually copy local
io.Reader
bytes to remoteio.Writer
, - Continually copy remote
io.Reader
bytes to localio.Writer
.
Here is the code:
package main
// Forward from local port 9000 to remote port 9999
import (
"io"
"log"
"net"
"golang.org/x/crypto/ssh"
)
var (
username = "root"
password = "password"
serverAddrString = "192.168.1.100:22"
localAddrString = "localhost:9000"
remoteAddrString = "localhost:9999"
)
func forward(localConn net.Conn, config *ssh.ClientConfig) {
// Setup sshClientConn (type *ssh.ClientConn)
sshClientConn, err := ssh.Dial("tcp", serverAddrString, config)
if err != nil {
log.Fatalf("ssh.Dial failed: %s", err)
}
// Setup sshConn (type net.Conn)
sshConn, err := sshClientConn.Dial("tcp", remoteAddrString)
// Copy localConn.Reader to sshConn.Writer
go func() {
_, err = io.Copy(sshConn, localConn)
if err != nil {
log.Fatalf("io.Copy failed: %v", err)
}
}()
// Copy sshConn.Reader to localConn.Writer
go func() {
_, err = io.Copy(localConn, sshConn)
if err != nil {
log.Fatalf("io.Copy failed: %v", err)
}
}()
}
func main() {
// Setup SSH config (type *ssh.ClientConfig)
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
}
// Setup localListener (type net.Listener)
localListener, err := net.Listen("tcp", localAddrString)
if err != nil {
log.Fatalf("net.Listen failed: %v", err)
}
for {
// Setup localConn (type net.Conn)
localConn, err := localListener.Accept()
if err != nil {
log.Fatalf("listen.Accept failed: %v", err)
}
go forward(localConn, config)
}
}
答案2
得分: 5
我已经使用了你(damick)的示例代码来构建一个小型开源工具:SSHTunnel
https://github.com/SommerEngineering/SSHTunnel
因此,该代码在GitHub上是免费提供给任何人使用的:请随意用于学习目的或其他任何用途 我已经提到了你的昵称,并链接到了这个问题。
最好的祝福,
Thorsten。
英文:
I have used your (damick) example code to build a tiny open source tool: SSHTunnel
https://github.com/SommerEngineering/SSHTunnel
Therefore, the code is freely available at GitHub for anyone: Please feel free to use it for learning purposes or for anything else I have mentioned your nickname and also linked to this question.
Best regards,
Thorsten.
答案3
得分: 2
我已经完成了一个名为mallory的简单SSH端口转发工具。
它提供的是HTTP代理而不是SOCKS代理,与ssh -D
非常相似。
核心代码与damick的答案相似。
- 创建ClientConfig
- 使用配置信息
ssh.Dial
到远程SSH服务器并返回Client
- 现在你可以使用
Client.Dial
来转发任何你喜欢的东西。
Dial从远程主机初始化到addr的连接。所得到的连接具有零的LocalAddr()和RemoteAddr()。
- 如果你想提供一个SOCKS代理服务器,请使用
Client.Dial
连接到远程服务器。
1: https://github.com/justmao945/mallory
英文:
I'v finished a simple SSH port forward tool called mallory.
It provides HTTP proxy instead of SOCKS proxy, which is really similar to ssh -D
.
The core code is similar to damick's answer.
- Create ClientConfig
ssh.Dial
to remote SSH server with the config and returnClient
- Now you can use
Client.Dial
to forward anything you like.
>Dial initiates a connection to the addr from the remote host. The resulting connection has a zero LocalAddr() and RemoteAddr(). - If you want to serve a SOCKS proxy server, use
Client.Dial
to connect to the remote server.
1: https://github.com/justmao945/mallory
答案4
得分: 0
我写了一个工具,叫做gosshtool。使用这个工具,你可以轻松地在Go语言中创建一个简单的SSH TCP端口转发。你可以在这里找到这个工具:https://github.com/scottkiss/gosshtool
我使用这个工具实现了一个端口转发服务器示例项目:https://github.com/scottkiss/gooverssh
英文:
I Wrote a tool,Called gosshtool,with this tool,you can easy to create a simple TCP port forward over SSH with Go.https://github.com/scottkiss/gosshtool
I use this tool implemented a port forward server example project:
https://github.com/scottkiss/gooverssh
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论