/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.brooklyn.util.net;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.math.BitList;
import org.apache.brooklyn.util.math.BitUtils;
import org.apache.brooklyn.util.text.Strings;
import com.google.common.collect.ImmutableList;
/** represents a CIDR (classless inter-domain routing) token, i.e. 10.0.0.0/8 or 192.168.4.0/24 */
public class Cidr implements Serializable {
private static final long serialVersionUID = -4605909101590811958L;
/** 0.0.0.0/0 -- matches all addresses */
public static final Cidr UNIVERSAL = new Cidr();
public static final Cidr _10 = new Cidr(10);
public static final Cidr _172_16 = new Cidr("172.16.0.0/12");
public static final Cidr _192_168 = new Cidr(192, 168);
public static final Cidr CLASS_A = _10;
public static final Cidr CLASS_B = _172_16;
public static final Cidr CLASS_C = _192_168;
public static final List<Cidr> PRIVATE_NETWORKS_RFC_1918 = ImmutableList.<Cidr>of(_192_168, _172_16, _10);
public static final Cidr _169_254 = new Cidr("169.254.0.0/16");
public static final Cidr LINK_LOCAL = _169_254;
public static final Cidr _127 = new Cidr("127.0.0.0/8");
public static final Cidr LOOPBACK = _127;
public static final List<Cidr> NON_PUBLIC_CIDRS = ImmutableList.<Cidr>builder().addAll(PRIVATE_NETWORKS_RFC_1918).add(LINK_LOCAL).add(LOOPBACK).build();
final int[] subnetBytes = new int[] { 0, 0, 0, 0 };
final int length;
public Cidr(String cidr) {
if (Strings.isBlank(cidr))
// useful e.g. if user leaves it blank in gui
cidr = "0.0.0.0/0";
int slash = cidr.indexOf('/');
if (slash==-1) throw new IllegalArgumentException("CIDR should be of form 192.168.0.0/16 (missing slash); input="+cidr);
String subnet = cidr.substring(0, slash);
String lengthS = cidr.substring(slash+1);
this.length = Integer.parseInt(lengthS);
String[] bytes = subnet.split("\\.");
int i=0;
for (; i<this.length/8; i++)
subnetBytes[i] = Integer.parseInt(bytes[i]);
for (; i<(this.length+7)/8; i++)
// for fractional parts: reverse significance, trim, reverse back
subnetBytes[i] =
BitUtils.unsigned( BitUtils.reverseBitSignificanceInByte(
BitList.newInstanceFromBytes(BitUtils.reverseBitSignificanceInByte(Integer.parseInt(bytes[i]))).
resized(this.length % 8).intValue() ));
}
/** returns true iff this CIDR is well-formed and canonical,
* i.e. 4 dot-separated bytes followed by a slash and a length,
* where length is <= 32, and the preceding 4 bytes don't include any 1 bits beyond the indicated length;
* e.g. 10.0.0.0/8 -- but not 10.0.0.1/8 or 10.../8
* (although the latter ones are accepted by the constructor and converted to the canonical CIDR) */
public static boolean isCanonical(String cidr) {
try {
return cidr.equals(new Cidr(cidr).toString());
} catch (Throwable e) {
Exceptions.propagateIfFatal(e);
return false;
}
}
/** allows creation as Cidr(192, 168) for 192.168.0.0/16;
* zero bits or ints included are significant, i.e. Cidr(10, 0) gives 10.0.0.0/16 */
public Cidr(int ...unsignedBytes) {
length = unsignedBytes.length*8;
System.arraycopy(unsignedBytes, 0, subnetBytes, 0, unsignedBytes.length);
}
public Cidr(int[] subnetBytes, int length) {
this.length = length;
if (subnetBytes.length>4)
throw new IllegalArgumentException("Cannot create CIDR beyond 4 bytes: "+Arrays.toString(subnetBytes));
if (length>32)
throw new IllegalArgumentException("Cannot create CIDR beyond 4 bytes: length "+length);
// reverse the bits to remove zeroed bits, then reverse back
byte[] significantSubnetBytes = BitList.newInstance(BitUtils.reverseBitSignificanceInBytes(subnetBytes)).resized(length).asBytes();
for (int i=0; i<significantSubnetBytes.length; i++)
this.subnetBytes[i] = BitUtils.unsigned(BitUtils.reverseBitSignificance(significantSubnetBytes[i]));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + length;
result = prime * result + Arrays.hashCode(subnetBytes);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Cidr other = (Cidr) obj;
if (length != other.length)
return false;
if (!Arrays.equals(subnetBytes, other.subnetBytes))
return false;
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int i=0;
while (i<(length+7)/8) {
if (sb.length()>0) sb.append(".");
sb.append(""+subnetBytes[i]);
i++;
}
while (i<4) {
if (sb.length()>0) sb.append(".");
sb.append("0");
i++;
}
sb.append("/");
sb.append(""+length);
return sb.toString();
}
public int[] getBytes() {
return Arrays.copyOf(subnetBytes, 4);
}
public int getLength() {
return length;
}
public Cidr subnet(int ...extraUnsignedBytes) {
if ((length%8)!=0) throw new IllegalStateException("subnet can only be used for byte boundary subnetted CIDR's; not "+this);
int[] newBytes = getBytes();
int newLen = this.length + extraUnsignedBytes.length*8;
if (newLen>32) throw new IllegalStateException("further subnet for "+Arrays.toString(extraUnsignedBytes)+" not possible on CIDR "+this);
for (int i=0; i<extraUnsignedBytes.length; i++) {
newBytes[this.length/8 + i] = extraUnsignedBytes[i];
}
return new Cidr(newBytes, newLen);
}
/** returns the netmask for this cidr; e.g. for a /24 cidr returns 255.255.255.0 */
public InetAddress netmask() {
final byte[] netmaskBytes = new byte[] { 0, 0, 0, 0 };
int lengthLeft = length;
int i=0;
while (lengthLeft>0) {
if (lengthLeft>=8) {
netmaskBytes[i] = (byte)255;
} else {
netmaskBytes[i] = (byte) ( (1 << lengthLeft) - 1 );
}
lengthLeft -= 8;
i++;
}
return Networking.getInetAddressWithFixedName(netmaskBytes);
}
/** taking the addresses in the CIDR in order, returns the one in the offset^th position
* (starting with the CIDR itself, even if final bits are 0) */
public InetAddress addressAtOffset(int offset) {
int[] ints = getBytes();
ints[3] += offset;
{
int i=3;
while (ints[i]>=256) {
ints[i-1] += (ints[i] / 256);
ints[i] %= 256;
i--;
}
}
byte[] bytes = new byte[] { 0, 0, 0, 0 };
for (int i=0; i<4; i++)
bytes[i] = (byte) ints[i];
return Networking.getInetAddressWithFixedName(bytes);
}
/** returns length of the prefix in common between the two cidrs */
public int commonPrefixLength(Cidr other) {
return asBitList().commonPrefixLength(other.asBitList());
}
public Cidr commonPrefix(Cidr other) {
return new Cidr(other.getBytes(), commonPrefixLength(other));
}
/** returns list of bits for the significant (length) bits of this CIDR */
public BitList asBitList() {
return BitList.newInstance(BitUtils.reverseBitSignificanceInBytes(getBytes())).resized(getLength());
}
public boolean contains(Cidr target) {
return commonPrefixLength(target) == getLength();
}
// FIXME remove from here, promote NetworkUtils
/** @deprecated use {@link Networking#getInetAddressWithFixedName(byte[])} */
@Deprecated
public static InetAddress getInetAddressWithFixedName(byte[] ip) {
return Networking.getInetAddressWithFixedName(ip);
}
}