// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.util; import java.net.InetAddress; import java.util.AbstractSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.function.Predicate; /** * A set of InetAddress patterns. * <p>This is a {@link Set} of String patterns that are used to match * a {@link Predicate} over InetAddress for containment semantics. * The patterns that may be set are: * </p> * <dl> * <dt>InetAddress</dt><dd>A single InetAddress either in hostname or address format. * All formats supported by {@link InetAddress} are accepted. Not ethat using hostname * matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd> * <dt>InetAddress/CIDR</dt><dd>An InetAddress with a integer number of bits to indicate * the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to * "192.168.255.255" </dd> * <dt>InetAddress-InetAddress</dt><dd>An inclusive range of InetAddresses. * eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd> * <dt>Legacy format</dt><dd>The legacy format used by {@link IPAddressMap} for IPv4 only. * eg. "10.10.10-14.0-128"</dd> * </dl> * <p>This class is designed to work with {@link IncludeExcludeSet}</p> * @see IncludeExcludeSet */ public class InetAddressSet extends AbstractSet<String> implements Set<String>, Predicate<InetAddress> { private Map<String,InetPattern> _patterns = new HashMap<>(); @Override public boolean add(String pattern) { return _patterns.put(pattern,newInetRange(pattern))==null; } protected InetPattern newInetRange(String pattern) { if (pattern==null) return null; int slash = pattern.lastIndexOf('/'); int dash = pattern.lastIndexOf('-'); try { if (slash>=0) return new CidrInetRange(pattern,InetAddress.getByName(pattern.substring(0,slash).trim()),StringUtil.toInt(pattern,slash+1)); if (dash>=0) return new MinMaxInetRange(pattern,InetAddress.getByName(pattern.substring(0,dash).trim()),InetAddress.getByName(pattern.substring(dash+1).trim())); return new SingletonInetRange(pattern,InetAddress.getByName(pattern)); } catch(Exception e) { try { if (slash<0 && dash>0) return new LegacyInetRange(pattern); } catch(Exception e2) { e.addSuppressed(e2); } throw new IllegalArgumentException("Bad pattern: "+pattern,e); } } @Override public boolean remove(Object pattern) { return _patterns.remove(pattern)!=null; } @Override public Iterator<String> iterator() { return _patterns.keySet().iterator(); } @Override public int size() { return _patterns.size(); } @Override public boolean test(InetAddress address) { if (address==null) return false; byte[] raw = address.getAddress(); for (InetPattern pattern : _patterns.values()) if (pattern.test(address,raw)) return true; return false; } abstract static class InetPattern { final String _pattern; InetPattern(String pattern) { _pattern=pattern; } abstract boolean test(InetAddress address, byte[] raw); @Override public String toString() { return _pattern; } } static class SingletonInetRange extends InetPattern { final InetAddress _address; public SingletonInetRange(String pattern, InetAddress address) { super(pattern); _address=address; } public boolean test(InetAddress address, byte[] raw) { return _address.equals(address); } } static class MinMaxInetRange extends InetPattern { final int[] _min; final int[] _max; public MinMaxInetRange(String pattern, InetAddress min, InetAddress max) { super(pattern); byte[] raw_min = min.getAddress(); byte[] raw_max = max.getAddress(); if (raw_min.length!=raw_max.length) throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: "+pattern); if (raw_min.length==4) { // there must be 6 '.' or this is likely to be a legacy pattern int count=0; for (char c:pattern.toCharArray()) if (c=='.') count++; if (count!=6) throw new IllegalArgumentException("Legacy pattern: "+pattern); } _min = new int[raw_min.length]; _max = new int[raw_min.length]; for (int i=0;i<_min.length;i++) { _min[i]=0xff&raw_min[i]; _max[i]=0xff&raw_max[i]; } for (int i=0;i<_min.length;i++) { if (_min[i]>_max[i]) throw new IllegalArgumentException("min is greater than max: "+pattern); if (_min[i]<_max[i]) break; } } public boolean test(InetAddress item, byte[] raw) { if (raw.length!=_min.length) return false; boolean min_ok = false; boolean max_ok = false; for (int i=0;i<_min.length;i++) { int r = 0xff&raw[i]; if (!min_ok) { if (r<_min[i]) return false; if (r>_min[i]) min_ok=true; } if (!max_ok) { if (r>_max[i]) return false; if (r<_max[i]) max_ok=true; } if (min_ok && max_ok) break; } return true; } } static class CidrInetRange extends InetPattern { final byte[] _raw; final int _octets; final int _mask; final int _masked; public CidrInetRange(String pattern, InetAddress address, int cidr) { super(pattern); _raw = address.getAddress(); _octets = cidr/8; _mask = 0xff&(0xff<<(8-cidr%8)); _masked = _mask==0?0:_raw[_octets]&_mask; if (cidr>(_raw.length*8)) throw new IllegalArgumentException("CIDR too large: "+pattern); if (_mask!=0 && _raw[_octets]!=_masked) throw new IllegalArgumentException("CIDR bits non zero: "+pattern); for (int o=_octets+(_mask==0?0:1);o<_raw.length;o++) if (_raw[o]!=0) throw new IllegalArgumentException("CIDR bits non zero: "+pattern); } public boolean test(InetAddress item, byte[] raw) { if (raw.length!=_raw.length) return false; for (int o=0;o<_octets;o++) if (_raw[o]!=raw[o]) return false; if (_mask!=0 && (raw[_octets]&_mask)!=_masked) return false; return true; } } static class LegacyInetRange extends InetPattern { int[] _min = new int[4]; int[] _max = new int[4]; public LegacyInetRange(String pattern) { super(pattern); String[] parts = pattern.split("\\."); if (parts.length!=4) throw new IllegalArgumentException("Bad legacy pattern: "+pattern); for (int i=0;i<4;i++) { String part=parts[i].trim(); int dash = part.indexOf('-'); if (dash<0) _min[i]=_max[i]=Integer.parseInt(part); else { _min[i] = (dash==0)?0:StringUtil.toInt(part,0); _max[i] = (dash==part.length()-1)?255:StringUtil.toInt(part,dash+1); } if (_min[i]<0 || _min[i]>_max[i] || _max[i]>255) throw new IllegalArgumentException("Bad legacy pattern: "+pattern); } } public boolean test(InetAddress item, byte[] raw) { if (raw.length!=4) return false; for (int i=0;i<4;i++) if ((0xff&raw[i])<_min[i] || (0xff&raw[i])>_max[i]) return false; return true; } } }