Open moebassist opened 3 years ago
Hi @moebassist, I don't know how familiar you are with BACnet and/or this library, so just a couple of basic info/questions first:
fails to detect a 'Send WhoIs'
Hi @gralin ,
Thanks for the response!
I have an entire HMI application running on .Net 4.7.1 via Mono. It reads data from many Modbus RTU serial devices and acts as a data gateway, publishing the serial data to ModbusTCP and BACnet. The code uses WinForms to run on Linux, hence .Net 4.7.1.
I'm just confirming that I have been sending 'WhoIs' requests from YABE, using 'iptraf' on the Linux PC I see:
UDP (46 bytes) from 192.168.1.76:63630 (my development PC - I'm not sure why the YABE connection uses this port) to 192.168.1.255:47808 (My Linux PC and the port I've started the server on).
As a work around, I'm sending an 'IAm' every 30 seconds from my BACnet server - but it defeats the point of browsing :)
Ant.
Ok. So you can force YABE to use the standard port (47808 or 0xBAC0) for sending and receiving messages like this
Maybe this will help? Of course you should receive the WhoIs request in your app whenever you send it from YABE. Maybe a firewall on Linux is blocking it?
It IS to do with broadcast, and nothing to do with machine configuration.
Looking at BacnetIpUdpProtocolTransport.Open()
:
_sharedConn
is created happily, but _exclusiveConn
fails because its endpoint is identical, at which point the function exits....so I can't see how the UDP listener ever worked for both connections?
At the end of this function Bvlc = new BVLC(this);
is called to attempt to retrieve the broadcast address - this never executes because of the above.....I also can't understand why this is done AFTER the _sharedConn
is created (which should be listening broadcast). I can only image this works because it was written/tested on the same PC, therefore broadcast would 'seemingly work' when YABE sends 'WhoIs'.
For my fix, I've revised the constructor to include the broadcast endpoint and store it in a private variable in the same manner as the IP address:
public BacnetIpUdpProtocolTransport(int port, bool useExclusivePort = false, bool dontFragment = false, int maxPayload = 1472, string localEndpointIp = "",string broadcastEndpointIP="")
And changed BacnetIpUdpProtocolTransport.Open():
for initialising _sharedConn
from
if (!string.IsNullOrEmpty(_localEndpoint)) ep = new IPEndPoint(IPAddress.Parse(_localEndpoint), SharedPort);
to
if (!string.IsNullOrEmpty(_localEndpoint)) ep = new IPEndPoint(IPAddress.Parse(_broadcastEndpoint), SharedPort);
This means _sharedConn
uses the determined TRUE broadcast endpoint (e.g. 192.168.1.255) and _exclusiveConn
uses desired endpoint (e.g. 192.168.1.204) - the routine no longer exits due to a binding error on an already used endpoint.
The source mentions this too:
// A lot of problems on Mono (Raspberry) to get the correct broadcast @
// so this method is overridable (this allows the implementation of operating system specific code)
// Marc solution http://stackoverflow.com/questions/8119414/how-to-query-the-subnet-masks-using-mono-on-linux
My application changes the OS IP/Subnet (via an 'options' dialogue) - I don't have to get it from the OS - therefore I wrote a helper function to calculate broadcast address from IP/subnet strings:
internal static string GetBroadcastAddress(string iP, string networkMask)
{
byte[] ipAddressBytes = System.Net.IPAddress.Parse(iP).GetAddressBytes();
byte[] subnetMaskBytes = System.Net.IPAddress.Parse(networkMask).GetAddressBytes();
if (ipAddressBytes.Length != subnetMaskBytes.Length) throw new ArgumentException("IP/Mask lengths differ!");
byte[] bcBytes = new byte[ipAddressBytes.Length];
for (int i = 0; i < bcBytes.Length; i++)
{
bcBytes[i]=(byte)(ipAddressBytes[i] | (subnetMaskBytes[i] ^ 255));
}
return new System.Net.IPAddress(bcBytes).ToString();
}
.....Now it works on Linux. It's not a fix for everyone.
@moebassist Great, thanks a lot. Your solution worked perfectly for me.
@moebassist Great, thanks a lot. Your solution worked perfectly for me.
Are you experiencing memory leak as per #89 ?
In addition to the above, the Open()
function creates a new BLVC at the last line - which also attempts to determine the broadcast address by iterating through all of the available network adaptors. I've changed this too - (old code simply remarked out)
// A lot of problems on Mono (Raspberry) to get the correct broadcast @
// so this method is overridable (this allows the implementation of operating system specific code)
// Marc solution http://stackoverflow.com/questions/8119414/how-to-query-the-subnet-masks-using-mono-on-linux for instance
protected virtual BacnetAddress _GetBroadcastAddress()
{
// general broadcast by default if nothing better is found
//var ep = new IPEndPoint(IPAddress.Parse("255.255.255.255"), SharedPort);
var ep = new IPEndPoint(IPAddress.Parse(_broadcastEndpoint), SharedPort);
Convert(ep, out var broadcast);
broadcast.net = 0xFFFF;
return broadcast;
//UnicastIPAddressInformation ipAddr = null;
//if (LocalEndPoint.Address.ToString() == "0.0.0.0")
//{
// ipAddr = GetAddressDefaultInterface();
//}
//else
//{
// // restricted local broadcast (directed ... routable)
// foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
// foreach (var ip in adapter.GetIPProperties().UnicastAddresses)
// if (LocalEndPoint.Address.Equals(ip.Address))
// {
// ipAddr = ip;
// break;
// }
//}
//if (ipAddr != null)
//{
// try
// {
// var strCurrentIP = ipAddr.Address.ToString().Split('.');
// var strIPNetMask = ipAddr.IPv4Mask.ToString().Split('.');
// var broadcastStr = new StringBuilder();
// for (var i = 0; i < 4; i++)
// {
// broadcastStr.Append(((byte) (int.Parse(strCurrentIP[i]) | ~int.Parse(strIPNetMask[i]))).ToString());
// if (i != 3) broadcastStr.Append('.');
// }
// ep = new IPEndPoint(IPAddress.Parse(broadcastStr.ToString()), SharedPort);
// }
// catch
// {
// // on mono IPv4Mask feature not implemented
// }
//}
//Convert(ep, out var broadcast);
//broadcast.net = 0xFFFF;
//return broadcast;
}
It IS to do with broadcast, and nothing to do with machine configuration.
Windows forwards packets sent to broadcast address to all the addresses bound within the subnet. Linux does not forward the broadcasts anywhere. Thus, you have to explicitly bind to the broadcast address. This is good, since you as developer can decide if you want to listen for broadcasts! In IPv6 there won't even be broadcasts at all!
This means
_sharedConn
uses the determined TRUE broadcast endpoint (e.g. 192.168.1.255) and_exclusiveConn
uses desired endpoint (e.g. 192.168.1.204) - the routine no longer exits due to a binding error on an already used endpoint.
This is highly OS dependent. As stated before, Windows will forward these, thus, you will receive all the broadcasts twice on Windows systems. I fixed this some days ago. Will send a PR as soon as I can.
Hi,
I'm running V2.0.4 on Ubuntu via Mono. If I connect YABE to my PC's ethernet adaptor and the launch my BACnet server app (running on a separate PC Ubuntu 18), everything works well because right after starting the server, an 'Iam(Storage.Deviceid) is executed.
However, when the BACnet server app is running before YABE, it fails to detect a 'Send WhoIs'. I've reviewed incoming packets on the Ubuntu machine and I can confirm the Ethernet adaptor is receiving the broadcast traffic via 192.168.1.255 so there's no firewall/routine issues.
I've added debug code to the 'OnWhoIs' handler which is correctly subscribed to the server (BacNetClient.OnWhoIs += xxxx) that should output 'Who Is' to the console - it never appears, so my guess is the event isn't executing.
Any thoughts?
Thanks!