Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,6 @@ public final class IPListBuilder {
private IPListBuilder() {}

public static IPList createIPList(Collection<String> ips) {
IPList ipList = new IPList();
if (ips == null) {
return ipList; // Don't iterate over null.
}
for (String ip: ips) {
// Add ip address or subnet to IP list :
ipList.add(ip);
}

return ipList;
return new IPList(ips);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,66 @@

import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import inet.ipaddr.format.util.DualIPv4v6Tries;
import inet.ipaddr.ipv4.IPv4Address;

import java.util.Collection;
import java.util.List;

// The networks come from Aikido, not from users, so the matcher doesn't validate them strictly:
// "1.2.3.4abc" is stored as 1.2.3.4.
public class IPList {
private final DualIPv4v6Tries ipAddresses;
private volatile IPMatcher matcher;

public IPList() {
this.ipAddresses = new DualIPv4v6Tries();
this(List.of());
}

public IPList(Collection<String> ipsOrCIDRs) {
this.matcher = new IPMatcher(normalizeNetworks(ipsOrCIDRs));
}

public void add(String ipOrCIDR) {
if (ipOrCIDR == null) {
return; // Don't add if IP is null
}
IPAddress ip = new IPAddressString(ipOrCIDR).getAddress();
if (ip == null) {
return;
}
// Normalize IPv4-mapped IPv6 addresses to their IPv4 form so matching is symmetric.
if (ip.isIPv6() && ip.toIPv6().isIPv4Convertible()) {
IPAddress ipv4 = ip.toIPv6().toIPv4();
if (ipv4 != null) {
ip = ipv4;
}
}
if (ipOrCIDR.contains("/")) {
ip = ip.toPrefixBlock();
}
ipAddresses.add(ip);
matcher = matcher.withNetworks(List.of(ipv4IfMapped(ipOrCIDR)));
Comment on lines 14 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Each add rebuilds, sorts, and summarizes all existing networks, making repeated additions increasingly expensive and potentially quadratic overall.

Show fix
Suggested change
public IPList() {
this.ipAddresses = new DualIPv4v6Tries();
this(List.of());
}
public IPList(Collection<String> ipsOrCIDRs) {
this.matcher = new IPMatcher(normalizeNetworks(ipsOrCIDRs));
}
public void add(String ipOrCIDR) {
if (ipOrCIDR == null) {
return; // Don't add if IP is null
}
IPAddress ip = new IPAddressString(ipOrCIDR).getAddress();
if (ip == null) {
return;
}
// Normalize IPv4-mapped IPv6 addresses to their IPv4 form so matching is symmetric.
if (ip.isIPv6() && ip.toIPv6().isIPv4Convertible()) {
IPAddress ipv4 = ip.toIPv6().toIPv4();
if (ipv4 != null) {
ip = ipv4;
}
}
if (ipOrCIDR.contains("/")) {
ip = ip.toPrefixBlock();
}
ipAddresses.add(ip);
matcher = matcher.withNetworks(List.of(ipv4IfMapped(ipOrCIDR)));
private final List<String> networks;
public IPList() {
this(List.of());
}
public IPList(Collection<String> ipsOrCIDRs) {
this.networks = new java.util.concurrent.CopyOnWriteArrayList<>(normalizeNetworks(ipsOrCIDRs));
this.matcher = new IPMatcher(this.networks);
}
public void add(String ipOrCIDR) {
if (ipOrCIDR == null) {
return; // Don't add if IP is null
}
networks.add(ipv4IfMapped(ipOrCIDR));
matcher = new IPMatcher(networks);
Details

✨ AI Reasoning
​The method is intended to add one network. The replacement constructs a new matcher from the existing matcher and that network; the constructor copies the base entries and sorts and summarizes them. Repeated additions therefore repeat work proportional to the growing collection, which can become significant for large lists.

Reply on this comment to give feedback or ignore the issue.
More info

}

public boolean matches(String ip) {
IPAddressString ipAddressString = new IPAddressString(ip);
if (!ipAddressString.isValid()) {
return false; // Invalid IP address
}
IPAddress ipAddress = ipAddressString.getAddress();
return matcher.has(ip);
}

public int length() {
return matcher.size();
}

if (containsAddress(ipAddress)) {
return true;
private static List<String> normalizeNetworks(Collection<String> ipsOrCIDRs) {
if (ipsOrCIDRs == null) {
return List.of();
}
return ipsOrCIDRs.stream().map(IPList::ipv4IfMapped).toList();
}

// Also try the embedded IPv4 form for IPv4-mapped IPv6 addresses (e.g. ::ffff:23.45.67.89)
if (ipAddress.isIPv6() && ipAddress.toIPv6().isIPv4Convertible()) {
IPAddress ipv4 = ipAddress.toIPv6().toIPv4();
if (ipv4 != null && containsAddress(ipv4)) {
return true;
// The matcher matches IPv4-mapped lookups against IPv4 networks, not IPv4 lookups against mapped networks.
// So we store "::ffff:1.2.3.4" as "1.2.3.4".
private static String ipv4IfMapped(String network) {
if (!mayBeIpv4Mapped(network)) {
return network;
}
try {
IPAddress address = new IPAddressString(network).getAddress();
if (address == null || !address.isIPv6()) {
return network;
}
IPv4Address ipv4 = address.toIPv6().toIPv4();
return ipv4 == null ? network : ipv4.toNormalizedString();
} catch (RuntimeException e) {
return network;
}
return false;
}

private boolean containsAddress(IPAddress ipAddress) {
return ipAddresses.elementContains(ipAddress);
}
public int length() {
return ipAddresses.size();
// Avoids running the IP parser on every entry, lists can be big.
private static boolean mayBeIpv4Mapped(String network) {
return network != null && network.contains(":") && network.toLowerCase().contains("ffff");
}
}
Loading
Loading