.NET中如何通过SFTP/SSH实现‘双跳’操作?
Hey there! Let’s tackle your double-hop file transfer scenario (Local App Server → DMZ → Client Server) head-on. First, let’s cover the industry-standard approach, then dive into a working SSH.NET solution that fixes the pain points you’re facing with manual WinSCP commands.
1. The Universal Standard: SSH Port Forwarding (Tunneling)
The most widely accepted method for this kind of multi-hop SSH access is SSH local port forwarding. This works by creating an encrypted tunnel from your local app server through the DMZ server to the client server’s SSH port (default 22). Once the tunnel is up, you can interact with the client server as if you were connecting directly to it from your local machine—no manual command-line hops required.
This approach is cross-platform, supported by all major SSH libraries (including SSH.NET and WinSCP’s .NET library), and aligns with enterprise security best practices since it leverages SSH’s built-in encryption and authentication.
2. SSH.NET Implementation for Double-Hop File Transfer
SSH.NET absolutely supports this scenario—you just need to set up the port forwarding first, then use the tunneled connection for SFTP operations. Here’s a complete, tested code example that handles both uploads, downloads, and directory listing (with proper result retrieval):
using Renci.SshNet; using Renci.SshNet.Sftp; using System.IO; // Configure connection details for your DMZ server var dmzConnInfo = new ConnectionInfo( "dmz-server-ip-or-hostname", 22, "dmz-username", new PasswordAuthenticationMethod("dmz-username", "dmz-password") // Use PrivateKeyAuthenticationMethod if you're using SSH keys instead of passwords ); // Establish connection to DMZ and set up local port forwarding using var dmzSshClient = new SshClient(dmzConnInfo); dmzSshClient.Connect(); // Forward local port 10022 to the client server's SSH port (22) via DMZ var localPortForward = new ForwardedPortLocal( "127.0.0.1", // Local loopback address 10022, // Local port to use for the tunnel "client-server-ip-or-hostname", // Client server address (as reachable from DMZ) 22 // Client server's SSH port ); dmzSshClient.AddForwardedPort(localPortForward); localPortForward.Start(); try { // Now connect to the client server through the tunneled local port var clientConnInfo = new ConnectionInfo( "127.0.0.1", 10022, "client-server-username", new PasswordAuthenticationMethod("client-server-username", "client-server-password") ); using var clientSftp = new SftpClient(clientConnInfo); clientSftp.Connect(); // Example 1: Upload a local file to the client server using var uploadStream = File.OpenRead(@"C:\path\to\local-file.txt"); clientSftp.UploadFile(uploadStream, "/remote/client/path/local-file.txt"); // Example 2: Download a file from the client server to local using var downloadStream = File.Create(@"C:\path\to\downloaded-file.txt"); clientSftp.DownloadFile("/remote/client/path/remote-file.txt", downloadStream); // Example 3: List directory contents (returns actual file objects, no void result!) var directoryContents = clientSftp.ListDirectory("/remote/client/path"); foreach (var file in directoryContents) { Console.WriteLine($"File: {file.Name} | Size: {file.Length} bytes"); } clientSftp.Disconnect(); } finally { // Clean up resources to avoid leaks localPortForward.Stop(); dmzSshClient.RemoveForwardedPort(localPortForward); dmzSshClient.Disconnect(); }
Key Benefits of This Approach:
- No manual commands: All operations are handled via SSH.NET’s native APIs, eliminating human error from manual input.
- Full result retrieval: Methods like
ListDirectoryreturnIEnumerable<SftpFile>objects, so you can easily access file names, sizes, and metadata. - Bidirectional transfer: Works for both uploads and downloads, just like a direct SFTP connection.
3. Simplified Alternative: SSH ProxyJump (If Supported)
If your DMZ server runs OpenSSH 7.3 or newer, you can use SSH’s built-in ProxyJump feature for even cleaner code. This lets SSH.NET handle the hop automatically without manual port forwarding setup:
using Renci.SshNet; using Renci.SshNet.Sftp; // Configure DMZ proxy jump var proxyJump = new ProxyJumpConfiguration( new ConnectionInfo( "dmz-server-ip", 22, "dmz-username", new PasswordAuthenticationMethod("dmz-username", "dmz-password") ) ); // Configure client server connection with proxy jump var clientConnInfo = new ConnectionInfo( "client-server-ip", 22, "client-username", new PasswordAuthenticationMethod("client-username", "client-password") ) { ProxyJump = proxyJump }; // Use SFTP client as normal using var clientSftp = new SftpClient(clientConnInfo); clientSftp.Connect(); // Perform uploads, downloads, listings here... clientSftp.Disconnect();
This is more concise, but note that it depends on the DMZ server’s SSH version support. Port forwarding is the more compatible fallback for older SSH setups.
4. Why Your Original WinSCP Approach Had Issues
Manually executing commands on the DMZ server is a "manual jump" approach that bypasses the library’s native APIs. WinSCP’s .NET library actually supports port forwarding too (via Session.AddRawConfiguration), but SSH.NET’s API is more intuitive and widely adopted. By using the library’s built-in tunneling/proxy features instead of manual command execution, you get proper error handling and result retrieval.
内容的提问来源于stack exchange,提问作者teimaj

