/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.node; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import freenet.io.comm.FreenetInetAddress; import freenet.io.comm.Peer; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.transport.ip.IPUtil; /** * Combine the detected IP address with the NodeCrypto's port number and the port numbers we have * on connections in the given class to get a list of Peer's. * @author toad */ public class NodeIPPortDetector { /** The Node object */ final Node node; /** The NodeIPDetector which determines the node's IP address but not its port number */ final NodeIPDetector ipDetector; /** The NodeCrypto with the node's port number and list of peers */ final NodeCrypto crypto; /** ARK inserter. */ private final NodeARKInserter arkPutter; /** Last detected IP address */ Peer[] lastPeers; private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } NodeIPPortDetector(Node node, NodeIPDetector ipDetector, NodeCrypto crypto, boolean enableARKs) { this.node = node; this.ipDetector = ipDetector; this.crypto = crypto; arkPutter = new NodeARKInserter(node, crypto, this, enableARKs); ipDetector.addPortDetector(this); } /** * Combine the NodeIPDetector's output with any per-port information we may have to get a definitive * (for that port/NodeCrypto) list of IP addresses (still without port numbers). */ FreenetInetAddress[] detectPrimaryIPAddress() { FreenetInetAddress addr = crypto.getBindTo(); if(addr.isRealInternetAddress(false, true, false)) { // Binding to a real internet address => don't want us to use the others, most likely // he is on a multi-homed box where only one IP can be used for Freenet. return new FreenetInetAddress[] { addr }; } return ipDetector.detectPrimaryIPAddress(!crypto.config.includeLocalAddressesInNoderefs); } /** * Get our Peer's. This is a list of IP:port's at which we might be contactable. Some of them * will have the same port as the listenPort, but if we are behind a NAT which rewrites our * port number, some of them may not. (If we're behind a symmetric NAT which rewrites it * differently for each connection, we're stuffed, and we tell the user). */ Peer[] detectPrimaryPeers() { final boolean logMINOR = NodeIPPortDetector.logMINOR; ArrayList<Peer> addresses = new ArrayList<Peer>(); FreenetInetAddress[] addrs = detectPrimaryIPAddress(); for(FreenetInetAddress addr: addrs) { addresses.add(new Peer(addr, crypto.portNumber)); if(logMINOR) Logger.minor(this, "Adding "+addr); } // Now try to get the rewritten port number from our peers. // Only considering those within this crypto port, this time. PeerNode[] peerList = crypto.getPeerNodes(); if(peerList != null) { HashMap<Peer,Integer> countsByPeer = new HashMap<Peer,Integer>(); // FIXME use a standard mutable int object, we have one somewhere for(PeerNode pn: peerList) { Peer p = pn.getRemoteDetectedPeer(); if((p == null) || p.isNull()) continue; // DNSRequester doesn't deal with our own node if(!IPUtil.isValidAddress(p.getAddress(true), false)) continue; if(logMINOR) Logger.minor(this, "Peer "+pn.getPeer()+" thinks we are "+p); if(countsByPeer.containsKey(p)) { countsByPeer.put(p, countsByPeer.get(p) + 1); } else { countsByPeer.put(p, 1); } } if(countsByPeer.size() == 1) { Iterator<Peer> it = countsByPeer.keySet().iterator(); Peer p = (it.next()); Logger.minor(this, "Everyone agrees we are "+p); if(!addresses.contains(p)) { addresses.add(p); } } else if(countsByPeer.size() > 1) { // Take two most popular addresses. Peer best = null; Peer secondBest = null; int bestPopularity = 0; int secondBestPopularity = 0; for (Map.Entry<Peer,Integer> entry : countsByPeer.entrySet()) { Peer cur = entry.getKey(); int curPop = entry.getValue(); Logger.normal(this, "Detected peer: "+cur+" popularity "+curPop); if(curPop >= bestPopularity) { secondBestPopularity = bestPopularity; bestPopularity = curPop; secondBest = best; best = cur; } } if(best != null) { if((bestPopularity > 1) || (addrs.length == 0)) { if(!addresses.contains(best)) { Logger.normal(this, "Adding best peer "+best+" ("+bestPopularity+ ')'); addresses.add(best); } if((secondBest != null) && (secondBestPopularity > 1)) { if(!addresses.contains(secondBest)) { Logger.normal(this, "Adding second best peer "+secondBest+" ("+secondBest+ ')'); addresses.add(secondBest); } if(best.getAddress().equals(secondBest.getAddress()) && bestPopularity == 1) { Logger.error(this, "Hrrrm, maybe this is a symmetric NAT? Expect trouble connecting!"); System.err.println("Hrrrm, maybe this is a symmetric NAT? Expect trouble connecting!"); ipDetector.setMaybeSymmetric(); Peer p = new Peer(best.getFreenetAddress(), crypto.portNumber); if(!addresses.contains(p)) addresses.add(p); } } } } } } lastPeers = addresses.toArray(new Peer[addresses.size()]); if(logMINOR) Logger.minor(this, "Returning for port "+crypto.portNumber+" : "+Arrays.toString(lastPeers)); return lastPeers; } void update() { arkPutter.update(); } void startARK() { arkPutter.start(); } public Peer[] getPrimaryPeers() { if(lastPeers == null) return detectPrimaryPeers(); else return lastPeers; } public boolean includes(FreenetInetAddress addr) { FreenetInetAddress[] a = detectPrimaryIPAddress(); for(FreenetInetAddress ai: a) if(ai.equals(addr)) return true; return false; } }