ftp,sftp: add socks_proxy support for SOCKS5 proxies

Fixes #3558
This commit is contained in:
Zach 2023-07-29 22:02:08 -04:00 committed by GitHub
parent f4449440f8
commit 347812d1d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 3 deletions

View File

@ -28,6 +28,7 @@ import (
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/env"
"github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/proxy"
"github.com/rclone/rclone/lib/readers"
)
@ -174,6 +175,18 @@ Enabled by default. Use 0 to disable.`,
If this is set and no password is supplied then rclone will ask for a password
`,
Advanced: true,
}, {
Name: "socks_proxy",
Default: "",
Help: `Socks 5 proxy host.
Supports the format user:pass@host:port, user@host:port, host:port.
Example:
myUser:myPass@localhost:9005
`,
Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
@ -218,6 +231,7 @@ type Options struct {
ShutTimeout fs.Duration `config:"shut_timeout"`
AskPassword bool `config:"ask_password"`
Enc encoder.MultiEncoder `config:"encoding"`
SocksProxy string `config:"socks_proxy"`
}
// Fs represents a remote FTP server
@ -359,7 +373,12 @@ func (f *Fs) ftpConnection(ctx context.Context) (c *ftp.ServerConn, err error) {
defer func() {
fs.Debugf(f, "> dial: conn=%T, err=%v", conn, err)
}()
conn, err = fshttp.NewDialer(ctx).Dial(network, address)
baseDialer := fshttp.NewDialer(ctx)
if f.opt.SocksProxy != "" {
conn, err = proxy.SOCKS5Dial(network, address, f.opt.SocksProxy, baseDialer)
} else {
conn, err = baseDialer.Dial(network, address)
}
if err != nil {
return nil, err
}

View File

@ -416,6 +416,17 @@ An example setting might be:
Note that when using an external ssh binary rclone makes a new ssh
connection for every hash it calculates.
`,
}, {
Name: "socks_proxy",
Default: "",
Help: `Socks 5 proxy host.
Supports the format user:pass@host:port, user@host:port, host:port.
Example:
myUser:myPass@localhost:9005
`,
Advanced: true,
}},
}
@ -457,6 +468,7 @@ type Options struct {
MACs fs.SpaceSepList `config:"macs"`
HostKeyAlgorithms fs.SpaceSepList `config:"host_key_algorithms"`
SSH fs.SpaceSepList `config:"ssh"`
SocksProxy string `config:"socks_proxy"`
}
// Fs stores the interface to the remote SFTP files

View File

@ -6,9 +6,11 @@ package sftp
import (
"context"
"io"
"net"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/lib/proxy"
"golang.org/x/crypto/ssh"
)
@ -22,8 +24,17 @@ type sshClientInternal struct {
// convenience function that connects to the given network address,
// initiates the SSH handshake, and then sets up a Client.
func (f *Fs) newSSHClientInternal(ctx context.Context, network, addr string, sshConfig *ssh.ClientConfig) (sshClient, error) {
dialer := fshttp.NewDialer(ctx)
conn, err := dialer.Dial(network, addr)
baseDialer := fshttp.NewDialer(ctx)
var (
conn net.Conn
err error
)
if f.opt.SocksProxy != "" {
conn, err = proxy.SOCKS5Dial(network, addr, f.opt.SocksProxy, baseDialer)
} else {
conn, err = baseDialer.Dial(network, addr)
}
if err != nil {
return nil, err
}

41
lib/proxy/socks.go Normal file
View File

@ -0,0 +1,41 @@
package proxy
import (
"fmt"
"net"
"strings"
"golang.org/x/net/proxy"
)
// SOCKS5Dial dials a net.Conn using a SOCKS5 proxy server.
// The socks5Proxy address can be in the form of [user:password@]host:port, [user@]host:port or just host:port if no auth is required.
// It will optionally take a proxyDialer to dial the SOCKS5 proxy server. If nil is passed, it will use the default net.Dialer.
func SOCKS5Dial(network, addr, socks5Proxy string, proxyDialer proxy.Dialer) (net.Conn, error) {
if proxyDialer == nil {
proxyDialer = &net.Dialer{}
}
var (
proxyAddress string
proxyAuth *proxy.Auth
)
if credsAndHost := strings.SplitN(socks5Proxy, "@", 2); len(credsAndHost) == 2 {
proxyCreds := strings.SplitN(credsAndHost[0], ":", 2)
proxyAuth = &proxy.Auth{
User: proxyCreds[0],
}
if len(proxyCreds) == 2 {
proxyAuth.Password = proxyCreds[1]
}
proxyAddress = credsAndHost[1]
} else {
proxyAddress = credsAndHost[0]
}
proxyDialer, err := proxy.SOCKS5("tcp", proxyAddress, proxyAuth, proxyDialer)
if err != nil {
return nil, fmt.Errorf("failed to create proxy dialer: %w", err)
}
return proxyDialer.Dial(network, addr)
}