io.hekate.network.address.AddressPattern Maven / Gradle / Ivy
/*
* Copyright 2022 The Hekate Project
*
* The Hekate Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.hekate.network.address;
import io.hekate.core.HekateException;
import io.hekate.core.internal.util.AddressUtils;
import io.hekate.network.NetworkServiceFactory;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Pattern-based implementation of {@link AddressSelector} interface.
*
*
* This implementation of {@link AddressSelector} interface scans through all available network interfaces of the local host and uses
* the pattern matching logic in order to decide which address should be selected.
*
*
*
* The following patterns can be specified:
*
*
*
* - any - any non-loopback address
* - any-ip4 - any IPv4 non-loopback address
* - any-ip6 - any IPv6 non-loopback address
*
* - ip~regex - any IP address that matches the specified regular expression
* - ip4~regex - any IPv4 address that matches the specified regular expression
* - ip6~regex - any IPv6 address that matches the specified regular expression
*
* - !ip~regex - any IP address that does NOT match the specified regular expression
* - !ip4~regex - any IPv4 address that does NOT match the specified regular expression
* - !ip6~regex - any IPv6 address that does NOT match the specified regular expression
*
* - net~regex - any IP address of a network interface whose {@link NetworkInterface#getName() name} matches the specified
* regular expression
* - net4~regex - IPv4 address of a network interface whose {@link NetworkInterface#getName() name} matches the specified
* regular expression
* - net6~regex - IPv6 address of a network interface whose {@link NetworkInterface#getName() name} matches the specified
* regular expression
*
* - !net~regex - any IP address of a network interface whose {@link NetworkInterface#getName() name} does NOT match the
* specified regular expression
* - !net4~regex - IPv4 address of a network interface whose {@link NetworkInterface#getName() name} does NOT match the
* specified regular expression
* - !net6~regex - IPv6 address of a network interface whose {@link NetworkInterface#getName() name} does NOT match the
* specified regular expression
*
* - ...all other values will be treated as a directly specified address (see {@link InetAddress#getByName(String)})
*
*
*
* If several addresses match the specified pattern, the first one will be selected (order is not guaranteed, but preference will be given
* to non-{@link NetworkInterface#isPointToPoint() P2P} addresses).
*
*
* @see AddressSelector
* @see NetworkServiceFactory#setHostSelector(AddressSelector)
*/
public class AddressPattern implements AddressSelector {
private static final Logger log = LoggerFactory.getLogger(AddressPattern.class);
private static final boolean DEBUG = log.isDebugEnabled();
private final AddressPatternOpts opts;
/**
* Constructs a new instance with {@code 'any'} pattern.
*/
public AddressPattern() {
this(null);
}
/**
* Constructs a new instance.
*
* @param pattern Pattern (see the descriptions of this class for the list of supported patterns).
*/
public AddressPattern(String pattern) {
this.opts = AddressPatternOpts.parse(pattern);
}
/**
* Returns the host pattern as string.
*
* @return Host pattern.
*/
public String pattern() {
return opts.toString();
}
@Override
public InetAddress select() throws HekateException {
try {
if (opts.exactAddress() != null) {
if (DEBUG) {
log.debug("Using the exact address [{}]", opts);
}
return InetAddress.getByName(opts.exactAddress());
}
if (DEBUG) {
log.debug("Trying to resolve address [{}]", opts);
}
Pattern niIncludes = regex(opts.interfaceMatch());
Pattern niExcludes = regex(opts.interfaceNotMatch());
Pattern addrIncludes = regex(opts.ipMatch());
Pattern addrExcludes = regex(opts.ipNotMatch());
List nis = networkInterfaces();
InetAddress p2pAddr = null;
String p2pNiName = null;
for (NetworkInterface ni : nis) {
if (!ni.isUp() || ni.isLoopback()) {
continue;
}
String niName = ni.getName();
if (matches(true, niName, niIncludes) && matches(false, niName, niExcludes)) {
Enumeration addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (DEBUG) {
log.debug("Trying address {}", address);
}
if (checkAddress(addrIncludes, addrExcludes, niName, address)) {
if (ni.isPointToPoint()) {
// If this is a P2P address then store it as a selected address in case if there are no other addresses.
if (p2pAddr == null) {
p2pAddr = address;
p2pNiName = niName;
}
} else {
if (DEBUG) {
log.debug("Resolved address [interface={}, address={}]", niName, address);
}
// Returns the selected address.
return address;
}
}
}
} else {
if (DEBUG) {
log.debug("Skipped network interface that doesn't match name pattern [name={}]", niName);
}
}
}
if (DEBUG) {
log.debug("Resolved Point-to-Point address [interface={}, address={}]", p2pNiName, p2pAddr);
}
// Returns the first selected P2P address if we couldn't find any alternatives.
return p2pAddr;
} catch (IOException e) {
throw new HekateException("Failed to resolve node address [" + opts + ']', e);
}
}
// Package level for testing purposes.
AddressPatternOpts opts() {
return opts;
}
// Package level for testing purposes.
List networkInterfaces() throws SocketException {
return AddressUtils.activeNetworks();
}
private boolean checkAddress(Pattern addrIncludes, Pattern addrExcludes, String niName, InetAddress address) {
if (address.isLinkLocalAddress()) {
if (DEBUG) {
log.debug("Skipped link local address [interface={}, address={}]", niName, address);
}
} else if (!ipVersionMatch(address)) {
if (DEBUG) {
log.debug("Skipped address that doesn't match IP protocol version [interface={}, address={}]", niName, address);
}
} else {
String host = address.getHostAddress();
if (matches(true, host, addrIncludes) && matches(false, host, addrExcludes)) {
return true;
} else {
if (DEBUG) {
log.debug("Skipped address that doesn't match host pattern [interface={}, address={}]", niName, host);
}
}
}
return false;
}
private boolean ipVersionMatch(InetAddress addr) {
if (opts.ipVersion() != null) {
switch (opts.ipVersion()) {
case V4: {
return addr instanceof Inet4Address;
}
case V6: {
return addr instanceof Inet6Address;
}
default: {
throw new IllegalStateException("Unexpected IP version type: " + opts.ipVersion());
}
}
}
return true;
}
private boolean matches(boolean shouldMatch, String str, Pattern pattern) {
return pattern == null || pattern.matcher(str).matches() == shouldMatch;
}
private Pattern regex(String pattern) {
if (pattern != null) {
pattern = pattern.trim();
if (!pattern.isEmpty()) {
return Pattern.compile(pattern);
}
}
return null;
}
@Override
public String toString() {
return opts.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy