001/* 002 * PermissionsEx 003 * Copyright (C) zml and PermissionsEx contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ca.stellardrift.permissionsex.impl.util; 018 019import org.checkerframework.checker.nullness.qual.Nullable; 020import org.spongepowered.configurate.ConfigurationNode; 021import org.spongepowered.configurate.serialize.SerializationException; 022import org.spongepowered.configurate.serialize.TypeSerializer; 023 024import java.lang.reflect.Type; 025import java.net.Inet4Address; 026import java.net.Inet6Address; 027import java.net.InetAddress; 028import java.net.UnknownHostException; 029import java.util.function.Predicate; 030 031import static java.util.Objects.requireNonNull; 032 033public final class IpSet implements Predicate<InetAddress> { 034 public static final TypeSerializer<IpSet> SERIALIZER = new IpSetSerializer(); 035 private final InetAddress address; 036 private final int prefixLength; 037 038 /** 039 * Create an IP set matching only one single address. 040 * 041 * @param address the address to match 042 * @return a new ip set 043 */ 044 public static IpSet only(final InetAddress address) { 045 requireNonNull(address, "address"); 046 return new IpSet(address, maxPrefixLength(address)); 047 } 048 049 /** 050 * Resolve an ip set from address and prefix length 051 * @param address the IP address 052 * @param prefixLen the prefix length 053 * @return a new ip set 054 * @throws IllegalArgumentException if the prefix length is not valid for the provided address 055 */ 056 public static IpSet fromAddrPrefix(final InetAddress address, final int prefixLen) { 057 requireNonNull(address, "address"); 058 validatePrefixLength(address, prefixLen); 059 return new IpSet(address, prefixLen); 060 } 061 062 /** 063 * Given a CIDR range, construct an IP set. 064 * @param spec the specification 065 * @return a parsed ip set matching the range 066 * @throws IllegalArgumentException if the syntax is not valid for CIDR, or the prefix length is invalid. 067 */ 068 public static IpSet fromCidr(final String spec) { 069 requireNonNull(spec, "spec"); 070 071 final @Nullable String addrString; 072 final int prefixLen; 073 final int slashIndex = spec.lastIndexOf("/"); 074 if (slashIndex == -1) { 075 addrString = spec; 076 prefixLen = addrString.startsWith("[") ? 128 : 32; // are we IPv6? 077 } else { 078 addrString = spec.substring(0, slashIndex); 079 prefixLen = Integer.parseInt(spec.substring(slashIndex + 1)); 080 } 081 final InetAddress addr; 082 try { 083 addr = InetAddress.getByName(addrString); 084 } catch (final UnknownHostException ex) { 085 throw new IllegalArgumentException(addrString + "does not contain a valid IP address"); 086 } 087 return fromAddrPrefix(addr, prefixLen); 088 } 089 090 IpSet(final InetAddress address, final int prefixLength) { 091 this.address = address; 092 this.prefixLength = prefixLength; 093 } 094 095 // State validation 096 097 static int maxPrefixLength(final InetAddress address) { 098 if (address instanceof Inet4Address) { 099 return 32; 100 } else if (address instanceof Inet6Address) { 101 return 128; 102 } else { 103 throw new IllegalArgumentException("Unknown IP address type " + address.getClass()); 104 } 105 } 106 107 static void validatePrefixLength(final InetAddress address, final int prefixLength) { 108 if (prefixLength < 0) { 109 throw new IllegalArgumentException("Minimum prefix length for an IP address is 0, but " 110 + prefixLength + " was provided."); 111 } 112 final int maxLength = maxPrefixLength(address); 113 if (prefixLength > maxLength) { 114 throw new IllegalArgumentException("Maximum prefix length for a " 115 + address.getClass().getSimpleName() + " is " + maxLength + ", but " 116 + prefixLength + " was provided"); 117 } 118 } 119 120 @Override 121 public boolean test(final InetAddress address) { 122 return this.contains(address); 123 } 124 125 public boolean contains(final InetAddress input) { 126 final byte[] address = input.getAddress(); 127 final byte[] checkAddr = this.address.getAddress(); 128 if (address.length != checkAddr.length) { 129 return false; 130 } 131 final byte completeSegments = (byte) (this.prefixLength >> 3); 132 final byte overlap = (byte) (this.prefixLength & 7); 133 for (int i = 0; i < completeSegments; ++i) { 134 if (address[i] != checkAddr[i]) { 135 return false; 136 } 137 } 138 139 for (int i = 0; i < overlap; ++i) { 140 if (((checkAddr[completeSegments + 1] >> (7 - i)) & 0x1) 141 != ((address[completeSegments + 1] >> (7 - i)) & 0x1)) { 142 return false; 143 } 144 } 145 return true; 146 } 147 148 public boolean contains(final IpSet other) { 149 if (other.prefixLength < this.prefixLength) { 150 return false; 151 } 152 153 return other.address.equals(this.address) 154 || this.contains(other.address); 155 } 156 157 /** 158 * Get the CIDR string representation of this IP set. 159 * 160 * @return the representation 161 */ 162 @Override 163 public String toString() { 164 return this.address.getHostAddress() + "/" + this.prefixLength; 165 } 166 167 static class IpSetSerializer implements TypeSerializer<IpSet> { 168 169 IpSetSerializer() { 170 } 171 172 @Override 173 public IpSet deserialize(final Type type, final ConfigurationNode node) throws SerializationException { 174 try { 175 return IpSet.fromCidr(node.getString()); 176 } catch (final IllegalArgumentException ex) { 177 throw new SerializationException(ex); 178 } 179 } 180 181 @Override 182 public void serialize(final Type type, final @Nullable IpSet obj, final ConfigurationNode node) throws SerializationException { 183 if (obj == null) { 184 node.raw(null); 185 return; 186 } 187 188 node.set(obj.toString()); // to cidr 189 } 190 } 191 192}