Does NetworkStream with StreamWriter behave differently on Linux? - c#

The following console app works fine on Windows but when I run it in WSL or as a Linux Docker container I get a timeout from the server.
using System;
using System.IO;
using System.Net.Sockets;
namespace ClientTest
{
class Program
{
static void Main(string[] args)
{
const string server = "inbound-smtp.us-west-2.amazonaws.com";
const int port = 25;
Console.WriteLine($"Connecting to: {server}");
using (var client = new TcpClient(server, port))
{
using (NetworkStream stream = client.GetStream())
using (StreamReader clearTextReader = new StreamReader(stream))
using (StreamWriter clearTextWriter = new StreamWriter(stream) { AutoFlush = true })
{
// read connection response
var connectResponse = clearTextReader.ReadLine();
Console.WriteLine(connectResponse);
// send command
Console.WriteLine($"HELO domain.com");
clearTextWriter.WriteLine("HELO domain.com");
// read command response
var commandResponse = clearTextReader.ReadLine();
Console.WriteLine(commandResponse);
}
}
}
}
}
Output on Windows with 250 success...
Output on Linux with 451 Timeout...
However, If I change the server address to ASPMX.L.GOOGLE.com for example then the app works as expected on Windows and Linux.
The console app is targeting .net5.0 but have also tried it with .net3.1 and got the same results. I have also tried running it on a Linux VM with Docker in the cloud but also got the same results.
Are there differences with StreamWriter/NetworkStream on Linux (compared to Windows) and more specifically why do I not get the same issue when using the Google server?

I wonder... is this as simple as a line-ending delta? Windows and Linux have different Environment.NewLine values. Perhaps try adding \r\n explicitly (since Windows seems to work), rather than relying on the local OS line-ending?
clearTextWriter.Write("HELO domain.com\r\n");
Also: try adding an explicit flush - clearTextWriter.Flush();.
As for why the Google server works: perhaps it is more forgiving in what it accepts, where-as the first server is following a specification more precisely, and that specification presumably says that messages are terminated by \r\n. Or the specification may be ambiguous, and Google just chose to be permissive.
BTW: for similar reasons, it would be a good idea to explicitly specify an Encoding here, presumably UTF8.

Related

Why is reading from IBM MQ queue extremely slow using .NET standard library? (unlike .NET)

I wrote a console app on .NET, to read without consuming the messages from an IBM MQ queue.
Worked perfect.
Now, I need to migrate that app into .NET Core. Can't figure out why it is extremely slow.
How it works:
target framework .NET Core 3.1
IBMMQDotNetClient NuGet package installed
created a helper class, static, with a static constructor where I initialise MQEnvironment properties like so:
MQEnvironment.CertificateLabel = "ibmwebsphere"; // this is the friendlyname on mmc certificate
MQEnvironment.SSLKeyRepository = "*SYSTEM";
added a method called Init where I initialise connection to MQManager like so:
Hashtable properties = new Hashtable();
properties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED);
properties.Add(MQC.HOST_NAME_PROPERTY, hostName); // I read the hostName from a config file
properties.Add(MQC.PORT_PROPERTY, port); // I read the port from a config file
properties.Add(MQC.CHANNEL_PROPERTY, channelName); // I read the channel from a config file
properties.Add(MQC.SSL_CIPHER_SPEC_PROPERTY, cipherSpec); // I read the cipher spec from a config file, it's something like TLS_RSA_WITH_AES_256_CBC_SHA256
Then, I'm going to create a connection to the queue manager using the connection, and read messages one by one until coming to end of the queue.
var queueManager = new MQQueueManager(qm, properties); // I read the qm from a config file
var queue = queueManager.AccessQueue(queueName, MQC.MQOO_BROWSE + MQC.MQOO_FAIL_IF_QUIESCING); // I read the queueName from a config file
var mqGMO = new MQGetMessageOptions();
mqGMO.Options = MQC.MQGMO_FAIL_IF_QUIESCING + MQC.MQGMO_NO_WAIT + MQC.MQGMO_BROWSE_NEXT; mqGMO.MatchOptions = MQC.MQMO_NONE;
try {
while (true) {
MQMessage queueMessage = new MQMessage();
queue.Get(queueMessage, mqGMO); // code gets apparently stuck on this line,
// overprocessing, for many minutes until it gets to the next line,
// even though I mentioned "NO_WAIT" in the options.
// Note this only happens for .NET Core, but not in .NET framework.
var message = queueMessage.ReadString(queueMessage.MessageLength);
string fileName = message.Substring(0,3); // some processing here to extract some info from each message
}
}
catch(MQException ex)
{
if(err.ReasonCode.CompareTo(MQC.MQRC_NO_MSG_AVAILABLE) == 0)
{
// harmless exception to indicate there are no messages on the queue
}
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
Of course it would be preferable to use a listener, not sure how to do that yet, it would be part of optimising, but for now - why is working so slow on line:
queue.Get(queueMessage, mqGMO); // but, again, as mentioned, only with the amqmdnetstd.dll (.NET Core), because if I use amqmdnet.dll (.NET framework), it works super fast, and it's supposed to be the other way around.
I do need to use .NET Standard/Core because I will run this in Linux, currently testing in Windows.
Don't use the MQEnvironment class as it is not threaded safe. Also, don't mix and match between MQEnvironment class and using MQ HashTable. Put your SSL/TLS information as a property in the MQ HashTable.
i.e.
properties.Add(MQC.SSL_PEER_NAME_PROPERTY, "ibmwebsphere");
properties.Add(MQC.SSL_CERT_STORE_PROPERTY, "*SYSTEM");
There isn't enough of your code to test to see why it might be failing.
I wrote and posted a blog item called: IBM MQ For .NET Core Primer. In the blog post, I included a fully functioning C# MQ example (MQTest62L.cs) that was built and run using .NET Core v3.1 and everything worked perfectly (see very bottom of post). Hence, I would suggest you follow my instructions, download, compile and run MQTest62L.cs to see if the issue is your code or MQ Client library.
Note: I was using Windows 10 Pro, IBM MQ v9.2.0.0 and .NET Core v3.1.415.

Could not create SSL/TLS secure channel "http://www.rtvs.sk/televizia/archiv"

I am very new to ssl/tls, so please be patient. The code works for me few months for another sites (iprima.cz for example), but dont working for rtvs.sk.
it writes: Could not create SSL/TLS secure channel.
I have tried everything, but nothing works. Please help me.
string url1 = "https://www.rtvs.sk/televizia/archiv";
ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00);
ServicePointManager.Expect100Continue = true;
ServicePointManager.DefaultConnectionLimit = 9999;
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) => true;
//It is because I have tried enerything
string rets = "";
using (WebClient wc = new WebClient())
{
wc.Proxy = null;
int i = 0;
zatry:
try
{
Stream stream = wc.OpenRead(url1);
Encoding kod = Encoding.UTF8;
StreamReader sr = new StreamReader(stream, kod);
rets = sr.ReadToEnd();
sr.Close();
}
It seems to me to be site-specific, it works on Opera and other browsers, but not on my code.
It maybe a simple thing, but after reviewing all solutions for 5 hours and nothing works, I need a help. Thank you very much
Microsoft .NET Framework 4.7.03062SP1Rel
It has the same error on Microsoft Visual Studio 2010 and 2019 version 16.4.3
Introduction
It seems like the https://www.rtvs.sk/ server uses a not standard way of communication, that is not supported in .NET Framework libraries.
The server redirects any HTTP connection to HTTPS connection and this can not be avoided.
To bypass the "Could not create SSL/TLS secure channel" error you need to use a different HTTPS client. And I do not mean use the WebRequest.Create instead of the WebClient. The error is still here as this was the code I used in my application. A communication error is probably somewhere deeper in system since even Internet Explorer does not open the page (at least not in Windows 7).
There are a few possible options of an alternate client:
Write your own client from scratch - I cannot recommend this solution, it is hard, prone to errors...
Buy an existing alternative client - I found only one alternative HTTPS client for C# and the price was high. Too much for my simple personal application.
Use an alternative external client - e.g. wget, or an other downloader (custom and/or existing).
Try to use the application from Linux (or Mono framework). I did not test this solution but it is (at least theoretically) possible that the problem can be solved this way.
My Solution
After a simple test I found out, that there is no problem when downloading using Python. So I made a simple Python downloader and use it from my C# application.
The code in C# is:
private const string PythonSciptPath = "PythonDownloader.py";
private static bool UseStandardDownloader(string address) =>
!address.StartsWith("https://www.rtvs.sk/", StringComparison.Ordinal);
private static byte[] GetData(string address)
{
var start = new ProcessStartInfo
{
FileName = "python.exe",
Arguments = PythonSciptPath + " \"" + address + "\"",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
string result;
using (Process process = Process.Start(start))
using (StreamReader reader = process.StandardOutput)
{
result = reader.ReadToEnd();
}
return Convert.FromBase64String(result);
}
The code in Python (the "PythonDownloader.py" file) is:
import base64
import sys
import urllib.request
opener = urllib.request.URLopener()
stream = opener.open(sys.argv[1])
while True:
contentBytes = stream.read(60)
if len(contentBytes) == 0:
break
contentbase64Bytes = base64.b64encode(contentBytes)
contentString = contentbase64Bytes.decode("UTF-8")
print(contentString)
A simple way how to work with this is to add the file to project and select "Copy to output directory" option to "Copy if newer".
The current simple solution the code does not handle any error. I leave that part to you.
I also tried to use IronPython, but the simple client I wrote did not work because of a missing library. And it is possible that it will not work because it uses the .NET Framework libraries as interpreter.

.NET Core 2.2 HttpClient/WebClient vs Curl - .NET libs very slow for some servers

EDIT: added reproduction samples + I am running this (on all servers) on Ubuntu 18.04 with .Net Core 2.2.203.
EDIT: tested from home from my Windows 10 laptop; same results
I have a piece of very simple code for HttpClient (static as recommended, but I tested with using() as well):
sw.Start(); // stopwatch
client.GetAsync(url).Result();
sw.Stop();
and then for curl:
time curl -L "url" > /dev/null
and for lynx:
time lynx "url" > /dev/null
The difference is staggering; it really depends on the requested server/url, but i'm getting differences to 2-50x slower from HttpClient than curl/lynx on requests from the same server.
I tried all fixes I could find;
HttpHandler without proxy (UseProxy = false, Proxy = null)
Using await instead of .Result (not that that should make a difference and it indeed does not)
WebClient
ModernHttpClient
and the Curl wrapper CurlThin
That last option (obviously) did give the right results, the rest (.NET options) are just incredibly slow.
For now i'm using the Curl wrappers because the .NET results are just incorrect and slowing our stack down.
Did anyone have this before? I tried (as you can see above) all 'fixes' provided by Googling, but none of them provided any help.
EDIT: from Matthiee in the comments, if you are running Windows with Powershell, this reproduces it too;
(Measure-Command -Expression { $site = Invoke-WebRequest -Uri "reddit.com" }).Milliseconds
EDIT: Code to reproduce:
use with:
dotnet run -- https://reddit.com
using System;
using System.Diagnostics;
using System.Net.Http;
namespace Download.Playground
{
class Program
{
static HttpClient client;
static void Main(string[] args)
{
HttpClientHandler hch = new HttpClientHandler();
hch.Proxy = null;
hch.UseProxy = false;
client = new HttpClient(hch);
Stopwatch sw = new Stopwatch();
sw.Start();
var result = client.GetAsync(args[0]).Result;
sw.Stop();
Console.WriteLine($"Spent {sw.ElapsedMilliseconds}ms");
}
}
}
Little script to check 20 times, run with:
./runbench https://reddit.com
#!/bin/bash
for i in {1..20}
do
dotnet run -- $1
time curl -L $1 > /dev/null
done
The issues was resolved, just a combination of factors that led up to a large portion of the target audience sites to not have their content cached. Nothing to do with HttpClient (Besides it not sending a user-agent).
Read the comments for more information.

How to read bluetooth data from Windows CE to bluetooth Device

I have a Windows Mobile/CE 6 device that needs to read RFID data from a bluetooth RFID wand in a custom .NET 3.5 CF application. Tried many different solutions and trying to make 32Feet's InTheHand bluetooth library work. I can get the device to pair and connect just fine, but when I send commands to the unit and try to read from the stream, it always results in an exception of "Operation time-out". Any tips or help is much appreciated.
var endpoint = new BluetoothEndPoint(device.DeviceAddress, BluetoothService.SerialPort);
var client = new BluetoothClient();
client.Connect(endpoint); // Connects just fine!
var stream = client.GetStream();
using (var sw = new StreamWriter(stream))
{
sw.Write("l");
}
if (stream.CanRead) // Always False
{
using (var sr = new StreamReader(stream))
{
var exi = sr.Peek();
var p = sr.ReadToEnd();
var pp = sr.ReadLine();
}
}
1.
I would first check, if there is any communication possible using the standard tools. Possibly the RFID does not work as you think.
Connect the BT RFID using the BT applet and add a new outgoing Serial Port also in the BT applet. Then use PocketPuTTY or another serial terminal tool to communicate with the RFID module. If that works, you can go on and start using 32feet.
2.
Will the RFID answer on a simple char or does it need a CR/LF on received data?
3.
Test the communication from a PC following 1. usning a Hyperterminal like app.

Using SSH.NET to connect to Remote Hosts

I'm trying to automate configuring remote hosts, we have hundreds of these devices, we normally do it through USB programming, but if I could get a script to connect to these devices and do it programmatically, it would free up time.
These devices run some type of linux os, i'm not sure exactly, but they do have SSH enabled and confirm server host keys when you first connect to them via utility like PuTTY.
For now, i'm just trying to initiate an SSH session with the device. I've done quite a bit of research, and have come up with this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Renci.SshNet;
using Renci.SshNet.Common;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
//Connection information
string user = "admin";
string pass = "********";
string host = "IP Address";
//Set up the SSH connection
using (var client = new SshClient(host, user, pass))
{
//Accept Host key
client.HostKeyReceived += delegate (object sender, HostKeyEventArgs e)
{
e.CanTrust = true;
};
//Start the connection
client.Connect();
var output = client.RunCommand("show device details");
client.Disconnect();
Console.WriteLine(output.ToString());
Console.ReadLine();
}
}
}
}
The problem is this doesn't seem to execute the command listed. The console window comes up, and I can access the same device by WebGUI and see the log file, it shows a connection being made, but when I break the execution and see the variable values the output variable shows null.
If I let the execution sit, with the console window open (just shows a blinking cursor in the upper left), the connection times out after 10 minutes and connection is lost, which I also see happen in the device log.
Why would does this not seem to execute the runcommand and store the results in the output variable?
When you execute the RunCommand() method on an object of type Renci.SshNet.SshClient, it does not return the result as a variable.
Instead, it returns an object of the Renci.SshNet.SshCommand type.
The issue is that, it looks like you can't fit this resultant SshCommand object into a var.
This Renci.SshNet.SshCommand, returned when you execute RunCommand(), will contain several properties and methods.
The properties are:
CommandText
CommandTimeout
ExitStatus
OutputStream
ExtendedOutputStream
Result
Error
They're all useful, but as everything else seems to be working, the only relevant one you want is "Result".
The "Result" property will contain a String, which will be the host stream result of the command you provided to RunCommand().
As you mention the device's logfile has logged a successful connection being made, it looks like the connection is successful. So you'd just have to make the proper tweak to grab the Result, as described above, and you should be good to go.
Addendum:
The following line in the original post's code:
var output = client.RunCommand("show device details");
Should be replaced with this code:
var output = client.RunCommand("show device details").Result;
This will assign the Result property (which is a String) to the output var, which will give the desired outcome.

Categories