Skip to content

Commit

Permalink
Locally bind to specific address via new LocalIpAddress property
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddenis-stx committed Apr 30, 2021
1 parent 8c3cbc6 commit 04c0371
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 8 deletions.
49 changes: 49 additions & 0 deletions FluentFTP.CSharpExamples/LocalIpAddress.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
}
}
5 changes: 3 additions & 2 deletions FluentFTP/Client/FtpClient_Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
14 changes: 14 additions & 0 deletions FluentFTP/Client/FtpClient_Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,20 @@ public string SendHostDomain {
set => m_SendHostDomain = value;
}

private IPAddress m_LocalIpAddress;
/// <summary>
/// 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.
/// </summary>
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()
}
Expand Down
61 changes: 61 additions & 0 deletions FluentFTP/Helpers/LocalPorts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LocalPorts.cs" company="">
//
// </copyright>
// <summary>
// The local ports.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace FluentFTP.Helpers
{
#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;

#endregion

/// <summary>
/// The local ports.
/// </summary>
public static class LocalPorts
{
/// <summary>
/// The r.
/// </summary>
internal static readonly Random R = new Random();

/// <summary>
/// Get available.
/// </summary>
/// <param name="localIpAddress">
/// The local ip address.
/// </param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public static int GetRandomAvailable(IPAddress localIpAddress)
{
lock (R)
{
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpListeners();
var inUsePorts = new HashSet<int>(
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;
}
}
}
}
2 changes: 1 addition & 1 deletion FluentFTP/Streams/FtpDataStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public void SetPosition(long pos) {
/// Creates a new data stream object
/// </summary>
/// <param name="conn">The control connection to be used for carrying out this operation</param>
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.");
}
Expand Down
37 changes: 32 additions & 5 deletions FluentFTP/Streams/FtpSocketStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {


/// <summary>
/// Stream class used for talking. Used by FtpClient, extended by FtpDataStream
/// </summary>
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;
}

/// <summary>
Expand Down Expand Up @@ -815,7 +821,7 @@ public void SetSocketOption(SocketOptionLevel level, SocketOptionName name, bool
/// <param name="ipVersions">Internet Protocol versions to support during the connection phase</param>
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);
Expand Down Expand Up @@ -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(); };

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 04c0371

Please sign in to comment.