diff --git a/FluentFTP.CSharpExamples/LocalIpAddress.cs b/FluentFTP.CSharpExamples/LocalIpAddress.cs
new file mode 100644
index 000000000..7a6b8fb3a
--- /dev/null
+++ b/FluentFTP.CSharpExamples/LocalIpAddress.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CSharpExamples
+{
+ using System.Net;
+ using System.Net.Sockets;
+
+ using FluentFTP;
+
+ public static class LocalIpAddress
+ {
+ public static void LocaIpAddressExample()
+ {
+ // IP addresses for current host inside myprivatedomain
+ var localIpAddresses = new[]
+ {
+ IPAddress.Parse("10.244.191.143"),
+ IPAddress.Parse("fcec:177:cfbd:6555:8f8c::1")
+ };
+
+ foreach (var localIpAddress in localIpAddresses)
+ {
+ // let's say that ftp.myprivatedomain has ipv4 and ipv5 addresses
+ using (var f = new FtpClient("ftp.myprivatedomain", "test", "test")
+ {
+ InternetProtocolVersions = localIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? FtpIpVersion.IPv6 : FtpIpVersion.IPv4,
+
+ // Equivalent to lftp's ftp:port-ipv[4|6] and net:socket-bind-ipv[4|6] (see http://manpages.org/lftp)
+ LocalIpAddress = localIpAddress
+ })
+ {
+ f.Connect();
+
+ Console.WriteLine($"Connected to {f.RemoteEndPoint} from {f.LocalEndPoint}");
+ foreach (var file in f.GetListing())
+ {
+ Console.Out.WriteLine(file);
+ }
+
+ f.Disconnect();
+ }
+ }
+ }
+ }
+}
diff --git a/FluentFTP/Client/FtpClient_Connection.cs b/FluentFTP/Client/FtpClient_Connection.cs
index ac8f42567..c4320c1ca 100644
--- a/FluentFTP/Client/FtpClient_Connection.cs
+++ b/FluentFTP/Client/FtpClient_Connection.cs
@@ -310,6 +310,7 @@ protected FtpClient CloneConnection() {
conn.ServerHandler = ServerHandler;
conn.UploadDirectoryDeleteExcluded = UploadDirectoryDeleteExcluded;
conn.DownloadDirectoryDeleteExcluded = DownloadDirectoryDeleteExcluded;
+ conn.LocalIpAddress = LocalIpAddress;
// configure new connection as clone of self (.NET core props only)
#if CORE
@@ -359,7 +360,7 @@ public virtual void Connect() {
}
if (m_stream == null) {
- m_stream = new FtpSocketStream(m_SslProtocols);
+ m_stream = new FtpSocketStream(m_SslProtocols, m_LocalIpAddress);
m_stream.Client = this;
m_stream.ValidateCertificate += new FtpSocketStreamSslValidation(FireValidateCertficate);
}
@@ -517,7 +518,7 @@ public virtual void Connect() {
}
if (m_stream == null) {
- m_stream = new FtpSocketStream(m_SslProtocols);
+ m_stream = new FtpSocketStream(m_SslProtocols, m_LocalIpAddress);
m_stream.Client = this;
m_stream.ValidateCertificate += new FtpSocketStreamSslValidation(FireValidateCertficate);
}
diff --git a/FluentFTP/Client/FtpClient_Properties.cs b/FluentFTP/Client/FtpClient_Properties.cs
index 6fa96ea8f..d7c039f4b 100644
--- a/FluentFTP/Client/FtpClient_Properties.cs
+++ b/FluentFTP/Client/FtpClient_Properties.cs
@@ -1042,6 +1042,20 @@ public string SendHostDomain {
set => m_SendHostDomain = value;
}
+ private IPAddress m_LocalIpAddress;
+ ///
+ /// Allow to bind sockets to a particular local IP/interface.
+ /// Useful if you have several usable public IP addresses and want to use a particular one.
+ ///
+ public IPAddress LocalIpAddress
+ {
+ get => m_LocalIpAddress;
+ set => m_LocalIpAddress = value;
+ }
+
+ public IPEndPoint LocalEndPoint => this.m_stream.LocalEndPoint;
+ public IPEndPoint RemoteEndPoint => this.m_stream.RemoteEndPoint;
+
// ADD PROPERTIES THAT NEED TO BE CLONED INTO
// FtpClient.CloneConnection()
}
diff --git a/FluentFTP/Helpers/LocalPorts.cs b/FluentFTP/Helpers/LocalPorts.cs
new file mode 100644
index 000000000..b9cbd13ea
--- /dev/null
+++ b/FluentFTP/Helpers/LocalPorts.cs
@@ -0,0 +1,61 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+//
+//
+//
+// The local ports.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace FluentFTP.Helpers
+{
+ #region Usings
+
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Net;
+ using System.Net.NetworkInformation;
+
+ #endregion
+
+ ///
+ /// The local ports.
+ ///
+ public static class LocalPorts
+ {
+ ///
+ /// The r.
+ ///
+ internal static readonly Random R = new Random();
+
+ ///
+ /// Get available.
+ ///
+ ///
+ /// The local ip address.
+ ///
+ ///
+ /// The .
+ ///
+ public static int GetRandomAvailable(IPAddress localIpAddress)
+ {
+ lock (R)
+ {
+ var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
+ var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpListeners();
+ var inUsePorts = new HashSet(
+ tcpConnInfoArray.Where(ipEndPoint => localIpAddress.Equals(ipEndPoint.Address))
+ .Select(ipEndPoint => ipEndPoint.Port));
+ int localPort;
+ do
+ {
+ localPort = 1025 + R.Next(32000);
+ }
+ while (inUsePorts.Contains(localPort));
+
+ return localPort;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/FluentFTP/Streams/FtpDataStream.cs b/FluentFTP/Streams/FtpDataStream.cs
index 936998d98..6fdbe9739 100644
--- a/FluentFTP/Streams/FtpDataStream.cs
+++ b/FluentFTP/Streams/FtpDataStream.cs
@@ -150,7 +150,7 @@ public void SetPosition(long pos) {
/// Creates a new data stream object
///
/// The control connection to be used for carrying out this operation
- public FtpDataStream(FtpClient conn) : base(conn.SslProtocols) {
+ public FtpDataStream(FtpClient conn) : base(conn.SslProtocols, conn.LocalIpAddress) {
if (conn == null) {
throw new ArgumentException("The control connection cannot be null.");
}
diff --git a/FluentFTP/Streams/FtpSocketStream.cs b/FluentFTP/Streams/FtpSocketStream.cs
index 0d77550ab..c8b61a769 100644
--- a/FluentFTP/Streams/FtpSocketStream.cs
+++ b/FluentFTP/Streams/FtpSocketStream.cs
@@ -9,22 +9,28 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
+using System.Net.NetworkInformation;
+
+using FluentFTP.Helpers;
using FluentFTP.Exceptions;
#if CORE || NET45
using System.Threading.Tasks;
-
#endif
namespace FluentFTP {
+
+
///
/// Stream class used for talking. Used by FtpClient, extended by FtpDataStream
///
public class FtpSocketStream : Stream, IDisposable {
private SslProtocols m_SslProtocols;
+ private IPAddress m_LocalIpAddress;
public FtpClient Client;
- public FtpSocketStream(SslProtocols defaultSslProtocols) {
+ public FtpSocketStream(SslProtocols defaultSslProtocols, IPAddress localIpAddress) {
m_SslProtocols = defaultSslProtocols;
+ m_LocalIpAddress = localIpAddress;
}
///
@@ -815,7 +821,7 @@ public void SetSocketOption(SocketOptionLevel level, SocketOptionName name, bool
/// Internet Protocol versions to support during the connection phase
public void Connect(string host, int port, FtpIpVersion ipVersions) {
#if CORE
- IPAddress[] addresses = Dns.GetHostAddressesAsync(host).Result;
+ IPAddress[] addresses = Dns.GetHostAddresses(host);
#else
IAsyncResult ar = null;
var addresses = Dns.GetHostAddresses(host);
@@ -861,10 +867,14 @@ public void Connect(string host, int port, FtpIpVersion ipVersions) {
}
m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ BindLocalIfNecessary();
+
#if CORE
+
+
var args = new SocketAsyncEventArgs {
- RemoteEndPoint = new IPEndPoint(addresses[i], port)
- };
+ RemoteEndPoint = new IPEndPoint(addresses[i], port)
+ };
var connectEvent = new ManualResetEvent(false);
args.Completed += (s, e) => { connectEvent.Set(); };
@@ -968,6 +978,7 @@ public async Task ConnectAsync(string host, int port, FtpIpVersion ipVersions, C
}
m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ BindLocalIfNecessary();
#if CORE
await EnableCancellation(m_socket.ConnectAsync(addresses[i], port), token, () => CloseSocket());
break;
@@ -1305,6 +1316,22 @@ public void EndAccept(IAsyncResult ar) {
}
}
#endif
+ private void BindLocalIfNecessary()
+ {
+ if (this.m_LocalIpAddress == null)
+ {
+ return;
+ }
+
+ var localPort = LocalPorts.GetRandomAvailable(m_LocalIpAddress);
+ var localEndpoint = new IPEndPoint(m_LocalIpAddress, localPort);
+
+#if DEBUG
+ Client.LogStatus(FtpTraceLevel.Verbose, $"Will now bind to {localEndpoint}");
+#endif
+
+ this.m_socket.Bind(localEndpoint);
+ }
#if CORE
internal SocketAsyncEventArgs BeginAccept() {