package com.subgraph.orchid.circuits.path; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import com.subgraph.orchid.ConsensusDocument; import com.subgraph.orchid.Directory; import com.subgraph.orchid.Router; import com.subgraph.orchid.TorConfig; import com.subgraph.orchid.crypto.TorRandom; public class CircuitNodeChooser { private final static Logger logger = Logger.getLogger(CircuitNodeChooser.class.getName()); public enum WeightRule { WEIGHT_FOR_DIR, WEIGHT_FOR_EXIT, WEIGHT_FOR_MID, WEIGHT_FOR_GUARD, NO_WEIGHTING}; private final Directory directory; private final TorRandom random = new TorRandom(); private final TorConfigNodeFilter configNodeFilter; public CircuitNodeChooser(TorConfig config, Directory directory) { this.directory = directory; this.configNodeFilter = new TorConfigNodeFilter(config); } /** * * @param candidates * @return The chosen exit router or 'null' if no suitable router is available */ public Router chooseExitNode(List<Router> candidates) { final List<Router> filteredCandidates = configNodeFilter.filterExitCandidates(candidates); return chooseByBandwidth(filteredCandidates, WeightRule.WEIGHT_FOR_EXIT); } public Router chooseDirectory() { final RouterFilter filter = new RouterFilter() { public boolean filter(Router router) { return router.getDirectoryPort() != 0; } }; final List<Router> candidates = getFilteredRouters(filter, false); final Router choice = chooseByBandwidth(candidates, WeightRule.WEIGHT_FOR_DIR); if(choice == null) { return directory.getRandomDirectoryAuthority(); } else { return choice; } } /** * * @param rule * @param routerFilter * @return The chosen router or 'null' if no suitable router is available. */ public Router chooseRandomNode(WeightRule rule, RouterFilter routerFilter) { final List<Router> candidates = getFilteredRouters(routerFilter, true); final Router choice = chooseByBandwidth(candidates, rule); if(choice == null) { // try again with more permissive flags return null; } return choice; } private List<Router> getFilteredRouters(RouterFilter rf, boolean needDescriptor) { final List<Router> routers = new ArrayList<Router>(); for(Router r: getUsableRouters(needDescriptor)) { if(rf.filter(r)) { routers.add(r); } } return routers; } List<Router> getUsableRouters(boolean needDescriptor) { final List<Router> routers = new ArrayList<Router>(); for(Router r: directory.getAllRouters()) { if(r.isRunning() && r.isValid() && !r.isHibernating() && !(needDescriptor && r.getCurrentDescriptor() == null)) { routers.add(r); } } return routers; } private Router chooseByBandwidth(List<Router> candidates, WeightRule rule) { final Router choice = chooseNodeByBandwidthWeights(candidates, rule); if(choice != null) { return choice; } else { return chooseNodeByBandwidth(candidates, rule); } } private Router chooseNodeByBandwidthWeights(List<Router> candidates, WeightRule rule) { final ConsensusDocument consensus = directory.getCurrentConsensusDocument(); if(consensus == null) { return null; } final BandwidthWeightedRouters bwr = computeWeightedBandwidths(candidates, consensus, rule); return bwr.chooseRandomRouterByWeight(); } private BandwidthWeightedRouters computeWeightedBandwidths(List<Router> candidates, ConsensusDocument consensus, WeightRule rule) { final CircuitNodeChooserWeightParameters wp = CircuitNodeChooserWeightParameters.create(consensus, rule); if(!wp.isValid()) { logger.warning("Got invalid bandwidth weights. Falling back to old selection method"); return null; } final BandwidthWeightedRouters weightedRouters = new BandwidthWeightedRouters(); for(Router r: candidates) { double wbw = wp.calculateWeightedBandwidth(r); weightedRouters.addRouter(r, wbw); } return weightedRouters; } private Router chooseNodeByBandwidth(List<Router> routers, WeightRule rule) { final BandwidthWeightedRouters bwr = new BandwidthWeightedRouters(); for(Router r: routers) { long bw = getRouterBandwidthBytes(r); if(bw == -1) { bwr.addRouterUnknown(r); } else { bwr.addRouter(r, bw); } } bwr.fixUnknownValues(); if(bwr.isTotalBandwidthZero()) { if(routers.size() == 0) { return null; } final int idx = random.nextInt(routers.size()); return routers.get(idx); } computeFinalWeights(bwr, rule); return bwr.chooseRandomRouterByWeight(); } private final static double EPSILON = 0.1; private void computeFinalWeights(BandwidthWeightedRouters bwr, WeightRule rule) { final double exitWeight = calculateWeight(rule == WeightRule.WEIGHT_FOR_EXIT, bwr.getTotalExitBandwidth(), bwr.getTotalBandwidth()); final double guardWeight = calculateWeight(rule == WeightRule.WEIGHT_FOR_GUARD, bwr.getTotalGuardBandwidth(), bwr.getTotalBandwidth()); bwr.adjustWeights(exitWeight, guardWeight); } private double calculateWeight(boolean matchesRule, double totalByType, double total) { if(matchesRule || totalByType < EPSILON) { return 1.0; } final double result = 1.0 - (total / (3.0 * totalByType)); if(result <= 0.0) { return 0.0; } else { return result; } } private long getRouterBandwidthBytes(Router r) { if(!r.hasBandwidth()) { return -1; } else { return kbToBytes(r.getEstimatedBandwidth()); } } private long kbToBytes(long bw) { return (bw > (Long.MAX_VALUE / 1000) ? Long.MAX_VALUE : bw * 1000); } }