MangoAutomation / BACnet4J

BACnet/IP stack written in Java. Forked from http://sourceforge.net/projects/bacnet4j/
GNU General Public License v3.0
183 stars 110 forks source link

BACnet4J running on Linux cannot receive broadcast messages #53

Closed NiallBegley closed 2 years ago

NiallBegley commented 3 years ago

Describe the bug While running a Java application using BACnet4j I cannot receive broadcast bacnet UDP data even though I can see the UDP traffic coming in on Wireshark. This is on Ubuntu 19.04 and has been tested with both OpenJDK 8 and Oracle's JDK 8 and using BACnet4J version 6.0.0-SNAPSHOT.

To Reproduce Create a BACnet device in an java application running on the Linux machine.

On a Windows machine create another Java application that creates a device using the same code as above (but change the device number). Issue a WhoIs manually however you'd like (periodically, from pushing a button, etc).

If you issue broadcast messages from the Linux side, the application running on the Windows will pick up on them just fine. However, any broadcast messages issued from the Windows side will be ignored by BACnet4J (although you can verify the messages are coming in via Wireshark - see below).

Also, all unicast messages go through in both directions without any issues.

I've recreated this problem on multiple Linux machines including a couple VM's. None of the linux machines have firewalls.

It's also worth noting that the messages are not coming in to BACnet4J and being ignored for one reason or another - putting a breakpoint in IpNetwork's run() function shows that nothing is ever coming in from socket.receive() (unless you send unicast data, of course).

Expected behavior I'd expect the BACnet4J stack to pick up on broadcast messages as you see from Windows to Windows machine.

I've created a workaround by changing the binding of the DatagramSocket in IpNetwork.java from binding to a specific interface to just binding to a port as follows:

 @Override
    public void initialize(final Transport transport) throws Exception {
        super.initialize(transport);

        localBindAddress = InetAddrCache.get(localBindAddressStr, port);

        if (reuseAddress) {
            socket = new DatagramSocket(null);
            socket.setReuseAddress(true);
            if (!socket.getReuseAddress())
                LOG.warn("reuseAddress was set, but not supported by the underlying platform");
            socket.bind(port); // <---- Changed
        } else
            socket = new DatagramSocket(port); // <---- Changed
        socket.setBroadcast(true);

        //        broadcastAddress = new Address(broadcastIp, port, new Network(0xffff, new byte[0]));
        broadcastMAC = IpNetworkUtils.toOctetString(broadcastAddressStr, port);
        subnetMask = BACnetUtils.dottedStringToBytes(subnetMaskStr);

        thread = new Thread(this, "BACnet4J IP socket listener for " + transport.getLocalDevice().getId());
        thread.start();
    }

However, I don't consider this a valid fix as I'll now receive broadcast data on all interfaces which will probably be a problem for me at some point down the line.

Screenshots Here is the output of the ignored broadcast packet coming in to the Linux side. 192.168.1.22 is the windows machine, 192.168.1.32 is the Linux machine.

image

Mango Version (please complete the following information): N/A

Browser (please complete the following information): N/A

Additional context N/A

ma.log Snippet N/A

Browser console errors N/A

Minimal code example to reproduce

public class Main {

    public static void main(String[] args) throws Exception {

         //Either use the results of this to feed directly into the IpNetwork construction or just hardcode 
         List<InterfaceAddress> networkInterfaces = IpNetworkUtils.getLocalInterfaceAddresses();
        assert(networkInterfaces.size() > 0);

         IpNetwork networkOne = new IpNetworkBuilder()
                 .withLocalBindAddress("192.168.1.32")   //Change as needed
                 .withBroadcast("192.168.1.255", 24)
                 .withLocalNetworkNumber(1).withPort(0xBAC0).build();
         Transport transportOne = new DefaultTransport(networkOne);

         //Set up our device
         final LocalDevice localDeviceOne = new LocalDevice(1, transportOne);  //Change device # as needed

         localDeviceOne.initialize();
         localDeviceOne.getEventHandler().addListener(new DeviceEventAdapter() {
            @Override
            public void iAmReceived(RemoteDevice device) {
                System.out.println("inside I am received...");
                System.out.println("Discovered device " + device);
            }
        });

        //Drive firing of these however you'd like
        localDeviceOne.sendGlobalBroadcast(new WhoIsRequest());

        localDeviceOne.sendGlobalBroadcast(localDeviceOne.getIAm());
    }
}

Address and broadcast addresses were verified through NetworkInterface.getNetworkInterfaces()

splatch commented 3 years ago

@NiallBegley have you tried with different network number ie. 0?

NiallBegley commented 3 years ago

Yeah, even if both devices are on network 0 or one is on 1 and the other is on 0 it still doesn't work

terrypacker commented 3 years ago

@NiallBegley I've run into this before so I did a little more digging and remembered that BSD sockets behave differently from Win32 sockets. It is my understanding that in linux you cannot receive broadcast messages when bound to an address other than INADDR_ANY but in Windows there is no such rule.

terrypacker commented 2 years ago

@NiallBegley what I said didn't fix your issue can you please re-open and add more detail?