001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v2.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.util; 015 016import java.net.InetAddress; 017import java.net.UnknownHostException; 018 019/** 020 * Matches an {@link InetAddress} against a single IP address or a CIDR network 021 * range. 022 * <p> 023 * Accepted forms: 024 * <ul> 025 * <li>Single address: {@code 192.168.1.10}, {@code 2001:db8::1}</li> 026 * <li>CIDR range: {@code 192.168.1.0/24}, {@code 2001:db8::/32}</li> 027 * </ul> 028 * 029 * @author Ceki Gülcü 030 * @since 1.6.2 031 */ 032public class IpAddressMatcher { 033 034 private final String original; 035 private final byte[] networkAddress; 036 private final int prefixLength; 037 038 /** 039 * Creates a matcher for the given address or CIDR specification. 040 * 041 * @param addressOrCidr a single IP or {@code address/prefixLength} 042 * @throws IllegalArgumentException if the specification is invalid 043 */ 044 public IpAddressMatcher(String addressOrCidr) { 045 if (addressOrCidr == null) { 046 throw new IllegalArgumentException("addressOrCidr must not be null"); 047 } 048 this.original = addressOrCidr.trim(); 049 if (this.original.isEmpty()) { 050 throw new IllegalArgumentException("addressOrCidr must not be empty"); 051 } 052 053 String addressPart; 054 Integer explicitPrefix = null; 055 int slashIndex = this.original.indexOf('/'); 056 if (slashIndex >= 0) { 057 addressPart = this.original.substring(0, slashIndex).trim(); 058 String prefixPart = this.original.substring(slashIndex + 1).trim(); 059 if (addressPart.isEmpty() || prefixPart.isEmpty()) { 060 throw new IllegalArgumentException("Invalid CIDR specification [" + addressOrCidr + "]"); 061 } 062 try { 063 explicitPrefix = Integer.parseInt(prefixPart); 064 } catch (NumberFormatException e) { 065 throw new IllegalArgumentException( 066 "Invalid prefix length in [" + addressOrCidr + "]", e); 067 } 068 } else { 069 addressPart = this.original; 070 } 071 072 InetAddress parsed; 073 try { 074 parsed = InetAddress.getByName(addressPart); 075 } catch (UnknownHostException e) { 076 throw new IllegalArgumentException("Unknown host or invalid IP address [" + addressPart + "]", e); 077 } 078 079 this.networkAddress = parsed.getAddress(); 080 int maxPrefix = this.networkAddress.length * 8; 081 if (explicitPrefix != null) { 082 if (explicitPrefix < 0 || explicitPrefix > maxPrefix) { 083 throw new IllegalArgumentException("Prefix length " + explicitPrefix 084 + " is out of range for address [" + addressPart + "] (0-" + maxPrefix + ")"); 085 } 086 this.prefixLength = explicitPrefix; 087 } else { 088 this.prefixLength = maxPrefix; 089 } 090 } 091 092 /** 093 * Returns {@code true} if the given address matches this matcher. 094 * 095 * @param address the address to test; may be {@code null} 096 * @return {@code true} if {@code address} matches 097 */ 098 public boolean matches(InetAddress address) { 099 if (address == null) { 100 return false; 101 } 102 return matchesBytes(address.getAddress()); 103 } 104 105 /** 106 * Returns {@code true} if the given address string matches this matcher. 107 * 108 * @param address an IP address string 109 * @return {@code true} if the address matches 110 * @throws IllegalArgumentException if {@code address} cannot be parsed 111 */ 112 public boolean matches(String address) { 113 if (address == null) { 114 throw new IllegalArgumentException("address must not be null"); 115 } 116 try { 117 return matches(InetAddress.getByName(address.trim())); 118 } catch (UnknownHostException e) { 119 throw new IllegalArgumentException("Unknown host or invalid IP address [" + address + "]", e); 120 } 121 } 122 123 private boolean matchesBytes(byte[] candidate) { 124 if (candidate == null || candidate.length != networkAddress.length) { 125 return false; 126 } 127 128 int fullBytes = prefixLength / 8; 129 int remainingBits = prefixLength % 8; 130 131 for (int i = 0; i < fullBytes; i++) { 132 if (candidate[i] != networkAddress[i]) { 133 return false; 134 } 135 } 136 137 if (remainingBits > 0) { 138 int mask = 0xFF << (8 - remainingBits); 139 if ((candidate[fullBytes] & mask) != (networkAddress[fullBytes] & mask)) { 140 return false; 141 } 142 } 143 144 return true; 145 } 146 147 /** 148 * The original specification used to construct this matcher. 149 */ 150 public String getSpecification() { 151 return original; 152 } 153 154 @Override 155 public String toString() { 156 return "IpAddressMatcher[" + original + "]"; 157 } 158}