I have a C# application that needs to run certain commands on a piece of hardware over SSH. The application is using SSH.Net to make the connection, send the command, and the read the result. I have this working if I connect to my local machine using OpenSSH. Finally, I wanted to go a step further and setup my own SSH server so I could simulate multiple hardware devices at one time (need to simulate having 50+ devices to SSH into).
For this I have setup a simple SSH server using nodejs and the ssh2 package. So far I have the client connected, authenticated (all connections are accepted for now), and I can see a session object being created. Although where I'm hitting a wall is with the execution of commands sent by the client. I noticed that ssh2 has an event for exec on the session object but this never seems to trigger (regardless of what i put in SSH.Net's ShellStream).
The C# client code that initiates the connection is the following (command is already defined that the command string to be executed):
using(SshClient client = new SshClient(hostname, port, username, password))
{
try
{
client.ErrorOccurred += Client_ErrorOccurred;
client.Connect();
ShellStream shellStream = client.CreateShellStream("xterm", Columns, Rows, Width, Height, BufferSize, terminalModes);
var initialPrompt = await ReadDataAsync(shellStream);
// The command I write to the stream will get executed on OpenSSH
// but not on the nodejs SSH server
shellStream.WriteLine(command);
var output = await ReadDataAsync(shellStream);
var results = $"Command: {command} \nResult: {output}";
client.Disconnect();
Console.WriteLine($"Prompt: {initialPrompt} \n{results}\n");
}
catch (Exception ex)
{
Console.WriteLine($"Exception during SSH connection: {ex.ToString()}");
}
}
The nodejs server code that set ups the ssh2 server is following:
new ssh2.Server({
hostKeys: [fs.readFileSync('host.key')]
}, function(client) {
console.log('Client connected!');
client.on('authentication', function(ctx) {
ctx.accept();
}).on('ready', function() {
console.log('Client authenticated!');
client.on('session', function(accept, reject) {
var session = accept();
// Code gets here but never triggers the exec
session.once('exec', function(accept, reject, info) {
console.log('Client wants to execute: ' + inspect(info.command));
var stream = accept();
stream.write('returned result\n');
stream.exit(0);
stream.end();
});
});
}).on('end', function() {
console.log('Client disconnected');
});
}).listen(port, '127.0.0.1', function() {
console.log('Listening on port ' + this.address().port);
});
I have seen various ssh2 client examples invoking a client.exec function but I was assuming that it did not matter that my client was not using the ssh2 node package. Is there something that I'm missing here?
The "exec" Node.js server session event is for "non-interactive (exec) command execution". By which they most likely mean SSH "exec" channel (which is intended for "non-interactive command execution").
To execute a command using "exec" SSH channel in SSH.NET, use SshClient.RunCommand.
On the contrary SshClient.CreateShellStream uses SSH "shell" channel, which is intended for implementing an interactive shell session.
For that, you need to handle "shell" Node.js server session event.
Related
Once again, the Microsoft documentation leaves me wanting. I am trying to find the correct API where I can configure a callback to trap when a client closes their connection.
When I fire up a gRPC server, in the console I see all of the Kestrel configuration and startup log messages. When I fire up the gRPC client I can see in the server's console log messages indicating a connection has been made as follows:
dbug: Microsoft.AspNetCore.Server.Kestrel.Connections[39]
Connection id "0HMCEE5LHGSKR" accepted.
dbug: Microsoft.AspNetCore.Server.Kestrel.Connections[1]
Connection id "0HMCEE5LHGSKR" started.
When I close the client by clicking the Close Window button (X), I see the following:
dbug: Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets[19]
Connection id "0HMCEE5LHGSKR" reset.
dbug: Microsoft.AspNetCore.Server.Kestrel.Http2[48]
Connection id "0HMCEE5LHGSKR" is closed. The last processed stream ID was 1.
dbug: Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets[7]
Connection id "0HMCEE5LHGSKR" sending FIN because: "The client closed the connection."
dbug: Microsoft.AspNetCore.Server.Kestrel.Connections[2]
Connection id "0HMCEE5LHGSKR" stopped.
The option to use the ListenOptions.UseConnectionLogging(ListenOptions) extension method provides no callback option that I can find. Obviously, in the default middleware, the event is being captured, but I cannot find the path to that option. An examination of Microsoft.AspNetCore.Server.Kestrel namespace shows no way (that I can find) how to get to Microsoft.AspNetCore.Server.Kestrel.Connections or Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets data.
I am using Visual Studio 2022, .NET 6, C# 10 and gRPC. Here is my current Kestrel configuration:
// Configure Kestrel, the .NET Core web server.
var hostBuilder = webHostBuilder.ConfigureKestrel (kestrelServerOptions => {
kestrelServerOptions.ConfigureHttpsDefaults (httpsConnectionAdapterOptions => httpsConnectionAdapterOptions.SslProtocols = SslProtocols.Tls12);
// Read in the X.509 certificate file.
var certPath = Path.Combine (builder.Environment.ContentRootPath, "Certs", $"{environment}.pfx");
kestrelServerOptions.ConfigureEndpointDefaults (listenOptions => {
_ = listenOptions.UseHttps (certPath, password);
logger.Debug ($"Using {certPath} as the cert file.");
logger.Debug ("Configuring host to use HTTP/2 protocol.");
listenOptions.Protocols = HttpProtocols.Http2;
});
logger.Debug ("Reading config values for the server name and port.");
// Get the host name and port number to bind the service to.
var port = builder.Configuration.GetValue<int> ("AppSettings:OperationsServerPort");
var address = IPAddress.Parse ("0.0.0.0");
if (address != null) {
logger.Debug ($"Host will listen at https://{address}:{port}");
kestrelServerOptions.Listen (address, port);
} else {
logger.Error ("DNS address for service host cannot be determined! Exiting...");
Environment.Exit (-1);
}
});
Any clues, guidance, examples will be greatly appreciated!
Well, I may be late to the game in understanding all of this ASP.NET Core configuration, but to trap connections coming and going is tooooooo simple... Adding a middleware delegate to the listener is all it took...
var ipEndpoint = new IPEndPoint (address, port);
kestrelServerOptions.Listen (ipEndpoint, action => action.Use (async (context, next) => {
Console.WriteLine ($"INTERNAL ---------------- New Connection: {context.ConnectionId}");
await next.Invoke ();
Console.WriteLine ($"INTERNAL ---------------- Connection terminated: {context.ConnectionId}");
}));
This snippet modified my original post above by adding the middleware delegate. The required reading that got me beyond the logjam can be found here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0
I hope this helps somebody!!
Trying to use SSH.NET to connect to the Cisco wireless controller and add a mac address to it. When I debug my code I am getting a true on the IsConnected property of the client, but when I log onto the controller itself and do a "Show loginsession", I am not seeing the supposed connection being listed.
Also, when I get to var result = client.RunCommand(comman.ToString()).Result; the code just hangs with no response at all. Have left for several minutes and does not timeout or return any sort of error.
Picture of PuTTY session when executing above line
It does not matter what we use for "login as".
public WebAPIEndPoint Put(WebAPIEndPoint console)
{
var auth = new PasswordAuthenticationMethod("UserName", "Password");
var connectionInfo = new ConnectionInfo(hostIP, 22, "UserName", auth);
using (var client = new SshClient(connectionInfo))
{
client.Connect();
var command =
client.CreateCommand("config macfilter add 00:17:ab:ea:d4:aa 5 0 Testing1234");
var result = client.RunCommand(comman.ToString()).Result;
Console.WriteLine(result);
client.Disconnect();
return console;
}
}
When running plink user#host config macfilter add 00:17:ab:ea:d4:aa 5 0 Testing1234, I get:
FATAL ERROR: Server refused to start a shell/command.
Your server does not seem to support SSH "exec" channel. So you cannot use CreateCommand/RunCommand.
You have to use a "shell" channel (SshClient.CreateShell or SshClient.CreateShellStream). This is normally not recommended for a command automation. Even more so with SSH.NET, which does not even allow your to turn off a pseudo terminal for the "shell" channel. This brings lots of nasty side effects, particularly with "smart" shells on Linux. But with devices likes yours, it might be bearable and the only solution anyway.
ShellStream shellStream = client.CreateShellStream(string.Empty, 0, 0, 0, 0, 0);
shellStream.Write("username\n");
shellStream.Write("password\n");
shellStream.Write("config macfilter add 00:17:ab:ea:d4:aa 5 0 Testing1234\n");
i can connect to my socketio server, but how can i send a requst with params?
My socketio server listen event
socket.on('init', ( data ) => {
console.log('init', data);
});
on client side i tryed to do this
_socket.OnMessage += (sender, e) =>
{
Console.WriteLine("message: {0} {1}", e.IsBinary, e.Data);
_socket.Send(MakePacket("init", new Init
{
key = "1",
room = "eq",
type = "s"
}.ToJson())
);
};
private string MakePacket(string e, string data)
{
return new[] {e, data}.ToJson();
}
so i send json to server
["init", {"type":"s","key":"1","room":"eq"}]
But server wont react at this packet. Server working fine, i have problem only with call this event at C#. What i do wrong?
The problem is that socket.io is not plain websocket, but a custom protocol on top of websocket (or also on top of HTTP long polling as a fallback and on initialization). That means to speak with a socket.io server you have to encode your data and messages just like socket.io would do it. Here seems to be some documentation about how it works.
Alternatives:
Use a socket.io library on client side - but I don't know if one exists
Work with raw websockets on server side
C# has socketio library but i have some issues that i can't find answers at and there are no support at all. So i switched to websocket-sharp.
Afer some reseach of debug info from socketio server i found the answer. If i send this string all works fine
42["init", {"type":"s","key":"1","room":"eq"}]
Just add 42 before json and all will work fine. Magic.
I think this 42 number is like your current connect status or something like this. Because when you just connect to socketio server it's send string with 0 before json.
I want to do black-box testing of a messaging client library that uses the WindowsAzure.ServiceBus nuget package nuget package (docs) to send and receive messages to/from Microsoft ServiceBus queues.
My goal is to create integration tests for the messaging client library, but not communicate with an actual Microsoft Service Bus server.
Looking at the amqplite library, I was hoping that I could create an AMQP host using amqplite, and configure the messaging client library to communicate with this host, instead of an actual Microsoft Service Bus.
Here is my custom AMQP host, using amqplite:
public class LinkProcessor : ILinkProcessor
{
public void Process(AttachContext attachContext)
{
Console.WriteLine("LinkProcessor.Attach!");
}
}
var containerHost = new ContainerHost(new Uri("amqp://localhost:9876/"));
containerHost.RegisterLinkProcessor(new LinkProcessor());
containerHost.Open();
I register a ILinkProcessor on the containerHost so I can see on the console any connection attempts (based on my limited understanding of AMQP/AMQPlite this is what I believe a ILinkProcessor does).
The AMQP client below can connect, the server process outputs "LinkProcessor.Attach!" on the console.
async Task Main()
{
await SendMessage("q2", "hello world");
}
async Task SendMessage(string queue, string message)
{
var connection = await Connection.Factory.CreateAsync(new Address("amqp://localhost:9876/"));
var session = new Session(connection);
var msg = new Message(message);
var sender = new SenderLink(session, "sender-link", queue);
await sender.SendAsync(msg);
await sender.CloseAsync();
await session.CloseAsync();
await connection.CloseAsync();
}
Back to the messaging client library that I want to test:
The library is configured using a connection string, a working connection string looks like this (I assume the connection string format is one defined by WindowsAzure.ServiceBus):
Endpoint=sb://xxx/yyy;StsEndpoint=https://nnn:NNNN/xxx;RuntimePort=NNNN;ManagementPort=NNNN;OAuthUsername=xxx;OAuthPassword=yyy
I changed the config string to the following:
Endpoint=amqp://localhost:9876/q2
When I run the test client, the following happens:
The client throws an exception: You have tried to create a channel to a service that does not support .Net Framing.
I see no activity on the AMQPlite host end, i.e. no "LinkProcessor.Attach!" message on the console.
Question:
Is what I'm trying to accomplish possible? I.e. is there a way to configure a AMQPlite host so that it can accept connections from a WindowsAzure.ServiceBus client ?
i just don't understand why simple socket.io part don't work.
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
http.listen(3000, function(){
console.log('listening on *:3000');
});
app.use(function(req, res, next) {
console.log("INIT");
console.log(req.headers['user-agent']);
console.log(req.ip);
next();
});
app.use('/', routes);
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
This is my cliend side code at C#. So when my nodejs server is online i don't get any errors from C#, so it's connecting, but i don't see it at node console.
And this must work, i get this example here - https://www.youtube.com/watch?v=nwV3MS6pryY
using System;
using System.Net.Sockets;
namespace TCPSocketConsole
{
class Program
{
static TcpClient mySocket = new TcpClient();
static void Main(string[] args)
{
mySocket.Connect("127.0.0.1", 3000);
Console.ReadLine();
}
}
}
So when i connect to http://localhost:3000 i don't get "a user connected" at my console.
You are listening for a socket.io connection on your server, but your client is just make a plain TCP connection. The two protocols on each end must be the same. socket.io is not a plain TCP connection.
You can either listen for a plain TCP connection on your node.js server (and thus invent your own protocol) or you can get a class for a socket.io connection in your C# client so your C# client can actually speak to a socket.io server properly.
socket.io is based on webSocket and webSocket has a whole protocol for establishing the initial connection (it starts with an HTTP connection that is then upgraded to a webSocket connection) and then both webSocket and socket.io have their own framing for how data is sent. Both ends of the connection must speak the same protocol.
In the socket.io docs you have an example of the client and server side. It seems your are not connecting from client side.