Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MANGO-1817 - fix build methods in IpNetworkBuilder to get the Local IP Address, Broadcast Address and Subnet Mask #109

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<url>https://www.infiniteautomation.com/</url>
</organization>
<properties>
<revision>6.0.1-SNAPSHOT</revision>
<revision>6.1.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<additionalDeploymentRepositoryId>additional-deployment-repository</additionalDeploymentRepositoryId>
</properties>
Expand Down Expand Up @@ -48,8 +48,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<source>21</source>
<target>21</target>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely cannot target Java 21, since Mango only requires Java 17. We should discuss which version to target (I would be in favor of using Java 17).

</configuration>
</plugin>
<plugin>
Expand Down
18 changes: 11 additions & 7 deletions src/main/java/com/serotonin/bacnet4j/npdu/ip/IpNetworkBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

import static com.serotonin.bacnet4j.npdu.ip.IpNetworkUtils.toIpAddrString;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;

import com.serotonin.bacnet4j.type.constructed.Address;
import com.serotonin.bacnet4j.util.BACnetUtils;
Expand All @@ -41,10 +41,12 @@ public class IpNetworkBuilder {
private String subnetMask;
private int port = IpNetwork.DEFAULT_PORT;
private int localNetworkNumber = Address.LOCAL_NETWORK;
private boolean reuseAddress = false;
final private boolean reuseAddress = (SystemUtils.IS_OS_WINDOWS) ? false : true;

public IpNetworkBuilder withLocalBindAddress(final String localBindAddress) {
this.localBindAddress = localBindAddress;
this.broadcastAddress = IpNetworkUtils.getLocalBroadcastAddressString(localBindAddress);
this.subnetMask = IpNetworkUtils.getSubnetMask(localBindAddress);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are trying not to break existing usages of this code I think it would be wise to check if these fields are already set.
e.g.

if (this.broadcastAddress == null) {
  this.broadcastAddress = IpNetworkUtils.getLocalBroadcastAddressString(localBindAddress);
}
if (this.subnetMask == null) {
  this.subnetMask = IpNetworkUtils.getSubnetMask(localBindAddress);
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The operating systems would send the broadcast packets to the socket if and only if there is a socket opened for that specific broadcast address and that broadcast address & subnet mask is configured in the NIC. If the user happens to set it incorrectly, handling broadcast would become difficult. The same is applicable for local bind address.

Hence it would be right approach if the BACnet4J is initialized with the right set of network parameters. As per the above comments there are chances that the network could be built with wrong set of parameters. Hence checking of null would not be advisable. Further to avoid any confusion to the end user, building the network with NIC name is created. This method is simple and error free. For example

IpNetwork network = new IpNetworkBuilder().withInterfaceName("LAN1").build();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are chances that the network could be built with wrong set of parameters.

Correct, but we have always provided the opportunity to configure this wrong. I am simply requesting that we maintain existing behavior and no not override properties which were explicitly configured by the programmer.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has still not been addressed.

return this;
}

Expand All @@ -60,7 +62,7 @@ public IpNetworkBuilder withLocalBindAddress(final String localBindAddress) {
public IpNetworkBuilder withBroadcast(final String broadcastAddress, final int networkPrefixLength) {
this.broadcastAddress = broadcastAddress;
this.subnetMask = toIpAddrString(IpNetworkUtils.createMask(networkPrefixLength));

this.localBindAddress = IpNetworkUtils.getIPAddressString(this.broadcastAddress);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if localBindAddress is already set (as per above comment)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above said response

return this;
}

Expand All @@ -83,7 +85,7 @@ public IpNetworkBuilder withSubnet(final String subnetAddress, final int network
final long subnet = IpNetworkUtils.bytesToLong(BACnetUtils.dottedStringToBytes(subnetAddress));

this.broadcastAddress = toIpAddrString(subnet | negMask);

this.localBindAddress = IpNetworkUtils.getIPAddressString(this.broadcastAddress);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if localBindAddress is already set (as per above comment)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above said response

return this;
}

Expand All @@ -97,11 +99,13 @@ public IpNetworkBuilder withLocalNetworkNumber(final int localNetworkNumber) {
return this;
}

public IpNetworkBuilder withReuseAddress(final boolean reuseAddress) {
this.reuseAddress = reuseAddress;
public IpNetworkBuilder withInterfaceName(final String ifaceName ){
this.localBindAddress = IpNetworkUtils.getIPAddressString(ifaceName);
this.broadcastAddress = IpNetworkUtils.getLocalBroadcastAddressString(ifaceName);
this.subnetMask = IpNetworkUtils.getSubnetMask(ifaceName);
return this;
}

public String getLocalBindAddress() {
return localBindAddress;
}
Expand Down
144 changes: 139 additions & 5 deletions src/main/java/com/serotonin/bacnet4j/npdu/ip/IpNetworkUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,60 @@ public static Address toAddress(final int networkNumber, final InetSocketAddress
return toAddress(networkNumber, addr.getAddress().getAddress(), addr.getPort());
}

public static List<InterfaceAddress> getLocalInterfaceAddresses() {

/**
* Following details of the NIC (Network Interface Card) are stored in this record:
* <ul>
* <li> Integer format of index value of the NIC assigned by the OS </li>
* <li> String format of the name of the NIC </li>
* <li> String format of the IP4 Address assigned to the NIC </li>
* <li> String format of the the broadcast Address of the NIC </li>
* <li> String format of the SubnetMask of the NIC </li>
* <li> Integer format of the Network Prefix Length of the NIC </li>
* </ul>
*/
public static record InterfaceInfo(int index,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using static is redundant for records. Your IDE should warn about this.

String interfaceName,
String localIPAddress,
String broadcastAddress,
String subnetMask,
int networkPrefixLength) {}


/**
* Convenient method to get the deails of Network Interface card
* <p>
* Values of the following parameters of each NIC are stored in record format:
* <ul>
* <li> index (int) - NIC Card Index assigned by the OS
* <li> Name of the NIC (String)
* <li> IP4 Address assigned to the NIC (String)
* <li> Broadcast Address of the NIC (String)
* <li> SubnetMask of the NIC calculated based on the NetworkPrefixLength (String)
* <li> Network Prefix Length of the NIC (int)
* </ul>
* @return the list of records of all NIC available in this device
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of these tags are not closed. Your IDE should be configured to warn about this.

public static List<InterfaceInfo> getLocalInterfaceAddresses() {
try {
final List<InterfaceAddress> result = new ArrayList<>();
List<InterfaceInfo> ifaceDetails = new ArrayList<>();
for (final NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
for (final InterfaceAddress addr : iface.getInterfaceAddresses()) {
if (!addr.getAddress().isLoopbackAddress() && addr.getAddress().isSiteLocalAddress())
result.add(addr);
if (!addr.getAddress().isLoopbackAddress() && addr.getAddress().isSiteLocalAddress()){
String ifaceName = iface.getName();
String IPAddress = addr.getAddress().getHostName();
String broadcastAddress = addr.getBroadcast().getHostName();
final int networkPrefixLength = addr.getNetworkPrefixLength();
final long subnetMask = createMask(networkPrefixLength);
String networkMask = toIpAddrString(subnetMask);
int ifaceIndex = iface.getIndex();
InterfaceInfo ifaceInfo = new InterfaceInfo(ifaceIndex, ifaceName, IPAddress, broadcastAddress, networkMask, networkPrefixLength);
ifaceDetails.add(ifaceInfo);
}
}

}
return result;
return ifaceDetails;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning an immutable collection would be preferable.

} catch (final Exception e) {
// Should never happen, so just wrap in a RuntimeException
throw new RuntimeException(e);
Expand Down Expand Up @@ -199,4 +243,94 @@ public static String toIpAddrString(final long addr) {
sb.append(addr & 0xFF);
return sb.toString();
}


/**
* Method to return the IP Address in dotted string based on
* the broadcast Address string or IP Address string.
* Throws exception if the Broadcast Address or IP address is
* not configured in any of the available Network Interface
*
* @param host
* @return the IP Address assigned to the NIC
*/
public static String getIPAddressString(String host)
{
try{
for (InterfaceInfo info:getLocalInterfaceAddresses()){
if (info.localIPAddress().equals(host) ||
info.interfaceName().equals(host) ||
info.broadcastAddress().equals(host))
return info.localIPAddress();
}
throw new IllegalArgumentException(host + " is not configured in any interfaces.");


}catch (final Exception e) {
// Should never happen, so just wrap in a RuntimeException
throw new RuntimeException(e);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use an IllegalStateException?

}
}

/**
* Method to return the Broadcast IP Address configured
* for the corresponding interface based on the
* IP Address string or name of the NIC.
* Throws exception if the IP address or interface name is
* not configured in any of the available Network Interface.
*
* @param host
* @return BroadcastAddress
*/
public static String getLocalBroadcastAddressString(String host)
{
try{
for (InterfaceInfo info:getLocalInterfaceAddresses()){
if (info.localIPAddress().equals(host) ||
(info.broadcastAddress() != null &&
info.broadcastAddress().equals(host)) ||
info.interfaceName().equals(host))
return info.broadcastAddress();

}

throw new IllegalArgumentException(host + " is not configured in any interfaces.");

}catch (final Exception e) {
// Should never happen, so just wrap in a RuntimeException
throw new RuntimeException(e);
}
}


/**
* Method to return the the Subnet Mask configured
* for the corresponding interface based on the
* IP Address string or interface name.
* Throws IllegalArguement exception if the IP address or
* interface name is not configured in any of the
* available Network Interface.
*
* @param host
* @return SubnetMask of the NIC
*/
public static String getSubnetMask(String host)
{
try{

for (InterfaceInfo info:getLocalInterfaceAddresses()){
if (info.localIPAddress().equals(host) ||
info.interfaceName().equals(host) ||
info.broadcastAddress().equals(host))
return info.subnetMask();
}
throw new IllegalArgumentException(host + " is not configured in any interfaces.");



}catch (final Exception e) {
// Should never happen, so just wrap in a RuntimeException
throw new RuntimeException(e);
}
}
}
14 changes: 10 additions & 4 deletions src/test/java/com/serotonin/bacnet4j/adhoc/IpMasterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

package com.serotonin.bacnet4j.adhoc;


import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -15,6 +19,7 @@
import com.serotonin.bacnet4j.exception.BACnetException;
import com.serotonin.bacnet4j.npdu.Network;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkBuilder;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkUtils;
import com.serotonin.bacnet4j.service.unconfirmed.WhoIsRequest;
import com.serotonin.bacnet4j.transport.DefaultTransport;
import com.serotonin.bacnet4j.transport.Transport;
Expand All @@ -34,12 +39,13 @@ public static void main(final String[] args) throws Exception {
}

public LocalDevice createIpLocalDevice() throws Exception {
Network network =network = new IpNetworkBuilder()
.withLocalBindAddress("0.0.0.0")
.withBroadcast("255.255.255.255", 24)
Random rand = new Random();
List<IpNetworkUtils.InterfaceInfo> interfaceDetails = IpNetworkUtils.getLocalInterfaceAddresses();
final IpNetworkUtils.InterfaceInfo efaceInfo = interfaceDetails.get(rand.nextInt(interfaceDetails.size()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a random real network interface makes this test not repeatable.

Network network = new IpNetworkBuilder()
.withInterfaceName(efaceInfo.interfaceName())
.withPort(47808)
.withLocalNetworkNumber(5)
.withReuseAddress(true)
.build();

Transport transport = new DefaultTransport(network);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static void main(final String[] args) throws Exception {
IpNetwork networkOne = new IpNetworkBuilder()
.withLocalBindAddress(bindAddress1)
.withBroadcast("255.255.255.255", 24)
.withLocalNetworkNumber(1).withPort(47808).withReuseAddress(false).build();
.withLocalNetworkNumber(1).withPort(47808).build();
Transport transportOne = new DefaultTransport(networkOne);
LocalDevice localDeviceOne = new LocalDevice(1, transportOne);

Expand All @@ -60,7 +60,7 @@ public static void main(final String[] args) throws Exception {
IpNetwork networkTwo = new IpNetworkBuilder()
.withLocalBindAddress(bindAddress2)
.withBroadcast("255.255.255.255", 24)
.withLocalNetworkNumber(1).withPort(47808).withReuseAddress(false).build();
.withLocalNetworkNumber(1).withPort(47808).build();
Transport transportTwo = new DefaultTransport(networkTwo);
LocalDevice localDeviceTwo = new LocalDevice(2, transportTwo);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package com.serotonin.bacnet4j.npdu.ip;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.util.List;
import java.util.Random;

import org.junit.Before;
import org.junit.Test;

public class IpNetworkBuilderTest {

IpNetworkUtils.InterfaceInfo efaceInfo;
List<IpNetworkUtils.InterfaceInfo> interfaceDetails;

@Test
public void withSubnet16() {
final IpNetworkBuilder builder = new IpNetworkBuilder().withSubnet("192.168.0.0", 16);
Expand Down Expand Up @@ -46,4 +56,61 @@ public void withBroadcast19() {
assertEquals("192.168.223.255", builder.getBroadcastAddress());
assertEquals("255.255.224.0", builder.getSubnetMask());
}

@Before public void initialize(){
Random rand = new Random();
interfaceDetails = IpNetworkUtils.getLocalInterfaceAddresses();
efaceInfo = interfaceDetails.get(rand.nextInt(interfaceDetails.size()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a random real network interface makes this test not repeatable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, lets not use the name efaceInfo, variable names should be understandable. Use interfaceInfo instead.

}

@Test
public void withLocalIPAddress() throws RuntimeException{

String IPAddress = efaceInfo.localIPAddress();
final IpNetworkBuilder builder = new IpNetworkBuilder().withLocalBindAddress(IPAddress);
assertEquals(IPAddress,builder.getLocalBindAddress());
assertEquals(efaceInfo.broadcastAddress(), builder.getBroadcastAddress());
assertEquals(efaceInfo.subnetMask(), builder.getSubnetMask());
}

@Test
public void withBroadcast() throws RuntimeException{

String IPAddress = efaceInfo.broadcastAddress();
final IpNetworkBuilder builder = new IpNetworkBuilder().withBroadcast(IPAddress, efaceInfo.networkPrefixLength());
assertEquals(IPAddress,builder.getBroadcastAddress());
assertEquals(efaceInfo.localIPAddress(), builder.getLocalBindAddress());
assertEquals(efaceInfo.subnetMask(), builder.getSubnetMask());
}


@Test
public void withLocalBindAddress_IncorrectIP() throws RuntimeException{
String IPAddress = "1.1.1.1";
Exception exception = assertThrows(RuntimeException.class,() -> {
final IpNetworkBuilder builder = new IpNetworkBuilder().withLocalBindAddress(IPAddress);});
String actualMsg = exception.getMessage();
System.out.println(actualMsg);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove System.out logging

assertTrue((actualMsg.contains("IllegalArgument")));
}

@Test
public void verifyReuseAddress(){
final IpNetworkBuilder builder = new IpNetworkBuilder();
if (System.getProperty("os.name").contains("Windows"))
assertTrue(!builder.isReuseAddress());//reuseAddress = false;
else
assertTrue(builder.isReuseAddress());

}

@Test
public void withIterfaceName() throws RuntimeException{
String ifaceName = efaceInfo.interfaceName();
final IpNetworkBuilder builder = new IpNetworkBuilder().withInterfaceName(ifaceName);
assertEquals(efaceInfo.localIPAddress(), builder.getLocalBindAddress());
assertEquals(efaceInfo.broadcastAddress(),builder.getBroadcastAddress());
assertEquals(efaceInfo.subnetMask(), builder.getSubnetMask());

}
}