ScSSHChannel on Android: Cannot bind to address...
Posted: Mon 27 Mar 2017 10:24
Hello,
i'm using the Trial-Version of SecureBridge.
Here is the short version of my problem: After SSH Disconnect i can't connect the Channel again when running on Android -> 'Cannot bind to address 'localhost'. Address already in use. Socket Error Code: 98($62)'.
The long version:
I wrote a very straight-forward and simple demo consisting of two projects:
A Server-Project (VCL), running under Windows 10, with a TScSSHServer, a TScFileStorage and a TIdHTTPServer.
A Client-Project (FMX) with a TScSSHClient, a TScFileStorage, a TScChannel and a TIdHTTP.
The Server creates its components and waits for an incoming GET-Request, answering with a string.
The Client has a 'Connect'-Button to connect to the server (ScSSHClient.Connect) and establish the Tunnel (ScSSHChannel.Connect).
The Client has a 'Send'-Button to send the Get-Request.
The Client has a 'Disconnect'-Button to disconnect the ScSSHChannel, then disconnect the ScSSHClient.
On Win32 and iOs, everything works fine: The Client connects, sends it's request, gets an answer, disconnects. I can repeat this as often as i want.
On Android, everything works fine for the first connection. If i try to connect again after disconnect, sometimes it works (10%), but sometimes (90%) i got an exception: "Im Projekt ProjectClientFMX.apk ist eine Exception der Klasse std::ostream mit der Meldung 'Cannot bind to address 'localhost'. Address already in use. Socket Error Code: 98($62)' aufgetreten."
PS: Under Android, sometimes i get an Exception when trying to connect the first time: "Error on data reading from the connection: Interrupted system call. Socket Error Code: 4($4)". But this is not reproducable. Maybe this is a side effect of the "You are using SecureBridge Trial edition!"-Message?
Am I doing something wrong or is it a bug?
Here is the Server-Source (VCL-Form with one TButton and one TEdit):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, ScBridge, Vcl.StdCtrls, System.IOUtils,
IdContext, IdCustomHTTPServer, IdBaseComponent, IdComponent, IdCustomTCPServer, IdHTTPServer,
ScSSHServer, ScSSHChannel, ScSSHUtils, ScUtils;
const
SERVERKEYNAME = 'serverkey';
type
TForm1 = class(TForm)
Label1: TLabel;
edFingerprint: TEdit;
btGenerateHostKey: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure ScSSHServerChannelError(Sender: TObject; ChannelInfo: TScSSHChannelInfo; E: Exception);
procedure ScSSHServerError(Sender: TObject; E: Exception);
procedure btGenerateHostKeyClick(Sender: TObject);
private
MyScSSHServer: TScSSHServer;
MyScFileStorage: TScFileStorage;
MyIdHTTPServer: TIdHTTPServer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btGenerateHostKeyClick(Sender: TObject);
var
fp: string;
User: TScUser;
Key: TScKey;
begin
// eigenen Key löschen und neu erzeugen.
MyScSSHServer.Storage.DeleteStorage;
User := TScUser.Create(MyScSSHServer.Storage.Users);
User.UserName := 'Olli';
User.Password := 'Olli';
User.Authentications := [uaPassword];
try
MyScSSHServer.Storage.Keys.CheckKeyName(SERVERKEYNAME);
except
on E:Exception do
ShowMessage(Format('Key with this name already exists: %s', [E.Message]));
end;
Key := TscKey.Create(MyScSSHServer.Storage.Keys);
Key.KeyName := SERVERKEYNAME;
Key.Generate(aaRSA, 1024);
Key.GetFingerprint(haSHA2_256, fp);
ShowMessage(Format('Fine, Key added. Fingerprint = %s', [fp]));
edFingerprint.Text := fp;
//Key.ExportTo('d:\publickey.pub', TRUE, '', saTripleDES_cbc, kfIETF, 'erzeugt am '+DateToStr(Now) + ' um ' + TimeToStr(Now));
MyScSSHServer.Active := TRUE;
MyIdHTTPServer.Active := TRUE;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
fp: String;
begin
MyScSSHServer := TScSSHServer.Create(nil);
MyScFileStorage := TScFileStorage.Create(nil);
MyIdHTTPServer := TIdHTTPServer.Create(nil);
MyScSSHServer.KeyNameRSA := SERVERKEYNAME;
MyScSSHServer.OnError := ScSSHServerError;
MyScSSHServer.OnChannelError := ScSSHServerChannelError;
MyScSSHServer.Storage := MyScFileStorage;
MyScFileStorage.Path := TPath.GetDocumentsPath + TPath.DirectorySeparatorChar + 'My_SSH_Server_Test' + TPath.DirectorySeparatorChar;
MyScFileStorage.Algorithm := saAES256_cbc;
MyScFileStorage.Password := '12345678';
MyIdHTTPServer.DefaultPort := 7779;
MyIdHTTPServer.OnCommandGet := IdHTTPServerCommandGet;
MyScSSHServer.Active := TRUE;
MyIdHTTPServer.Active := TRUE;
MyScSSHServer.Storage.Keys.KeyByName(SERVERKEYNAME).GetFingerprint(TScHashAlgorithm.haSHA2_256, fp);
edFingerprint.Text := fp;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyIdHTTPServer.Active := FALSE;
MyScSSHServer.Active := FALSE;
MyScSSHServer.Free;
MyScFileStorage.Free;
MyIdHTTPServer.Free;
end;
procedure TForm1.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentText := 'It is ' + TimeToStr(Now);
end;
procedure TForm1.ScSSHServerChannelError(Sender: TObject; ChannelInfo: TScSSHChannelInfo; E: Exception);
begin
ShowMessage('SSH Server Channel Error, Exception: ' + E.Message);
end;
procedure TForm1.ScSSHServerError(Sender: TObject; E: Exception);
begin
ShowMessage('SSH Server Error, Exception: ' + E.Message);
end;
end.
Here is the Client-Source (FMX-Form with one TLabel, four TButton and one TEdit):
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.IOUtils,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Edit, FMX.Controls.Presentation,
IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,
ScBridge, ScSSHClient, ScSSHChannel, ScUtils;
const
SERVERKEYNAME = 'serverkey';
type
TForm1 = class(TForm)
btDelKeys: TButton;
btConnect: TButton;
btSend: TButton;
btDisconnect: TButton;
EditExpectedFingerprint: TEdit;
Label1: TLabel;
procedure btDelKeysClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ScSSHClient1ServerKeyValidate(Sender: TObject; NewServerKey: TScKey; var Accept: Boolean);
procedure btConnectClick(Sender: TObject);
procedure btSendClick(Sender: TObject);
procedure btDisconnectClick(Sender: TObject);
procedure ScSSHChannel1Error(Sender: TObject; E: Exception);
procedure FormDestroy(Sender: TObject);
private
MyScSSHClient: TScSSHClient;
MyScFileStorage: TScFileStorage;
MyScSSHChannel: TScSSHChannel;
MyIdHTTP: TIdHttp;
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}
{$R *.iPad.fmx IOS}
procedure TForm1.btConnectClick(Sender: TObject);
begin
try
MyScSSHClient.Connect;
except
on E:Exception do
Label1.Text := 'ScSSHClient.Connect: ' + E.Message;
end;
try
if MyScSSHClient.Connected then
MyScSSHChannel.Connect;
if MyScSSHChannel.Connected then
Label1.Text := 'Channel connected'
else
Label1.Text := 'Channel NOT connected';
except
on E:Exception do
Label1.Text := 'ScSSHChannel.Connect: ' + E.Message;
end;
end;
procedure TForm1.btDelKeysClick(Sender: TObject);
begin
MyScFileStorage.DeleteStorage;
if MyScFileStorage.Keys.Count = 0 then
Label1.Text := 'Key Storage cleared'
else
Label1.Text := 'Key Storage NOT cleared'
end;
procedure TForm1.btDisconnectClick(Sender: TObject);
begin
MyScSSHChannel.Disconnect;
MyScSSHClient.Disconnect;
Label1.Text := 'Disconnected';
end;
procedure TForm1.btSendClick(Sender: TObject);
begin
if MyScSSHChannel.Connected then
begin
// Get-Request to Servers TIdHttpServer, Server answers "'It is ' + TimeToStr(Now)"
Label1.Text := MyIdHTTP.Get('http://127.0.0.1:7778/doyouknowwhattimeitis.cgi');
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyScSSHClient := TScSSHClient.Create(nil);
MyScFileStorage := TScFileStorage.Create(nil);
MyScSSHChannel := TScSSHChannel.Create(nil);
MyIdHTTP := TIdHTTP.Create(nil);
MyScFileStorage.Path := TPath.GetDocumentsPath + TPath.DirectorySeparatorChar + 'My_SSH_Client_Test' + TPath.DirectorySeparatorChar;
MyScFileStorage.Password := '1234';
MyScSSHClient.KeyStorage := MyScFileStorage;
MyScSSHClient.HostKeyName := SERVERKEYNAME;
MyScSSHClient.OnServerKeyValidate := ScSSHClient1ServerKeyValidate;
MyScSSHClient.HostName := '192.168.1.26';
MyScSSHClient.User := 'Olli';
MyScSSHClient.Password := 'Olli';
MyScSSHChannel.Client := MyScSSHClient;
MyScSSHChannel.DestHost := '192.168.1.26';
MyScSSHChannel.DestPort := 7779;
MyScSSHChannel.SourcePort := 7778;
MyScSSHChannel.OnError := ScSSHChannel1Error;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyIdHTTP.Free;
MyScSSHChannel.Free;
MyScSSHClient.Free;
MyScFileStorage.Free;
end;
procedure TForm1.ScSSHChannel1Error(Sender: TObject; E: Exception);
begin
ShowMessage('Channel Error: ' + E.Message);
end;
procedure TForm1.ScSSHClient1ServerKeyValidate(Sender: TObject; NewServerKey: TScKey; var Accept: Boolean);
var
fp: string;
begin
// There is no matching server key in the KeyStorage, so we end here.
NewServerKey.GetFingerprint(TScHashAlgorithm.haSHA2_256, fp);
Label1.Text := 'NewServerKey Fingerprint: ' + fp;
// Only for Test: Compare the first 8 Chars of the TEdit with the Server-Key-Fingerprint.
Accept := EditExpectedFingerprint.Text.Substring(0,
= fp.Substring(0,
;
// if Key is unknown but accepted, save it in KeyStorage.
if Accept and (MyScSSHClient.KeyStorage.Keys.FindKey(SERVERKEYNAME) = nil) then
begin
NewServerKey.KeyName := SERVERKEYNAME;
MyScSSHClient.KeyStorage.Keys.Add(NewServerKey);
end;
end;
end.
i'm using the Trial-Version of SecureBridge.
Here is the short version of my problem: After SSH Disconnect i can't connect the Channel again when running on Android -> 'Cannot bind to address 'localhost'. Address already in use. Socket Error Code: 98($62)'.
The long version:
I wrote a very straight-forward and simple demo consisting of two projects:
A Server-Project (VCL), running under Windows 10, with a TScSSHServer, a TScFileStorage and a TIdHTTPServer.
A Client-Project (FMX) with a TScSSHClient, a TScFileStorage, a TScChannel and a TIdHTTP.
The Server creates its components and waits for an incoming GET-Request, answering with a string.
The Client has a 'Connect'-Button to connect to the server (ScSSHClient.Connect) and establish the Tunnel (ScSSHChannel.Connect).
The Client has a 'Send'-Button to send the Get-Request.
The Client has a 'Disconnect'-Button to disconnect the ScSSHChannel, then disconnect the ScSSHClient.
On Win32 and iOs, everything works fine: The Client connects, sends it's request, gets an answer, disconnects. I can repeat this as often as i want.
On Android, everything works fine for the first connection. If i try to connect again after disconnect, sometimes it works (10%), but sometimes (90%) i got an exception: "Im Projekt ProjectClientFMX.apk ist eine Exception der Klasse std::ostream mit der Meldung 'Cannot bind to address 'localhost'. Address already in use. Socket Error Code: 98($62)' aufgetreten."
PS: Under Android, sometimes i get an Exception when trying to connect the first time: "Error on data reading from the connection: Interrupted system call. Socket Error Code: 4($4)". But this is not reproducable. Maybe this is a side effect of the "You are using SecureBridge Trial edition!"-Message?
Am I doing something wrong or is it a bug?
Here is the Server-Source (VCL-Form with one TButton and one TEdit):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
Vcl.Dialogs, ScBridge, Vcl.StdCtrls, System.IOUtils,
IdContext, IdCustomHTTPServer, IdBaseComponent, IdComponent, IdCustomTCPServer, IdHTTPServer,
ScSSHServer, ScSSHChannel, ScSSHUtils, ScUtils;
const
SERVERKEYNAME = 'serverkey';
type
TForm1 = class(TForm)
Label1: TLabel;
edFingerprint: TEdit;
btGenerateHostKey: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure ScSSHServerChannelError(Sender: TObject; ChannelInfo: TScSSHChannelInfo; E: Exception);
procedure ScSSHServerError(Sender: TObject; E: Exception);
procedure btGenerateHostKeyClick(Sender: TObject);
private
MyScSSHServer: TScSSHServer;
MyScFileStorage: TScFileStorage;
MyIdHTTPServer: TIdHTTPServer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btGenerateHostKeyClick(Sender: TObject);
var
fp: string;
User: TScUser;
Key: TScKey;
begin
// eigenen Key löschen und neu erzeugen.
MyScSSHServer.Storage.DeleteStorage;
User := TScUser.Create(MyScSSHServer.Storage.Users);
User.UserName := 'Olli';
User.Password := 'Olli';
User.Authentications := [uaPassword];
try
MyScSSHServer.Storage.Keys.CheckKeyName(SERVERKEYNAME);
except
on E:Exception do
ShowMessage(Format('Key with this name already exists: %s', [E.Message]));
end;
Key := TscKey.Create(MyScSSHServer.Storage.Keys);
Key.KeyName := SERVERKEYNAME;
Key.Generate(aaRSA, 1024);
Key.GetFingerprint(haSHA2_256, fp);
ShowMessage(Format('Fine, Key added. Fingerprint = %s', [fp]));
edFingerprint.Text := fp;
//Key.ExportTo('d:\publickey.pub', TRUE, '', saTripleDES_cbc, kfIETF, 'erzeugt am '+DateToStr(Now) + ' um ' + TimeToStr(Now));
MyScSSHServer.Active := TRUE;
MyIdHTTPServer.Active := TRUE;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
fp: String;
begin
MyScSSHServer := TScSSHServer.Create(nil);
MyScFileStorage := TScFileStorage.Create(nil);
MyIdHTTPServer := TIdHTTPServer.Create(nil);
MyScSSHServer.KeyNameRSA := SERVERKEYNAME;
MyScSSHServer.OnError := ScSSHServerError;
MyScSSHServer.OnChannelError := ScSSHServerChannelError;
MyScSSHServer.Storage := MyScFileStorage;
MyScFileStorage.Path := TPath.GetDocumentsPath + TPath.DirectorySeparatorChar + 'My_SSH_Server_Test' + TPath.DirectorySeparatorChar;
MyScFileStorage.Algorithm := saAES256_cbc;
MyScFileStorage.Password := '12345678';
MyIdHTTPServer.DefaultPort := 7779;
MyIdHTTPServer.OnCommandGet := IdHTTPServerCommandGet;
MyScSSHServer.Active := TRUE;
MyIdHTTPServer.Active := TRUE;
MyScSSHServer.Storage.Keys.KeyByName(SERVERKEYNAME).GetFingerprint(TScHashAlgorithm.haSHA2_256, fp);
edFingerprint.Text := fp;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyIdHTTPServer.Active := FALSE;
MyScSSHServer.Active := FALSE;
MyScSSHServer.Free;
MyScFileStorage.Free;
MyIdHTTPServer.Free;
end;
procedure TForm1.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentText := 'It is ' + TimeToStr(Now);
end;
procedure TForm1.ScSSHServerChannelError(Sender: TObject; ChannelInfo: TScSSHChannelInfo; E: Exception);
begin
ShowMessage('SSH Server Channel Error, Exception: ' + E.Message);
end;
procedure TForm1.ScSSHServerError(Sender: TObject; E: Exception);
begin
ShowMessage('SSH Server Error, Exception: ' + E.Message);
end;
end.
Here is the Client-Source (FMX-Form with one TLabel, four TButton and one TEdit):
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.IOUtils,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Edit, FMX.Controls.Presentation,
IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,
ScBridge, ScSSHClient, ScSSHChannel, ScUtils;
const
SERVERKEYNAME = 'serverkey';
type
TForm1 = class(TForm)
btDelKeys: TButton;
btConnect: TButton;
btSend: TButton;
btDisconnect: TButton;
EditExpectedFingerprint: TEdit;
Label1: TLabel;
procedure btDelKeysClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ScSSHClient1ServerKeyValidate(Sender: TObject; NewServerKey: TScKey; var Accept: Boolean);
procedure btConnectClick(Sender: TObject);
procedure btSendClick(Sender: TObject);
procedure btDisconnectClick(Sender: TObject);
procedure ScSSHChannel1Error(Sender: TObject; E: Exception);
procedure FormDestroy(Sender: TObject);
private
MyScSSHClient: TScSSHClient;
MyScFileStorage: TScFileStorage;
MyScSSHChannel: TScSSHChannel;
MyIdHTTP: TIdHttp;
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{$R *.LgXhdpiPh.fmx ANDROID}
{$R *.iPad.fmx IOS}
procedure TForm1.btConnectClick(Sender: TObject);
begin
try
MyScSSHClient.Connect;
except
on E:Exception do
Label1.Text := 'ScSSHClient.Connect: ' + E.Message;
end;
try
if MyScSSHClient.Connected then
MyScSSHChannel.Connect;
if MyScSSHChannel.Connected then
Label1.Text := 'Channel connected'
else
Label1.Text := 'Channel NOT connected';
except
on E:Exception do
Label1.Text := 'ScSSHChannel.Connect: ' + E.Message;
end;
end;
procedure TForm1.btDelKeysClick(Sender: TObject);
begin
MyScFileStorage.DeleteStorage;
if MyScFileStorage.Keys.Count = 0 then
Label1.Text := 'Key Storage cleared'
else
Label1.Text := 'Key Storage NOT cleared'
end;
procedure TForm1.btDisconnectClick(Sender: TObject);
begin
MyScSSHChannel.Disconnect;
MyScSSHClient.Disconnect;
Label1.Text := 'Disconnected';
end;
procedure TForm1.btSendClick(Sender: TObject);
begin
if MyScSSHChannel.Connected then
begin
// Get-Request to Servers TIdHttpServer, Server answers "'It is ' + TimeToStr(Now)"
Label1.Text := MyIdHTTP.Get('http://127.0.0.1:7778/doyouknowwhattimeitis.cgi');
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyScSSHClient := TScSSHClient.Create(nil);
MyScFileStorage := TScFileStorage.Create(nil);
MyScSSHChannel := TScSSHChannel.Create(nil);
MyIdHTTP := TIdHTTP.Create(nil);
MyScFileStorage.Path := TPath.GetDocumentsPath + TPath.DirectorySeparatorChar + 'My_SSH_Client_Test' + TPath.DirectorySeparatorChar;
MyScFileStorage.Password := '1234';
MyScSSHClient.KeyStorage := MyScFileStorage;
MyScSSHClient.HostKeyName := SERVERKEYNAME;
MyScSSHClient.OnServerKeyValidate := ScSSHClient1ServerKeyValidate;
MyScSSHClient.HostName := '192.168.1.26';
MyScSSHClient.User := 'Olli';
MyScSSHClient.Password := 'Olli';
MyScSSHChannel.Client := MyScSSHClient;
MyScSSHChannel.DestHost := '192.168.1.26';
MyScSSHChannel.DestPort := 7779;
MyScSSHChannel.SourcePort := 7778;
MyScSSHChannel.OnError := ScSSHChannel1Error;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyIdHTTP.Free;
MyScSSHChannel.Free;
MyScSSHClient.Free;
MyScFileStorage.Free;
end;
procedure TForm1.ScSSHChannel1Error(Sender: TObject; E: Exception);
begin
ShowMessage('Channel Error: ' + E.Message);
end;
procedure TForm1.ScSSHClient1ServerKeyValidate(Sender: TObject; NewServerKey: TScKey; var Accept: Boolean);
var
fp: string;
begin
// There is no matching server key in the KeyStorage, so we end here.
NewServerKey.GetFingerprint(TScHashAlgorithm.haSHA2_256, fp);
Label1.Text := 'NewServerKey Fingerprint: ' + fp;
// Only for Test: Compare the first 8 Chars of the TEdit with the Server-Key-Fingerprint.
Accept := EditExpectedFingerprint.Text.Substring(0,


// if Key is unknown but accepted, save it in KeyStorage.
if Accept and (MyScSSHClient.KeyStorage.Keys.FindKey(SERVERKEYNAME) = nil) then
begin
NewServerKey.KeyName := SERVERKEYNAME;
MyScSSHClient.KeyStorage.Keys.Add(NewServerKey);
end;
end;
end.