package se.sics.asdistances;
import java.io.*;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.*;
/**
* A serializable class for storage of distances between transit ASes, with
* methods for calculating the distance in hops between any two ASes.
*
* @author Niklas Wahlén <nwahlen@kth.se>
* @see DistanceCalculator
*/
public class ASDistances implements Serializable {
private static final long serialVersionUID = 7656264260256297456L;
private Map<Integer, int[]> stubASProviders;
private byte[] transitASDistances;
private Map<Integer, Integer> transitASToInternalIndex;
private Map<InternalPrefix, Integer> ipPrefixToAS;
private static ASDistances instance;
public static final String DEFAULT_DATA_PATH = "data/";
public static final String DEFAULT_AS_DISTANCES_FILE = "ASDistances.gz";
public ASDistances() {
}
public void serialize() throws IOException {
serialize(DEFAULT_DATA_PATH + DEFAULT_AS_DISTANCES_FILE);
}
public void serialize(String distancesFilePath) throws IOException {
File distancesFile = new File(distancesFilePath);
if (!distancesFile.exists()) {
distancesFile.createNewFile();
}
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(distancesFile));
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(this);
oos.flush();
oos.close();
out.close();
}
private static ASDistances load() throws IOException {
return load(DEFAULT_DATA_PATH + DEFAULT_AS_DISTANCES_FILE);
}
private static ASDistances load(String distancesFile) throws IOException {
GZIPInputStream in = new GZIPInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream(distancesFile));
ObjectInputStream ois = new ObjectInputStream(in);
ASDistances asd = null;
try {
asd = (ASDistances) ois.readObject();
} catch (ClassNotFoundException ex) {
Logger.getLogger(ASDistances.class.getName()).log(Level.SEVERE, null, ex);
}
ois.close();
in.close();
return asd;
}
public static ASDistances getInstance() {
if (instance == null) {
try {
instance = load();
} catch (IOException ex) {
Logger.getLogger(ASDistances.class.getName()).log(Level.SEVERE, null, ex);
}
}
return instance;
}
/**
* Calculates the AS hop count between two IP addresses. The order of the IP
* addresses does not matter.
*
* @param ip1
* @param ip2
* @return The AS hop count between the IP addresses. This method returns
* <code>Byte.MAX_VALUE</code> if: at least on of the IP addresses misses an
* AS mapping, or there is no connection between the ASes holding the IP
* addresses.
*/
public byte getDistance(String ip1, String ip2) {
return getDistance(PrefixHandler.prefixToInteger(ip1), PrefixHandler.prefixToInteger(ip2));
}
public byte getDistance(int ip1, int ip2) {
int as1 = getASFromIP(ip1);
int as2 = getASFromIP(ip2);
// If there is not matching prefix/AS for this IP (indicated by
// getASFromIP returning -1), return Byte.MAX_VALUE
if (as1 < 0 || as2 < 0) {
return Byte.MAX_VALUE;
}
// If the IP addresses belong to the same AS return 0
if (as1 == as2) {
return 0;
}
// Case: both ASes are transit ASes
if (transitASToInternalIndex.containsKey(as1) && transitASToInternalIndex.containsKey(as2)) {
return getDistanceBetweenTransitASes(as1, as2);
} // Case: as1 is a stub AS
else if (!transitASToInternalIndex.containsKey(as1) && transitASToInternalIndex.containsKey(as2)) {
return getDistanceFromStubASToTransitAS(as1, as2);
} // Case: as2 is a stub AS
else if (transitASToInternalIndex.containsKey(as1) && !transitASToInternalIndex.containsKey(as2)) {
return getDistanceFromStubASToTransitAS(as2, as1);
} // Case: both ASes are stub ASes
else if (!transitASToInternalIndex.containsKey(as1) && !transitASToInternalIndex.containsKey(as2)) {
return getDistanceBetweenStubASes(as1, as2);
}
// This segment should never be reached
return Byte.MAX_VALUE;
}
private byte getDistanceBetweenTransitASes(int transitAS1, int transitAS2) {
int i1 = transitASToInternalIndex.get(transitAS1);
int i2 = transitASToInternalIndex.get(transitAS2);
if (i1 > i2) {
return transitASDistances[(i1 * (i1 + 1) / 2) + i2];
} else {
return transitASDistances[(i2 * (i2 + 1) / 2) + i1];
}
}
/*
* Note that the order is important! (The method could be modified so that
* it isn't by checking in which Map each AS is present, but since this is
* an internal method it doesn't seem necessary).
*/
private byte getDistanceFromStubASToTransitAS(int stubAS, int transitAS) {
int[] fromStubASProviders = stubASProviders.get(stubAS);
if (fromStubASProviders == null) {
return Byte.MAX_VALUE;
}
byte transitDistance = Byte.MAX_VALUE;
for (int i = 0; i < fromStubASProviders.length; i++) {
byte d = getDistanceBetweenTransitASes(fromStubASProviders[i], transitAS);
if (d < transitDistance) {
transitDistance = d;
}
}
return (byte) (transitDistance + 1);
}
private byte getDistanceBetweenStubASes(int fromStubAS, int toStubAS) {
int[] fromStubASProviders = stubASProviders.get(fromStubAS);
int[] toStubASProviders = stubASProviders.get(toStubAS);
if (fromStubASProviders == null || toStubASProviders == null) {
return Byte.MAX_VALUE;
}
byte transitDistance = Byte.MAX_VALUE;
// Find the transit AS pair closest to eachother among the stub ASes providers
for (int i = 0; i < fromStubASProviders.length; i++) {
for (int j = 0; j < toStubASProviders.length; j++) {
byte d = getDistanceBetweenTransitASes(fromStubASProviders[i], toStubASProviders[j]);
if (d < transitDistance) {
transitDistance = d;
}
}
}
return (byte) (transitDistance + 2);
}
/**
* Finds the AS which owns an IP address.
*
* @param ip
* @return The AS which owns the specified IP or -1 if no owner is found.
*/
public int getASFromIP(String ip) {
return getASFromIP(PrefixHandler.prefixToInteger(ip));
}
public int getASFromIP(int ip) {
for (byte prefixLength = 32; prefixLength > 0; prefixLength--) {
Integer asn = ipPrefixToAS.get(new InternalPrefix(ip, prefixLength));
if (asn != null) {
return asn;
}
}
return -1;
}
/*
* -- Setters and getters --
*
*/
public byte[] getTransitASDistances() {
return transitASDistances;
}
/**
* This method reads a full
* <code>NxN</code> distance matrix and stores it as an array of only half
* the size (since the path between AS A and AS B is bidirectional, it is
* possible to remove half of the cells without losing information if done
* right).
*
* @param transitASDistancesFull
*/
public void setTransitASDistances(byte[][] transitASDistancesFull) {
int transits = transitASDistancesFull.length;
System.out.println("ASDistances#setTransitASDistances() - Minimizing the distance structure");
transitASDistances = new byte[transits * (transits + 1) / 2];
for (Integer i1 : transitASToInternalIndex.values()) {
for (Integer i2 : transitASToInternalIndex.values()) {
if (i1 > i2) {
transitASDistances[(i1 * (i1 + 1) / 2) + i2] = transitASDistancesFull[i1][i2];
} else {
transitASDistances[(i2 * (i2 + 1) / 2) + i1] = transitASDistancesFull[i1][i2];
}
}
}
System.out.println("ASDistances#setTransitASDistances() - Verifying the minimizied distance structure");
if (transitASDistancesVerification(transitASDistancesFull, transitASDistances)) {
System.out.println("ASDistances#setTransitASDistances() - Verification OK");
} else {
System.out.println("ASDistances#setTransitASDistances() - Failed verification");
}
}
public void setTransitASDistances(byte[] transitASDistances) {
this.transitASDistances = transitASDistances;
}
public Map<Integer, Integer> getTransitASToInternalIndex() {
return transitASToInternalIndex;
}
public void setTransitASToInternalIndex(Map<Integer, Integer> transitASToInternalIndex) {
this.transitASToInternalIndex = transitASToInternalIndex;
}
public Map<Integer, int[]> getStubASProviders() {
return stubASProviders;
}
public void setStubAProviders(Map<Integer, int[]> stubASProviders) {
this.stubASProviders = stubASProviders;
}
public Map<InternalPrefix, Integer> getIpPrefixToAS() {
return ipPrefixToAS;
}
public void setIpPrefixToAS(Map<InternalPrefix, Integer> ipPrefixToAS) {
this.ipPrefixToAS = ipPrefixToAS;
}
/*
* -- Other --
*
*/
private boolean transitASDistancesVerification(byte[][] distancesFull, byte[] distances) {
boolean verified = true;
for (Integer i1 : transitASToInternalIndex.values()) {
for (Integer i2 : transitASToInternalIndex.values()) {
int distanceHalf;
if (i1 > i2) {
distanceHalf = distances[(i1 * (i1 + 1) / 2) + i2];
} else {
distanceHalf = distances[(i2 * (i2 + 1) / 2) + i1];
}
if (distancesFull[i1][i2] != distanceHalf) {
System.out.println("ASDistances::transitASDistancesVerification() - ! (" + i1 + "," + i2 + "): " + distancesFull[i1][i2] + " != " + distanceHalf);
verified = false;
}
}
}
return verified;
}
}