package com.subgraph.orchid.circuits.path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import com.subgraph.orchid.Directory; import com.subgraph.orchid.Router; import com.subgraph.orchid.TorConfig; import com.subgraph.orchid.circuits.guards.EntryGuards; import com.subgraph.orchid.circuits.path.CircuitNodeChooser.WeightRule; import com.subgraph.orchid.data.IPv4Address; import com.subgraph.orchid.data.exitpolicy.ExitTarget; public class CircuitPathChooser { public static CircuitPathChooser create(TorConfig config, Directory directory) { return new CircuitPathChooser(config, directory, new CircuitNodeChooser(config, directory)); } private final Directory directory; private final CircuitNodeChooser nodeChooser; private EntryGuards entryGuards; private boolean useEntryGuards; CircuitPathChooser(TorConfig config, Directory directory, CircuitNodeChooser nodeChooser) { this.directory = directory; this.nodeChooser = nodeChooser; this.entryGuards = null; this.useEntryGuards = false; } public void enableEntryGuards(EntryGuards entryGuards) { this.entryGuards = entryGuards; this.useEntryGuards = true; } public List<Router> chooseDirectoryPath() throws InterruptedException { if(useEntryGuards && entryGuards.isUsingBridges()) { final Set<Router> empty = Collections.emptySet(); final Router bridge = entryGuards.chooseRandomGuard(empty); if(bridge == null) { throw new IllegalStateException("Failed to choose bridge for directory request"); } return Arrays.asList(bridge); } final Router dir = nodeChooser.chooseDirectory(); return Arrays.asList(dir); } public List<Router> chooseInternalPath() throws InterruptedException, PathSelectionFailedException { final Set<Router> excluded = Collections.emptySet(); final Router finalRouter = chooseMiddleNode(excluded); return choosePathWithFinal(finalRouter); } public List<Router> choosePathWithExit(Router exitRouter) throws InterruptedException, PathSelectionFailedException { return choosePathWithFinal(exitRouter); } public List<Router> choosePathWithFinal(Router finalRouter) throws InterruptedException, PathSelectionFailedException { final Set<Router> excluded = new HashSet<Router>(); excludeChosenRouterAndRelated(finalRouter, excluded); final Router middleRouter = chooseMiddleNode(excluded); if(middleRouter == null) { throw new PathSelectionFailedException("Failed to select suitable middle node"); } excludeChosenRouterAndRelated(middleRouter, excluded); final Router entryRouter = chooseEntryNode(excluded); if(entryRouter == null) { throw new PathSelectionFailedException("Failed to select suitable entry node"); } return Arrays.asList(entryRouter, middleRouter, finalRouter); } public Router chooseEntryNode(final Set<Router> excludedRouters) throws InterruptedException { if(useEntryGuards) { return entryGuards.chooseRandomGuard(excludedRouters); } return nodeChooser.chooseRandomNode(WeightRule.WEIGHT_FOR_GUARD, new RouterFilter() { public boolean filter(Router router) { return router.isPossibleGuard() && !excludedRouters.contains(router); } }); } Router chooseMiddleNode(final Set<Router> excludedRouters) { return nodeChooser.chooseRandomNode(WeightRule.WEIGHT_FOR_MID, new RouterFilter() { public boolean filter(Router router) { return router.isFast() && !excludedRouters.contains(router); } }); } public Router chooseExitNodeForTargets(List<ExitTarget> targets) { final List<Router> routers = filterForExitTargets( getUsableExitRouters(), targets); return nodeChooser.chooseExitNode(routers); } private List<Router> getUsableExitRouters() { final List<Router> result = new ArrayList<Router>(); for(Router r: nodeChooser.getUsableRouters(true)) { if(r.isExit() && !r.isBadExit()) { result.add(r); } } return result; } private void excludeChosenRouterAndRelated(Router router, Set<Router> excludedRouters) { excludedRouters.add(router); for(Router r: directory.getAllRouters()) { if(areInSameSlash16(router, r)) { excludedRouters.add(r); } } for(String s: router.getFamilyMembers()) { Router r = directory.getRouterByName(s); if(r != null) { // Is mutual? if(isFamilyMember(r.getFamilyMembers(), router)) { excludedRouters.add(r); } } } } private boolean isFamilyMember(Collection<String> familyMemberNames, Router r) { for(String s: familyMemberNames) { Router member = directory.getRouterByName(s); if(member != null && member.equals(r)) { return true; } } return false; } // Are routers r1 and r2 in the same /16 network private boolean areInSameSlash16(Router r1, Router r2) { final IPv4Address a1 = r1.getAddress(); final IPv4Address a2 = r2.getAddress(); final int mask = 0xFFFF0000; return (a1.getAddressData() & mask) == (a2.getAddressData() & mask); } private List<Router> filterForExitTargets(List<Router> routers, List<ExitTarget> exitTargets) { int bestSupport = 0; if(exitTargets.isEmpty()) { return routers; } final int[] nSupport = new int[routers.size()]; for(int i = 0; i < routers.size(); i++) { final Router r = routers.get(i); nSupport[i] = countTargetSupport(r, exitTargets); if(nSupport[i] > bestSupport) { bestSupport = nSupport[i]; } } if(bestSupport == 0) { return routers; } final List<Router> results = new ArrayList<Router>(); for(int i = 0; i < routers.size(); i++) { if(nSupport[i] == bestSupport) { results.add(routers.get(i)); } } return results; } private int countTargetSupport(Router router, List<ExitTarget> targets) { int count = 0; for(ExitTarget t: targets) { if(routerSupportsTarget(router, t)) { count += 1; } } return count; } private boolean routerSupportsTarget(Router router, ExitTarget target) { if(target.isAddressTarget()) { return router.exitPolicyAccepts(target.getAddress(), target.getPort()); } else { return router.exitPolicyAccepts(target.getPort()); } } }