I have a distributed system of actors, some on Windows, and some on Linux machine. Sometimes one actor may need to connect other actor and make some communications. Of course, there are cases when one of them is on Windows, and other is on Linux system.
Actors connect each other via ActorSelection. There problem is, that when Windows actor is trying to communicate with Linux one, all works fine. But when Linux actor initiating communication, the ActorSelection.ResolveOne failes.
I've made a little sample here:
static void Main(string[] args)
{
ActorSystem system = ActorSystem.Create("TestSystem");
system.ActorOf(Props.Create(() => new ConnectActor()), "test");
while (true)
{
var address = Console.ReadLine();
if (string.IsNullOrEmpty(address))
{
system.Terminate();
return;
}
var remoteAddress = $"akka.tcp://{system.Name}#{address}/user/test";
try
{
var actor = system.ActorSelection(remoteAddress).ResolveOne(TimeSpan.FromMilliseconds(5000)).Result;
Console.WriteLine("Resolved: " + actor.Path);
}
catch (Exception ex)
{
Console.WriteLine("Failed: " + ex.Message);
}
}
}
Configuration in app.config is the following:
akka {
loggers = ["Akka.Logger.NLog.NLogLogger, Akka.Logger.NLog"]
suppress-json-serializer-warning = on
loglevel = "DEBUG"
log-config-on-start = on
actor {
provider = "Akka.Remote.RemoteActorRefProvider, Akka.Remote"
debug {
receive = on
autoreceive = on
lifecycle = on
event-stream = on
unhandled = on
}
}
remote {
log-remote-lifecycle-events = DEBUG
log-received-messages = on
helios.tcp {
transport-class = "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
transport-protocol = tcp
applied-adapters = []
port = 9000
hostname = "0.0.0.0"
public-hostname = "192.168.0.251" // This is different for different hosts, of course
}
}
}
The public-hostname is publicly available ip address.
So, here are the cases:
When running Windows/Windows, both instances see each other (I give them remote address - they output "Resolved ")
When running Windows/Linux, and give linux actor's address to windows actor, it outputs "Resolved". So windows connects linux with no problem. After that giving windows actor's address to linux actor also gives "Resolved" - I suppose, the connection is already established and there is no real handshakes passing
BUT when running Windiws/Linux and give windows actor's address to linux actor, it gives "Failed". No messages about any errors or dropping packages. At the end of the log there is the following:
Akka.Remote.Transport.AkkaProtocolManager|now supervising akka://TestSystem/system/transports/akkaprotocolmanager.tcp.0/akkaProtocol-tcp%3A%2F%2FTestSystem%40%5B%3A%3Affff%3A192.168.0.252%5D%3A36983-1||||
13:20:08.3766|DEBUGAkka.Remote.Transport.ProtocolStateActor|Started (Akka.Remote.Transport.ProtocolStateActor)||||
13:20:08.3922|DEBUG|Akka.Remote.Transport.ProtocolStateActor|Stopped||||
The issue with similar logs is described here: Akka.net starting and stopping with no activity
The reason there is that system protocols are not compatible. Is this the same issue? As I got from Akka.NET docs and release notes, it has full linux support...
So, am I missing something in configuration? Can anybody make this sample work with Linux -> Windows connection?
The issue here appears to be that Mono is using an IPV6 address mapped to IPV4 in its bound address for some reason.
akka://TestSystem/system/transports/akkaprotocolmanager.tcp.0/akkaProtocol-tcp%3A%2F%2FTestSystem%40%5B%3A%3Affff%3A192.168.0.252%5D%3A36983-1
If you decode this URL that gets translated to
akkaProtocol-tcp://TestSystem#[::ffff:192.168.0.252]:36983-
So I think what is happening here is that outbound address Helios is supposed to parse from that is getting screwed up on the Linux side, so it attempts to connect to a mal-formed address that isn't the same as the one Windows listens on. Something platform-specific in the actor selection URI parsing code is incorrect I suspect.
I've filed a bug here: https://github.com/akkadotnet/akka.net/issues/2254
Related
I try to generate a new device and publish some random data to it via MQTT.
I follow this official example:
https://cumulocity.com/guides/device-sdk/mqtt-examples/#hello-mqtt-cs
All operations are executed without any error. Even establishing connection. But when I try to publish a message to the device I get the following error
"Connecting with MQTT server failed (ConnectionRefusedNotAuthorized)"
Here is my info to connect to the server
const string serverUrl = "mytenant.eu-latest.cumulocity.com";
const string clientId = "d:testdevice4";
const string device_name = "testdevice4";
const string user = "<mytenant>.eu-latest/<myusername>";
const string password = "XXXXXXXX";
And here are the operations that are executed without throwing any exception or ConnectionFailed event:
Establish Connection
await client.EstablishConnectionAsync();
Create Device
string topic = "s/us";
string payload = $"100,{device_name}, c8y_MQTTDevice";
var message = new MqttMessageRequestBuilder()
.WithTopicName(topic)
.WithQoS(QoS.EXACTLY_ONCE)
.WithMessageContent(payload)
.Build();
The other operations on Cumulocity Example
// set device's hardware information
var deviceMessage = new MqttMessageRequestBuilder()
.WithTopicName("s/us")
.WithQoS(QoS.EXACTLY_ONCE)
.WithMessageContent($"110, {device_name}, MQTT test model, Rev0.1")
.Build();
await client.PublishAsync(deviceMessage);
// add restart operation
await client.SubscribeAsync(new MqttMessageRequest() { TopicName = "s/ds" });
await client.SubscribeAsync(new MqttMessageRequest() { TopicName = "s/e" });
await client.PublishAsync(new MqttMessageRequestBuilder()
.WithTopicName("s/us")
.WithQoS(QoS.EXACTLY_ONCE)
.WithMessageContent("114,c8y_Restart")
.Build());
But when I try to publish a message to the device as follows, ConnectionFailed event is invoked with the error:
"Connecting with MQTT server failed (ConnectionRefusedNotAuthorized)"
Random rnd = new Random();
while (!cToken.IsCancellationRequested)
{
int temp = rnd.Next(10, 20);
Console.WriteLine("Sending temperature measurement (" + temp + "º) ...");
var xx = client.ConnectionDetails;
await client.PublishAsync(new MqttMessageRequestBuilder()
.WithTopicName("s/us")
.WithQoS(QoS.EXACTLY_ONCE)
.WithMessageContent("211," + temp)
.Build());
Thread.Sleep(1000);
}
You get a ConnectionRefusedNotAuthorized error because your credentials are not correct. To be precise, the user:
const string user = "<mytenant>.eu-latest/<myusername>";
The user is formed as tenantID/username
Your tenant domain (<mytenant>.eu-latest) is not the tenant ID. Tenant IDs – in most of the cases – are a number preceded by the letter t, e.g. t123123.
so your string should look like:
const string user = "t123123/mytenant";
More details can be found in the public documentation:
Tenant ID and Tenant domain
GET the current tenant details
There could be more reasons than just authorization issues, as per
https://cumulocity.com/guides/device-sdk/mqtt/
which states for return code 5 - "Connection refused, not authorized"
Mostly a device side related problem, used when the device doesn’t
have permissions or is doing something forbidden. For example, if the
client sends malformed messages or tries to execute an operation
without authenticating first, such as publishing a message. Thrown on
any issue with certificate authentication (for example, wrong common
name, failed auto registration). Also thrown on general issues with
receiving device data or some other authorization problem related to
the device state on the platform. For example, device managed object
problems, or the sudden removal of permissions. In this situation it
may be required to take action on the platform to investigate and
apply a fix. When clientId is too long the user can receive this error
when using 3.1 version of MQTT. This can happen if clientId has 24
characters or more. Lastly, it can also be thrown on unexpected
exceptions like performance issues, especially during connection.
Therefore it is a good approach to repeat the connection a few times
to overcome temporary performance issues.
I had the same issue where all my coded attempts (C#, Node.js) were failing with this same error.
In my case I am using a trial Cumulocity environment where the tenant id differs from all the Cumulocity documentation (which says the tenant id starts with a 't' followed by a number of digits. In my trial environment, the Tenant ID is shown in the Cumulocity Cockpit under the user details (top right), and this is in the format "ENVxxxxxx".
To try to troubleshoot the issue I began testing with an online MQTT client http://www.emqx.io/online-mqtt-client and through this process I mistakenly typed the username with lower case "env" and this resolved my issue. Hope that helps others.
I'm using a very basic example for testing my MassTransit connection to rabbitMq through C#. Whenever I run this code to connect to my rabbitMq endpoint, it works fine whenever I have a wildcard set as permission. However, when I add the permissions in the rabbitMq admin to only allow this user to access the test event, this code will fail.
For some reason it will first try to connect to an exchange name that I guess is generated (by MassTransit?):
RabbitMQ.Client.Exceptions.OperationInterruptedException: 'The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=403, text="ACCESS_REFUSED - access to exchange '*ComputerName*_dotnet_bus_73451gfsgerqwrefxfddgf' in vhost '/' refused for user 'user'", classId=40, methodId=10, cause='
So the exchange *ComputerName*_dotnet_bus_73451gfsgerqwrefxfddgf, after that it will try to connect to the test exchange. Of course I can add the ComputerName.... exchange to the permissions but then this would need to be done for each computer trying to run this code. Why is MassTransit trying to connect to this exchange? Is the code incorrect or is this just how MassTransit works?
This is the code for the test application (I altered this a bit so it might not run right of the bat, but, in general the code runs fine):
using System;
using MassTransit;
namespace Test
{
public class Testing
{
public string Id { get; set; }
}
}
namespace Consumer
{
class Program
{
static void Main(string[] args)
{
var bus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri("rabbitmq://servername"), h =>
{
h.Username("user");
h.Password("user");
});
sbc.ReceiveEndpoint(host, "test", ep =>
{
ep.Handler<Testing>(context =>
{
return Console.Out.WriteLineAsync($"Received: {context.Message.Id}");
});
});
});
bus.Start();
// For testing purposes, we send a message ourselves.
bus.Publish(new Testing { Id = "X" });
Console.WriteLine("Waiting for messages. Press any key to exit");
Console.ReadKey();
bus.Stop();
}
}
}
Image of the rabbitMq admin user permission:
Is the code incorrect or is this just how MassTransit works?
More than likely this is just how MassTransit works. See this other question: Prevent MassTransit from creating a RabbitMQ exchange for a consumer host
Strangely enough I could not find this information anywhere in the MassTransit docs.
You'll have to grant the configure permission for exchanges containing the string dotnet_bus. More than likely other permissions like read and write will be required.
NOTE: the RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.
I have two apps on two computers. These apps are communicating by .NET Remoting.
First app acts as server for access to database, second app acts as client and writes that data to another database.
When my client calls some method on my remote object on server, I get following error:
A connection attempt failed because the connected party did not
properly respond after a period of time, or established connection
failed because connected host has failed to respond
192.168.200.60:31621
Well, that's nice, but I don't know anything about IP address 192.168.200.60, I was connecting to 172.XXX.XXX.216. It seems that there are two network connections and it's somehow not good for remoting.
ipcongif on my server look like that:
Exactly the same solution works on another 3 computers with Windows 2000, Windows XP and Windows 7. Server is developed in .NET Framework 2.0.
Client and server have common DLL library with two interfaces ICAOFactory and ICAO. First I create factory CAOFactory, which has method CreateCAO, which returns CAO object. When I call some method oh that ICAO object, it fails.
This is how my server app registers remoting object:
TcpChannel channel = new TcpChannel(31621);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(My_Server.CAOFactory), "My_Server", WellKnownObjectMode.Singleton);
This is how my client creates remote object:
My_Share.ICAOFactory srvFactory;
My_Share.ICAO srvCAO;
srvFactory = (My_Share.ICAOFactory)Activator.GetObject(typeof(Foerster_Share.ICAOFactory), "tcp://" + ip + ":" + port + "/My_Server");
srvCAO = srvFactory.CreateCAO(); // no problem
srvCAO.Init(dateTime); // problem
This is my CAOFactory object:
public class CAOFactory : MarshalByRefObject, ICAOFactory
{
public ICAO CreateCAO()
{
ICAO CAOObj = new CAO();
return CAOObj;
}
public void GetClientCount(out long clientCountSum, out long clientCountMax, out long clientCountActive)
{
clientCountSum = 0;
clientCountMax = 0;
clientCountActive = 0;
return;
}
public override object InitializeLifetimeService()
{
return null;
}
}
This is my CAO object:
public class CAO : MarshalByRefObject, ICAO
{
public void Ping()
{
return;
}
DateTime dtInit;
public void Init(DateTime dt)
{
dtInit = dt;
}
// + some other methods
}
Any help greatly appreciated!
What version of .NET are you targeting?
I think you need to use the bindTo property of the TcpChannel class https://msdn.microsoft.com/en-us/library/bb187434(v=vs.85).aspx to tell your server to bind to the correct NIC. This is probably most easily done in the configuration. Does your server project have an app.config? If not add one then add a section like this to it (this is copy/pasted from this question .Net Remoting: Indicate which local interface to use to connect to one server)
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="0" bindTo="172.xxx.xxx.xxx" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
This will tell the server to bind to the specific IP address.
Reordering network connection priority helped in my case.
http://ecross.mvps.org/howto/change-network-connection-priority-in-windows-10.htm
Press the Windows Key + X and select Network Connections from the menu.
Press the ALT key, click Advanced and then Advanced Settings.
Select the network connection and click the arrows to give priority to the network connection.
Click Ok when you are done organizing the priority of the network connection. The computer will now follow an order when connections are available.
Im playing with using akka.Net to develop a plugin architecture whereby each dll that contains one or more plugins is loaded into its own AppDomain and a new actor system is initialized ready to recieve messages from the "Host".
I become unstuck trying to get this to work with multiple plugins.
So the Host config looks like:
akka {
actor {
provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
}
remote {
helios.tcp {
transport-class = ""Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote""
applied-adapters = []
transport-protocol = tcp
port = 50003
hostname = localhost
}
}
}
And the plugin config looks like:
akka {
actor {
provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
}
remote {
helios.tcp {
transport-class = ""Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote""
applied-adapters = []
transport-protocol = tcp
port = 50004
hostname = localhost
}
}
(there are many of these)
My question is how do i get messages from the Host to all of the plugins?
The best recommendation is to use Akka.Cluster. Here's a well-documented example: https://github.com/petabridge/akkadotnet-code-samples/tree/master/Cluster.WebCrawler
Edit - removed suggestion to use dynamic port. Much better off using static ones so node reboots can be handled correctly.
Have each plugin config use a plugin-specific port (akka.remote.helios.tcp.port = 1231) and then define a clustered router that talks to actor systems fulfilling specific roles.
/api/broadcaster {
router = broadcast-group
routees.paths = ["user/api"]
cluster {
enabled = on
max-nr-of-instances-per-node = 1
allow-local-routees = on
use-role = crawler
}
}
This router, deployed at the path user/api/broadcaster on some node can communicate (via the Broadcast routing strategy) with any actor deployed at path user/api on any node in the cluster with role crawler without needing to look up IP addresses, ports, or any of that crap.
You configure a node's clustering information via the following section in Akka.NET's config:
cluster {
#manually populate other seed nodes here, i.e. "akka.tcp://lighthouse#127.0.0.1:4053"
seed-nodes = ["akka.tcp://webcrawler#127.0.0.1:4053"]
roles = [crawler]
}
Seed nodes - has to be a well-known, statically-defined port and IP address. Read the article for an explanation on why this is important.
Roles - comma-delimited strings that define what this particular nodes' capabilities are. They're more like tags. You can use them inside clustered routers (like the one I showed earlier) to articulate which types of nodes you want to communicate with.
Try as I might, I'm unable to resolve an address to IP. The code snippet is shown below. I keep getting the No such host is known exception, even though I could access google with my browser (The DNS server is almost certainly working). I'm however behind company's firewall.
try
{
foreach (IPAddress address in Dns.GetHostAddresses("www.google.com"))
{
Console.WriteLine(address.ToString());
}
}
catch (SocketException e)
{
Console.WriteLine("Source : " + e.Source); // System
Console.WriteLine("Message : " + e.Message); // No such host is known
}
There is nothing wrong with your code. Given that you can access www.google.com from a web browser the next most likely problem is that the web browser is using a proxy server. The web browser is actually accessing www.google.com through the proxy server which is allowed through the firewall. The simple application you wrote is not allowed through the firewall and is resulting in an exception.
You can verify this by looking at the proxy settings in Internet Explorer.
Tools -> Options -> Connections -> Lan Settings
There will be a proxy server group of settings. If there is a value present, this is almost certainly your problem.
You need to set up the proxy:
here's a snippet that should set it up for all the following calls:
protected void SetupProxy(string proxyUrl, string proxyLogin, string proxyPassword, string[] proxyBypass)
{
WebProxy proxy = new WebProxy(proxyUrl);
proxy.Credentials = new NetworkCredential(proxyLogin, proxyPassword);
proxy.BypassList = proxyBypass;
proxy.BypassProxyOnLocal = true;
WebRequest.DefaultWebProxy = proxy;
}
Rather than try through a browser, try pinging www.google.com (or some other host, of course) from the command line.
The ping itself may well not work, but it should show the IP address resolution first. If you get an error message like this:
Ping request could not find host www.google.com.
Please check the name and try again.
then it's likely that the proxy server is doing the DNS lookup for you when you're browsing, and your DNS server is either not working or your machine's network settings are incorrect.