package edu.washington.cs.oneswarm.f2f.dht; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.SHA1Simple; import org.gudy.azureus2.plugins.ddb.DistributedDatabase; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseContact; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseEvent; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseException; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseKey; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseListener; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseTransferHandler; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseTransferType; import org.gudy.azureus2.plugins.ddb.DistributedDatabaseValue; import org.gudy.azureus2.pluginsimpl.local.ddb.DDBaseImpl; import com.aelitis.azureus.core.dht.impl.DHTLog; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.instancemanager.AZInstance; import com.aelitis.azureus.core.instancemanager.AZInstanceManagerListener; import com.aelitis.azureus.core.instancemanager.AZInstanceTracked; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.impl.osssl.OneSwarmSslKeyManager; import edu.washington.cs.oneswarm.f2f.Friend; import edu.washington.cs.oneswarm.f2f.FriendInvitation; import edu.washington.cs.oneswarm.f2f.FriendInvitation.Status; import edu.washington.cs.oneswarm.f2f.OSF2FMain; import edu.washington.cs.oneswarm.f2f.friends.FriendManager; import edu.washington.cs.oneswarm.f2f.friends.LanFriendFinder; import edu.washington.cs.oneswarm.f2f.invitations.InvitationManager; import edu.washington.cs.oneswarm.f2f.network.OverlayManager; import edu.washington.cs.oneswarm.ui.gwt.server.community.CommunityServerManager; public class DHTConnector { // run the connect task every 30s, most of the time it will not do // anything private static final int CONNECTOR_FREQUENCY = 30 * 1000; private static final int ENCRYPTED_LENGTH = 128; private static Logger logger = Logger.getLogger(DHTConnector.class.getName()); // don't try to connect more than once every 15 minutes, // our friends will hopefully try to connect to us if they get online private final static long MIN_CONNECT_TIME_DIFF = 15 * 60 * 1000; private final static long REPUBLISH_TIME = 3600 * 1000; private static final int SIGN_LENGTH = 128; private final static String USE_CHT_PROXY_SETTINGS_KEY = "OSF2F.Use DHT Proxy"; private CHTClientUDP chtClientUDP; private long chtLastPublishTime; private long dhtLastPublishTime; private InetAddress externalIp = null; /* * keep track of this, when the system recovers from standby (time jumpes) * we want to force connection attempts */ private long lastConnectorRunTime = System.currentTimeMillis(); private InetAddress lastPublishedIP; private int lastPublishedPort; // private final FriendManager friendManager; private final OverlayManager overlayManager; private final InvitationManager invitationManager; private byte[] ownPublicKey = null; private int tcpListeningPort; private final HashMap<HashWrapper, DistributedDatabaseKey> dhtKeyCache = new HashMap<HashWrapper, DistributedDatabaseKey>(); private long queuedDHTReadRequests = 0; private long completedDHTReadRequests = 0; private long timedoutDHTReadRequests = 0; private long queuedDHTWriteRequests = 0; private long completedDHTWriteRequests = 0; private long timedoutDHTWriteRequests = 0; private static final long DHT_TIMEOUT = 60 * 1000; private final static int MAX_DHT_READ_QUEUE_LENGTH = 200; private final static int MAX_DHT_WRITE_QUEUE_LENGTH = 200; public DHTConnector(FriendManager friendManager, InvitationManager invitationManager, OverlayManager _overlayManager) { logger.fine("cht enabled=" + isChtEnabled()); this.overlayManager = _overlayManager; this.invitationManager = invitationManager; PublicKey k = overlayManager.getOwnPublicKey(); if (k != null) { ownPublicKey = k.getEncoded(); } // this.friendManager = friendManager; // this.pendingConnections = new ConcurrentHashMap<Friend, Long>(); final AZInstance myInstance = AzureusCoreImpl.getSingleton().getInstanceManager() .getMyInstance(); logger.finer("before external IP"); try { externalIp = InetAddress.getLocalHost(); } catch (UnknownHostException e1) { e1.printStackTrace(); externalIp = null; } Thread t = (new Thread("Get external IP") { @Override public void run() { try { DHTConnector.this.externalIp = myInstance.getExternalAddress(); logger.finer("got external IP: " + externalIp.getHostAddress()); } catch (Exception e) { e.printStackTrace(); } } }); t.setDaemon(true); t.start(); this.tcpListeningPort = myInstance.getTCPListenPort(); logger.finer("after external IP"); AzureusCoreImpl.getSingleton().getInstanceManager() .addListener(new AZInstanceManagerListener() { @Override public void instanceChanged(AZInstance instance) { AZInstance newInstance = AzureusCoreImpl.getSingleton() .getInstanceManager().getMyInstance(); boolean changed = false; if (!newInstance.getExternalAddress().equals(externalIp) || newInstance.getTCPListenPort() != tcpListeningPort) { changed = true; externalIp = newInstance.getExternalAddress(); tcpListeningPort = newInstance.getTCPListenPort(); publishLocationInfo(); } if (changed) { logger.fine("ip address/port changed, new addr: " + newInstance.getExternalAddress() + ":" + newInstance.getTCPListenPort()); /* * try to connect to friends again and republish in * 1 minute (should give them time to get * disconnected and for things to settle */ Timer t = new Timer("IpChangedFriendReconnector", true); t.schedule(new TimerTask() { @Override public void run() { logger.fine("ip changed, trying to connect to friends"); List<Friend> disconnectedFriends = overlayManager .getDisconnectedFriends(); for (Friend friend : disconnectedFriends) { if (!friend.isBlocked()) { connectToFriend(friend); } } LanFriendFinder lanFriendFinder = OSF2FMain.getSingelton() .getLanFriendFinder(); if (lanFriendFinder != null) { lanFriendFinder.stop(); lanFriendFinder.start(); } } }, 30 * 1000); } } @Override public void instanceFound(AZInstance instance) { } @Override public void instanceLost(AZInstance instance) { } @Override public void instanceTracked(AZInstanceTracked instance) { } }); /* * Create the cht client to assist the dht */ try { this.chtClientUDP = new CHTClientUDP("cht.oneswarm.org", 11744); } catch (UnknownHostException e) { Debug.out("unable to create CHT Client", e); } Timer connectorTimer = new Timer("OS Friend Connector", true); FriendConnectorRunnable r = new FriendConnectorRunnable(); connectorTimer.schedule(r, 0, CONNECTOR_FREQUENCY); Timer inviteConnectorTimer = new Timer("OS Invite Connector", true); InvitationConnectorRunnable ir = new InvitationConnectorRunnable(); inviteConnectorTimer.schedule(ir, 0, CONNECTOR_FREQUENCY); // for dht testing purposes boolean test = false; if (test) { test(); } } private boolean chtPublishAllowed(InetAddress localAddress, int localPort) { if (!isChtEnabled()) { return false; } if (ownPublicKey == null) { PublicKey k = overlayManager.getOwnPublicKey(); if (k != null) { ownPublicKey = k.getEncoded(); if (ownPublicKey == null) { return false; } } else { logger.fine("unknown local public key, " + "can't publish ip:port in cht"); return false; } } else if (lastPublishedIP != null && (!lastPublishedIP.equals(localAddress) || localPort != lastPublishedPort)) { // ip changed, new publish allowed return true; } else if (System.currentTimeMillis() - chtLastPublishTime < REPUBLISH_TIME) { logger.finest("can't publish ip:port in cht: " + "already published within time limit"); return false; } return true; } public int getTcpListenPort() { return tcpListeningPort; } private boolean connectAttemptAllowed(Friend friend) { /* * if we just added the friend, be a bit more aggressive (allow 1 * attempt per minute) */ if (System.currentTimeMillis() - friend.getDateAdded().getTime() < 15 * 60 * 1000) { // allow connect every minute if (friend.msSinceLastAttempt() > 60 * 1000) { return true; } } if (friend.msSinceLastAttempt() < MIN_CONNECT_TIME_DIFF) { logger.finest("connect attempt not allowed, " + "tried: " + (friend.msSinceLastAttempt() / (1000 * 60)) + " minutes ago"); return false; } if (friend.getStatus() != Friend.STATUS_OFFLINE) { logger.finest("connect attempt not allowed, (connecting/connected...)"); return false; } return true; } public void connectToFriend(final Friend friend) { connectToFriend(friend, true); } public void connectToFriend(final Friend friend, boolean allowDht) { final ConcurrentHashMap<String, Boolean> triedIps = new ConcurrentHashMap<String, Boolean>(); if (friend.getStatus() == Friend.STATUS_ONLINE) { return; } if (friend.isBlocked()) { logger.finer("tried to connect to blocked friend: " + friend.getNick()); return; } friend.connectionAttempt("Connection attempt initiated"); /* * first, try to connect to the cached ip if possible */ try { if (friend.getLastConnectIP() != null && !friend.getLastConnectIP().equals(InetAddress.getByName("0.0.0.0")) && friend.getLastConnectPort() != 0) { logger.finer("cache connecting to: " + friend.getNick()); String ipPort = friend.getLastConnectIP().getHostAddress() + ":" + friend.getLastConnectPort(); if (!triedIps.contains(ipPort)) { triedIps.put(ipPort, true); friend.updateConnectionLog(true, "Trying cached location: " + ipPort); overlayManager.createOutgoingConnection( new ConnectionEndpoint(new InetSocketAddress(friend.getLastConnectIP(), friend.getLastConnectPort())), friend); } else { friend.updateConnectionLog(true, "Skipping location: " + ipPort + " (already tried)"); } } } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } /* * connect to the agreed on location */ if (friend.getDhtReadLocation() != null) { byte[] key = friend.getDhtReadLocation(); if (chtClientUDP != null && isChtEnabled()) { chtLookupAndConnect(friend, triedIps, key, "secret loc"); } if (allowDht && getDht().isAvailable() && COConfigurationManager.getBooleanParameter("dht.enabled")) { dhtLookupAndConnect(friend, triedIps, key, "secret loc"); } else { friend.updateConnectionLog(true, "DHT not available"); } } if (!friend.isDhtLocationConfirmed()) { /* * get the location to look at, * * our friend has published his ip:port at "his key" append * "our key" */ byte[] key = new byte[friend.getPublicKey().length + ownPublicKey.length]; System.arraycopy(friend.getPublicKey(), 0, key, 0, friend.getPublicKey().length); System.arraycopy(ownPublicKey, 0, key, friend.getPublicKey().length, ownPublicKey.length); /* * and try the cht (centralized hash table) :-) */ if (chtClientUDP != null && isChtEnabled()) { byte[] keySha1 = new SHA1Simple().calculateHash(key); chtLookupAndConnect(friend, triedIps, keySha1, "pubkey loc"); } /* * and try the dht */ if (allowDht && getDht().isAvailable()) { dhtLookupAndConnect(friend, triedIps, key, "pubkey loc"); } else { friend.updateConnectionLog(true, "DHT not available"); } } } /* * A mock DHT that we use during startup until the real DHT exists. This * simply results in all DHT actions being deferred until the real DHT is * available. */ DistributedDatabase currentlyAvailableDht = new DistributedDatabase() { @Override public boolean isAvailable() { return false; } @Override public boolean isExtendedUseAllowed() { return false; } @Override public DistributedDatabaseContact getLocalContact() { throw new RuntimeException("Unsupported"); } @Override public DistributedDatabaseKey createKey(Object key) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public DistributedDatabaseKey createKey(Object key, String description) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public DistributedDatabaseValue createValue(Object value) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public DistributedDatabaseContact importContact(InetSocketAddress address) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void write(DistributedDatabaseListener listener, DistributedDatabaseKey key, DistributedDatabaseValue value) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void write(DistributedDatabaseListener listener, DistributedDatabaseKey key, DistributedDatabaseValue[] values) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void read(DistributedDatabaseListener listener, DistributedDatabaseKey key, long timeout) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void read(DistributedDatabaseListener listener, DistributedDatabaseKey key, long timeout, int options) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void readKeyStats(DistributedDatabaseListener listener, DistributedDatabaseKey key, long timeout) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void delete(DistributedDatabaseListener listener, DistributedDatabaseKey key) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public void addTransferHandler(DistributedDatabaseTransferType type, DistributedDatabaseTransferHandler handler) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public DistributedDatabaseTransferType getStandardTransferType(int standard_type) throws DistributedDatabaseException { throw new RuntimeException("Unsupported"); } @Override public DistributedDatabaseContact importContact(InetSocketAddress address, byte protocol_version) throws DistributedDatabaseException { // TODO Auto-generated method stub return null; } @Override public DistributedDatabaseContact importContact(InetSocketAddress address, byte protocol_version, int preferred_dht) throws DistributedDatabaseException { // TODO Auto-generated method stub return null; } @Override public void delete(DistributedDatabaseListener listener, DistributedDatabaseKey key, DistributedDatabaseContact[] targets) throws DistributedDatabaseException { // TODO Auto-generated method stub } @Override public void addListener(DistributedDatabaseListener l) { // TODO Auto-generated method stub } @Override public void removeListener(DistributedDatabaseListener l) { // TODO Auto-generated method stub } }; AtomicBoolean dhtGetCalled = new AtomicBoolean(false); /** * Returns the real DHT if it has completed initialization, otherwise a mock * object that always indicates that the DHT is unavailable. This routine * serves to prevent initialization from blocking when the DHT is enabled, * since we will be able to locate many IP-port pairs from our cache, the * CHT, or community servers. */ private DistributedDatabase getDht() { // Only do this once. if (dhtGetCalled.compareAndSet(false, true)) { Thread t = new Thread("DHTConnector DHT acquirer.") { @Override public void run() { currentlyAvailableDht = DDBaseImpl.getSingleton(AzureusCoreImpl.getSingleton()); } }; t.setDaemon(true); t.start(); } return currentlyAvailableDht; } private final ConcurrentHashMap<Friend, Long> lastDhtLookupForFriend = new ConcurrentHashMap<Friend, Long>(); private void dhtLookupAndConnect(final Friend friend, final ConcurrentHashMap<String, Boolean> triedIps, byte[] key, final String locSource) { logger.finer("DHT: connecting to: " + friend.getNick()); if (getOutstandingDhtReadRequests() > MAX_DHT_READ_QUEUE_LENGTH) { logger.finest("Skipping DHT location lookup, dht read queue too long: " + getOutstandingDhtReadRequests()); friend.updateConnectionLog(true, "Skipping DHT location lookup, dht read queue too long: " + getOutstandingDhtReadRequests()); return; } lastDhtLookupForFriend.put(friend, System.currentTimeMillis()); friend.updateConnectionLog(true, "Looking up friend location in DHT(" + locSource + ")"); try { final DistributedDatabaseKey dhtKey = createKey(key); queuedDHTReadRequests++; getDht().read(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { logger.finest("DHT read event:" + event.getType()); if (event.getType() == DistributedDatabaseEvent.ET_VALUE_READ) { DistributedDatabaseValue value = event.getValue(); try { byte[] bytes = (byte[]) value.getValue(byte[].class); decryptAndConnect(triedIps, friend, bytes, "DHT(" + locSource + ")"); } catch (Exception e) { friend.updateConnectionLog(true, "dht value error: " + e.getMessage()); } } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE) { completedDHTReadRequests++; logger.fine("DHT read event completed, queued=" + queuedDHTReadRequests + " completed=" + completedDHTReadRequests + " outstanding=" + getOutstandingDhtReadRequests() + " timeout=" + timedoutDHTReadRequests); } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT) { logger.fine("DHT read event timed out, queued=" + queuedDHTReadRequests + " completed=" + completedDHTReadRequests + " outstanding=" + getOutstandingDhtReadRequests() + " timeout=" + timedoutDHTReadRequests); timedoutDHTReadRequests++; } } }, dhtKey, DHT_TIMEOUT); } catch (DistributedDatabaseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public long getOutstandingDhtReadRequests() { return queuedDHTReadRequests - completedDHTReadRequests - timedoutDHTReadRequests; } public long getOutstandingDhtWriteRequests() { return queuedDHTWriteRequests - completedDHTWriteRequests - timedoutDHTWriteRequests; } private void chtLookupAndConnect(final Friend friend, final ConcurrentHashMap<String, Boolean> triedIps, byte[] key, final String locSource) { logger.finer("CHT: connecting to: " + friend.getNick()); friend.updateConnectionLog(true, "Looking up friend location in CHT (" + locSource + ")"); chtClientUDP.get(key, new CHTCallback() { @Override public void errorReceived(Throwable cause) { friend.updateConnectionLog(true, "UDP CHT lookup failed: " + cause.getMessage()); } @Override public void valueReceived(byte[] key, byte[] value) { try { if (isChtEnabled()) { decryptAndConnect(triedIps, friend, value, "UDP:CHT (" + locSource + ")"); } } catch (Exception e) { friend.updateConnectionLog(true, "UDP cht value error: " + e.getMessage()); e.printStackTrace(); } } }); // If this friend comes from a community server which supports address // resolution, // send a request there as well. String sourceNetwork = friend.getSourceNetwork(); if (sourceNetwork != null && sourceNetwork.startsWith("Community_server")) { try { String serverUrl = sourceNetwork.split("\\s+")[1]; final CHTClientHTTP chtClient = CommunityServerManager.get().getChtClientForUrl( serverUrl); if (chtClient != null) { if (chtClient.getServerRecord().isAllowAddressResolution() && chtClient.getServerRecord().getCht_path() != null) { chtClient.get(key, new CHTCallback() { @Override public void errorReceived(Throwable cause) { friend.updateConnectionLog(true, "HTTP CHT lookup failed: " + cause.getMessage()); } @Override public void valueReceived(byte[] key, byte[] value) { try { if (value.length == 0) { friend.updateConnectionLog(true, "HTTP CHT lookup -- key not found."); return; } decryptAndConnect(triedIps, friend, value, "HTTP:CHT (" + locSource + " / " + chtClient.getServerRecord().getBaseURL() + ")"); } catch (Exception e) { friend.updateConnectionLog(true, "HTTP cht value error: " + e.getMessage()); e.printStackTrace(); } } }); } } // if (found community record) } catch (Exception e) { logger.warning("Error during HTTP CHT lookup: " + e.toString()); e.printStackTrace(); } } // if (sourceNetwork) } private DistributedDatabaseKey createKey(byte[] key) throws DistributedDatabaseException { final DistributedDatabaseKey dhtKey; HashWrapper keyHash = new HashWrapper(key); if (dhtKeyCache.containsKey(keyHash)) { dhtKey = dhtKeyCache.get(keyHash); } else { dhtKey = getDht().createKey(key); dhtKeyCache.put(keyHash, dhtKey); logger.finer("Creating dht key, size=" + dhtKeyCache.size()); if (dhtKeyCache.size() > 10000 && dhtKeyCache.size() % 100 == 0) { Debug.out("Creating a lot of dht keys, size=" + dhtKeyCache.size()); } } return dhtKey; } public void connectToFriend(final Friend friend, InetAddress addrHint, int portHint) { if (friend.getStatus() == Friend.STATUS_ONLINE) { return; } if (friend.isBlocked()) { return; } if (friend.msSinceLastAttempt() < 60 * 1000) { return; } friend.connectionAttempt("Got location from LanFriendFinder: " + addrHint.getHostAddress() + ":" + portHint); overlayManager.createOutgoingConnection(new ConnectionEndpoint(new InetSocketAddress( addrHint, portHint)), friend); } public void connectToInvitation(final FriendInvitation invitation) { invitation.connectAttempted(); /* * the ip:port is xored with the first n bytes in the SHA1(bytes 10-20) * in the key * * the last 10 bytes are left untouched to make any offline attacks hard */ // first, check if we have ip:port in the invite String lastConnectIp = invitation.getLastConnectIp(); if (lastConnectIp != null && invitation.getLastConnectPort() > 0) { try { InetAddress byName = InetAddress.getByName(lastConnectIp); ConnectionEndpoint c = new ConnectionEndpoint(InetSocketAddress.createUnresolved( byName.getHostAddress(), invitation.getLastConnectPort())); logger.finer("Connecting to: " + byName + ":" + invitation.getLastConnectPort()); invitationManager.newOutgoingConnection(c, invitation); } catch (Exception e) { // ignoreany problems... } } // calc key base // if we created the invitation, look up the non-creator address // if we redeem the invitation, look up the creator address boolean creatorAddress = true; if (invitation.isCreatedLocally()) { creatorAddress = false; } byte[] keyBase = invitation.getDHTKeyBase(creatorAddress); // then, do the cht lookup if (chtClientUDP != null && isChtEnabled()) { byte[] loc = new SHA1Simple().calculateHash(keyBase); chtClientUDP.get(loc, new CHTCallback() { @Override public void errorReceived(Throwable cause) { logger.finest(cause.getMessage()); } @Override public void valueReceived(byte[] key, byte[] value) { logger.finest("got value from cht: " + Base32.encode(value)); try { createAuthConnFromHtValue(invitation, value); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); logger.finest("locating invitation: addr=" + Base32.encode(loc)); } // and last the dht lookup if (getDht().isAvailable()) { try { DistributedDatabaseKey dhtKey = createKey(keyBase); queuedDHTReadRequests++; getDht().read(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { logger.finest("DHT read event:" + event.getType()); if (event.getType() == DistributedDatabaseEvent.ET_VALUE_READ) { DistributedDatabaseValue value = event.getValue(); try { byte[] bytes = (byte[]) value.getValue(byte[].class); createAuthConnFromHtValue(invitation, bytes); } catch (Exception e) { e.printStackTrace(); } } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE) { completedDHTReadRequests++; logger.finest("DHT read event completed or timed out, queued=" + queuedDHTReadRequests + " completed=" + completedDHTReadRequests + " outstanding=" + getOutstandingDhtReadRequests()); } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT) { logger.finer("DHT read event completed or timed out, queued=" + queuedDHTReadRequests + " completed=" + completedDHTReadRequests + " outstanding=" + getOutstandingDhtReadRequests()); timedoutDHTReadRequests++; } } }, dhtKey, DHT_TIMEOUT); } catch (DistributedDatabaseException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } private void createAuthConnFromHtValue(final FriendInvitation invitation, byte[] value) throws UnknownHostException { byte[] ipPort = invitation.xorDhtValue(value); byte[] ip = new byte[ipPort.length - 2]; System.arraycopy(ipPort, 0, ip, 0, ip.length); InetAddress address = InetAddress.getByAddress(ip); byte[] portBytes = new byte[] { 0, 0, ipPort[ip.length], ipPort[ip.length + 1] }; int port = byteArrayToInt(portBytes); logger.finest("after decrypt: " + address.getHostAddress() + ":" + port); ConnectionEndpoint c = new ConnectionEndpoint(new InetSocketAddress(address, port)); if (invitation.getStatus().getCode() > Status.STATUS_NEW.getCode() && invitation.getStatus().getCode() < Status.STATUS_IP_PORT_LOCATED.getCode()) { invitation.setStatus(Status.STATUS_IP_PORT_LOCATED); } invitationManager.newOutgoingConnection(c, invitation); } private synchronized void decryptAndConnect(ConcurrentHashMap<String, Boolean> triedIps, final Friend friend, byte[] value, String source) throws Exception, UnknownHostException { byte[] decrypted = verifyAndDecrypt(value, friend.getPublicKeyObj()); InetAddress addr = getInetAddress(decrypted); int port = getPort(decrypted); long timeStamp = getTimeStamp(decrypted); long age = System.currentTimeMillis() - timeStamp; String ipPort = addr.getHostAddress() + ":" + port; friend.updateConnectionLog(true, "Resolved friend location from: " + source + ": " + ipPort + " age=" + age / (60 * 1000) + " minutes"); if (!triedIps.containsKey(ipPort)) { triedIps.put(ipPort, true); overlayManager.createOutgoingConnection(new ConnectionEndpoint(new InetSocketAddress( addr, port)), friend); } else { friend.updateConnectionLog(true, "Skipping ip:port from " + source + ": " + ipPort + " (already tried)"); } if (source.startsWith("DHT")) { logger.fine("DHT lookup verified successfully (" + friend.getNick() + ")"); } } private boolean dhtPublishAllowed(InetAddress localAddress, int localPort) { if (!getDht().isAvailable()) { return false; } if (ownPublicKey == null) { PublicKey k = overlayManager.getOwnPublicKey(); if (k != null) { ownPublicKey = k.getEncoded(); if (ownPublicKey == null) { return false; } } else { logger.finest("unknown local public key, " + "can't publish ip:port in dht"); return false; } } else if (lastPublishedIP != null && (!lastPublishedIP.equals(localAddress) || localPort != lastPublishedPort)) { // ip changed, new publish allowed return true; } else if (System.currentTimeMillis() - dhtLastPublishTime < REPUBLISH_TIME) { logger.finest("can't publish ip:port in dht: " + "already published within time limit"); return false; } return true; } private boolean isChtEnabled() { return COConfigurationManager.getBooleanParameter(USE_CHT_PROXY_SETTINGS_KEY); } public boolean forceRepublish() { lastPublishedIP = null; this.dhtLastPublishTime = this.chtLastPublishTime = 0; return this.publishLocationInfo(); } private void publishAttempted(InetAddress localAddress, int localPort, boolean dht, boolean cht) { this.lastPublishedIP = localAddress; this.lastPublishedPort = localPort; if (dht) { this.dhtLastPublishTime = System.currentTimeMillis(); } if (cht) { this.chtLastPublishTime = System.currentTimeMillis(); } } public void publishInvitation(final FriendInvitation invitation) { if (!invitation.isStillValid()) { logger.finer("not publishing invitation location"); return; } logger.finest("publishing invitation loc info"); boolean published = false; byte[] keyBase = invitation.getDHTKeyBase(invitation.isCreatedLocally()); byte[] value = shaAndXor(invitation, externalIp, tcpListeningPort); if (chtClientUDP != null && isChtEnabled()) { try { byte[] loc = new SHA1Simple().calculateHash(keyBase); chtClientUDP.put(loc, value); logger.finest("publishing invitation: key=" + Base32.encode(loc)); published = true; logger.finest("before encrypt: " + externalIp.getHostAddress() + ":" + tcpListeningPort + " after: " + Base32.encode(value)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } logger.finest("cht done"); if (getDht().isAvailable()) { try { DistributedDatabaseKey dhtKey = createKey(keyBase); final DistributedDatabaseValue[] dhtValue = new DistributedDatabaseValue[] { getDht() .createValue(value) }; published = true; queuedDHTWriteRequests++; getDht().write(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { // Log.log("DHT write event:" + event.getType(), // logToStdOut); // if (event.getType() == // DistributedDatabaseEvent.ET_VALUE_WRITTEN) { // Log.log("dht publish succeded", logToStdOut); // } if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE) { completedDHTWriteRequests++; logger.fine("DHT write event completed, queued=" + queuedDHTWriteRequests + " completed=" + completedDHTWriteRequests + " outstanding=" + getOutstandingDhtWriteRequests() + " timeout=" + timedoutDHTWriteRequests); } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT) { logger.fine("DHT write event timed out, queued=" + queuedDHTWriteRequests + " completed=" + completedDHTWriteRequests + " outstanding=" + getOutstandingDhtWriteRequests() + " timeout=" + timedoutDHTWriteRequests); timedoutDHTWriteRequests++; } } }, dhtKey, dhtValue); } catch (DistributedDatabaseException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } if (invitation.getStatus().equals(Status.STATUS_NEW) && published) { invitation.setStatus(Status.STATUS_IP_PORT_PUBLISHED); } logger.finest("dht done"); } private final ConcurrentHashMap<Friend, Long> lastDhtPublishForFriend = new ConcurrentHashMap<Friend, Long>(); public boolean publishLocationInfo() { InetAddress localAddress = externalIp; int localPort = tcpListeningPort; boolean useDht = dhtPublishAllowed(localAddress, localPort); boolean useCht = chtPublishAllowed(localAddress, localPort); publishAttempted(localAddress, localPort, useDht, useCht); if (useDht) { logger.fine("putting location info in DHT: " + localAddress.getHostAddress() + ":" + localPort); } if (useCht) { logger.fine("putting location info in CHT: " + localAddress.getHostAddress() + ":" + localPort); } byte[] ipPortTimeStamp = convertToIPPortTimeStamp(localAddress, localPort, System.currentTimeMillis()); try { final AtomicBoolean dhtAvailable = new AtomicBoolean(false); if (getDht().isAvailable()) { dhtAvailable.set(true); } ArrayList<Friend> friendsSorted = new ArrayList<Friend>(Arrays.asList(OSF2FMain .getSingelton().getFriendManager().getFriends())); Collections.sort(friendsSorted, new Comparator<Friend>() { @Override public int compare(Friend o1, Friend o2) { if (dhtAvailable.get() == false) { return lastConnectCompare(o1, o2); } else { /* * if o1 never been published in the dht, do so first */ Long o1Publish = lastDhtPublishForFriend.get(o1); Long o2Publish = lastDhtPublishForFriend.get(o2); if (o1Publish == null && o2Publish == null) { // neither is checked, sort by last connect return lastConnectCompare(o1, o2); } if (o1Publish != null && o2Publish == null) { // o1 has been checked before return -1; } else if (o1Publish == null && o2Publish != null) { return 1; } else { /* * sort by last dht lookup time */ return o1Publish.compareTo(o2Publish); } } } private int lastConnectCompare(Friend o1, Friend o2) { if (o1.getLastConnectDate() != null) { if (o2.getLastConnectDate() == null) { return -1; } else { return -1 * o1.getLastConnectDate().compareTo(o2.getLastConnectDate()); } } else { return 1; } } }); for (Friend f : friendsSorted) { publishLocationInfoForFriend(f, ipPortTimeStamp, useDht, useCht); } } catch (DistributedDatabaseException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvalidKeySpecException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return useCht || useDht; } public void publishLocationInfoForFriend(Friend f) throws NoSuchAlgorithmException, InvalidKeySpecException, DistributedDatabaseException, Exception { InetAddress localAddress = externalIp; int localPort = tcpListeningPort; byte[] ipPortTimeStamp = convertToIPPortTimeStamp(localAddress, localPort, System.currentTimeMillis()); boolean useDht = false; if (getDht().isAvailable()) { useDht = true; } boolean useCht = isChtEnabled(); publishLocationInfoForFriend(f, ipPortTimeStamp, useDht, useCht); } // private byte[] dhtValueMerge(HashMap<Byte, byte[]> dataRead) { // int totalSize = 0; // for (byte[] b : dataRead.values()) { // totalSize += b.length - 2; // } // // byte[] merged = new byte[totalSize]; // int pos = 0; // for (byte i = 0; i < dataRead.size(); i++) { // byte[] chunk = dataRead.get(i); // System.arraycopy(chunk, 2, merged, pos, chunk.length - 2); // pos += chunk.length - 2; // } // Log.log("merged " + dataRead.size() + " chunks (" + merged.length + // " bytes)", logToStdOut); // return merged; // } // private DistributedDatabaseValue[] dhtValueSplit(byte[] bigChunkOfData) // throws DistributedDatabaseException { // // use chunk sizes of 130 bytes // int MAX_CHUNK_SIZE = 256; // int SPLIT_CHUNK_SIZE = 128; // if (bigChunkOfData.length <= MAX_CHUNK_SIZE) { // return new DistributedDatabaseValue[] { // dhtManager.createValue(bigChunkOfData) }; // } else { // List<DistributedDatabaseValue> values = new // LinkedList<DistributedDatabaseValue>(); // // int pos = 0; // byte num = 0; // int totalChunks = ((bigChunkOfData.length / SPLIT_CHUNK_SIZE) + 1); // if (totalChunks > 128) { // throw new RuntimeException("max chunk num=128"); // } // while (pos < bigChunkOfData.length) { // int sizeOfCurrentChunk = Math.min(bigChunkOfData.length - pos, // SPLIT_CHUNK_SIZE); // byte[] currentChunk = new byte[sizeOfCurrentChunk + 2]; // currentChunk[0] = num; // currentChunk[1] = (byte) totalChunks; // System.arraycopy(bigChunkOfData, pos, currentChunk, 2, // sizeOfCurrentChunk); // // DistributedDatabaseValue v = dhtManager.createValue(currentChunk); // values.add(v); // // System.err.println("created dht value: pos=" + pos + " len=" // // + currentChunk.length + " total_len=" + bigChunkOfData.length // // + " chunk=" + num + " total_chunks=" + totalChunks); // pos += sizeOfCurrentChunk; // num++; // // } // return values.toArray(new DistributedDatabaseValue[values.size()]); // } // // } private void publishLocationInfoForFriend(Friend f, byte[] ipPortTimeStamp, boolean dht, boolean cht) throws NoSuchAlgorithmException, InvalidKeySpecException, DistributedDatabaseException, Exception { if (!dht && !cht) { return; } if (!f.isBlocked()) { if (dht) { logger.finer("DHT: publishing to: " + f.getNick()); if (getOutstandingDhtWriteRequests() > MAX_DHT_WRITE_QUEUE_LENGTH) { logger.finest("Skipping DHT location publish, dht write queue too long: " + getOutstandingDhtWriteRequests()); dht = false; } else { lastDhtPublishForFriend.put(f, System.currentTimeMillis()); } } /* * create a custom value for the friend */ byte[] value = encryptAndSign(ipPortTimeStamp, f.getPublicKeyObj()); /* * public key based key, format is: our key in bytes, append friends * key in bytes, Azureus will calc the sha1 of this * * if the friend has an unconfirmed custom dht location, publish to * both, else publish only to the custom location */ /* * if we have custom dht values */ if (f.getDhtWriteLocation() != null) { byte[] key = f.getDhtWriteLocation(); /* * and write to dht */ if (dht) { logger.finer("putting location info into dht for friend: " + f.getNick()); final DistributedDatabaseKey dhtKey = createKey(key); final DistributedDatabaseValue[] dhtValue = new DistributedDatabaseValue[] { getDht() .createValue(value) }; queuedDHTWriteRequests++; getDht().write(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE) { completedDHTWriteRequests++; logger.finest("DHT write event completed, queued=" + queuedDHTWriteRequests + " completed=" + completedDHTWriteRequests + " outstanding=" + getOutstandingDhtWriteRequests()); } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT) { logger.finest("DHT write event timed out, queued=" + queuedDHTWriteRequests + " completed=" + completedDHTWriteRequests + " outstanding=" + getOutstandingDhtWriteRequests()); timedoutDHTWriteRequests++; } } }, dhtKey, dhtValue); } if (cht) { chtClientUDP.put(key, value); // If this friend is from a community server, we check to // see if that server // supports acting as a backup name resolver. If so, publish // to it. String sourceNetwork = f.getSourceNetwork(); if (sourceNetwork != null && sourceNetwork.startsWith("Community_server")) { try { String serverUrl = sourceNetwork.split("\\s+")[1]; CHTClientHTTP chtClient = CommunityServerManager.get() .getChtClientForUrl(serverUrl); if (chtClient != null) { if (chtClient.getServerRecord().isAllowAddressResolution() && chtClient.getServerRecord().getCht_path() != null) { logger.fine("Putting k/v into CHT-supporting community server: " + chtClient.getServerRecord()); chtClient.put(key, value); } } // if (found community record) } catch (Exception e) { logger.warning("Error during CHT HTTP put: " + e.toString() + " / " + sourceNetwork); e.printStackTrace(); } } // if (sourceNetwork) } } /* * if the dht location is not confirmed we need to publish to the * public key based location as well */ if (!f.isDhtLocationConfirmed()) { byte[] friendKey = f.getPublicKey(); byte[] key = new byte[friendKey.length + ownPublicKey.length]; System.arraycopy(ownPublicKey, 0, key, 0, ownPublicKey.length); System.arraycopy(friendKey, 0, key, ownPublicKey.length, friendKey.length); /* * and write to dht */ if (dht) { final DistributedDatabaseKey dhtKey = createKey(key); final DistributedDatabaseValue[] dhtValue = new DistributedDatabaseValue[] { getDht() .createValue(value) }; queuedDHTWriteRequests++; getDht().write(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE || event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE) { if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_COMPLETE) { completedDHTWriteRequests++; logger.finest("DHT write event completed, queued=" + queuedDHTWriteRequests + " completed=" + completedDHTWriteRequests + " outstanding=" + getOutstandingDhtWriteRequests()); } else if (event.getType() == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT) { logger.finest("DHT write event timed out, queued=" + queuedDHTWriteRequests + " completed=" + completedDHTWriteRequests + " outstanding=" + getOutstandingDhtWriteRequests()); timedoutDHTWriteRequests++; } } } }, dhtKey, dhtValue); } if (cht) { byte[] keySha = new SHA1Simple().calculateHash(key); chtClientUDP.put(keySha, value); } } } } private void test() { DHTLog.logging_on = true; DHTLog.setLogger(null); Thread t = new Thread(new Runnable() { @Override public void run() { try { while (true) { Thread.sleep(30 * 1000); System.out.println("testing write"); testWrite(); Thread.sleep(15 * 1000); System.out.println("testing read"); testRead(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); t.setDaemon(true); t.setName("dht tester thread"); t.start(); } private void testRead() { byte[] keySha1 = new SHA1Simple().calculateHash(ownPublicKey); chtClientUDP.get(keySha1, new CHTCallback() { @Override public void errorReceived(Throwable cause) { System.out.println("CHT lookup failed"); } @Override public void valueReceived(byte[] key, byte[] value) { try { byte[] data = verifyAndDecrypt(value, overlayManager.getOwnPublicKey()); System.out.println("got cht: " + getInetAddress(data) + ":" + getPort(data) + " age=" + ((System.currentTimeMillis() - getTimeStamp(data)) / (1000)) + " s"); // byte[] verif } catch (Exception e) { System.out.println("got error: " + e.getMessage()); e.printStackTrace(); } } }); try { final DistributedDatabaseKey dhtKey = createKey(ownPublicKey); getDht().read(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { logger.fine("DHT read event:" + event.getType()); if (event.getType() == DistributedDatabaseEvent.ET_VALUE_READ) { DistributedDatabaseValue value = event.getValue(); try { byte[] bytes = (byte[]) value.getValue(byte[].class); byte[] data = verifyAndDecrypt(bytes, overlayManager.getOwnPublicKey()); logger.fine("got dht: " + getInetAddress(data) + ":" + getPort(data) + " age=" + ((System.currentTimeMillis() - getTimeStamp(data)) / (1000)) + " s"); // byte[] verif } catch (Exception e) { System.out.println("got error: " + e.getMessage()); e.printStackTrace(); } } } }, dhtKey, DHT_TIMEOUT); } catch (DistributedDatabaseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void testWrite() { try { InetAddress localAddress = externalIp; int localPort = tcpListeningPort; byte[] ipPortTimeStamp = convertToIPPortTimeStamp(localAddress, localPort, System.currentTimeMillis()); byte[] value; value = encryptAndSign(ipPortTimeStamp, overlayManager.getOwnPublicKey()); final DistributedDatabaseKey dhtKey = createKey(ownPublicKey); final DistributedDatabaseValue[] dhtValue = new DistributedDatabaseValue[] { getDht() .createValue(value) }; getDht().write(new DistributedDatabaseListener() { @Override public void event(DistributedDatabaseEvent event) { System.out.println("DHT write event:" + event.getType()); if (event.getType() == DistributedDatabaseEvent.ET_VALUE_WRITTEN) { System.out.println("dht publish succeded"); } } }, dhtKey, dhtValue); byte[] keySha = new SHA1Simple().calculateHash(ownPublicKey); chtClientUDP.put(keySha, value); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private static int byteArrayToInt(byte[] b) { return (b[0] << 24) + ((b[1] & 255) << 16) + ((b[2] & 255) << 8) + (b[3] & 255); } private static long byteArrayToLong(byte[] b) { return (((long) b[0] << 56) + ((long) (b[1] & 255) << 48) + ((long) (b[2] & 255) << 40) + ((long) (b[3] & 255) << 32) + ((long) (b[4] & 255) << 24) + ((b[5] & 255) << 16) + ((b[6] & 255) << 8) + ((b[7] & 255) << 0)); } /** * returns: 4/16 bytes inet addr, 2 byte port, 8 byte timestamp * * @param addr * @param port * @return */ private static byte[] convertToIPPortTimeStamp(InetAddress addr, int port, long time) { int addrLength = addr.getAddress().length; byte[] dhtBytes = new byte[addrLength + 2 + 8]; /* * copy in the inet address */ System.arraycopy(addr.getAddress(), 0, dhtBytes, 0, addrLength); /* * add the port */ byte[] portBytes = intToByteArray(port); dhtBytes[addrLength] = portBytes[2]; dhtBytes[addrLength + 1] = portBytes[3]; /* * and the time stamp */ byte[] timeStamp = longToByteArray(time); System.arraycopy(timeStamp, 0, dhtBytes, addrLength + 2, timeStamp.length); return dhtBytes; } private static byte[] encryptAndSign(byte[] data, PublicKey friendsPublicKey) throws Exception { /* * encrypt the payload so that only the friend can read it + verify that * the data is correct */ byte[] encrypted = OneSwarmSslKeyManager.getInstance().encrypt(data, friendsPublicKey); if (encrypted.length > ENCRYPTED_LENGTH) { throw new Exception("encrypted data length > " + ENCRYPTED_LENGTH); } // System.out.println("length before encryption: " + data.length + // " after: " + encrypted.length); /* * sign the entire thing (both length field and encrypted data) */ byte[] sign = OneSwarmSslKeyManager.getInstance().sign(encrypted); // System.out.println("signature length: " + sign.length); if (sign.length > SIGN_LENGTH) { throw new Exception("signature length > " + SIGN_LENGTH); } /* * copy it into the final byte[] */ byte[] dhtValue = new byte[encrypted.length + sign.length]; System.arraycopy(encrypted, 0, dhtValue, 0, encrypted.length); System.arraycopy(sign, 0, dhtValue, encrypted.length, sign.length); return dhtValue; } private static InetAddress getInetAddress(byte[] dhtValue) throws UnknownHostException { byte[] inetAddr = new byte[dhtValue.length - 10]; System.arraycopy(dhtValue, 0, inetAddr, 0, inetAddr.length); return InetAddress.getByAddress(inetAddr); } // private static byte[] decryptWithoutVerify(byte[] data) throws Exception // { // int dataLen = byteArrayToInt(new byte[] { 0, 0, data[0], data[1] }); // if (dataLen < 0 || dataLen > 1024) { // throw new // Exception("Got bad data (length of encrypted data specified as: " + // dataLen + ") full len=" + data.length); // } // byte[] decrypted = OneSwarmSslKeyManager.getInstance().decrypt(data, 2, // dataLen); // return decrypted; // } private static int getPort(byte[] dhtValue) { byte[] port = { 0, 0, dhtValue[dhtValue.length - 10], dhtValue[dhtValue.length - 9] }; return byteArrayToInt(port); } private static long getTimeStamp(byte[] dhtValue) { byte[] b = new byte[8]; System.arraycopy(dhtValue, dhtValue.length - 8, b, 0, 8); return byteArrayToLong(b); } private static byte[] intToByteArray(int v) { return new byte[] { (byte) (v >>> 24), (byte) (v >>> 16), (byte) (v >>> 8), (byte) v }; } private static byte[] longToByteArray(long v) { byte[] b = new byte[8]; b[0] = (byte) (v >>> 56); b[1] = (byte) (v >>> 48); b[2] = (byte) (v >>> 40); b[3] = (byte) (v >>> 32); b[4] = (byte) (v >>> 24); b[5] = (byte) (v >>> 16); b[6] = (byte) (v >>> 8); b[7] = (byte) (v >>> 0); return b; } public static void main(String[] args) { try { InetAddress a = InetAddress.getByName("www.cs.washington.edu"); int port = 12303; long time = System.currentTimeMillis(); System.out.println("input4=" + a.getHostAddress() + "\t" + port + "\t" + time); byte[] serialized = convertToIPPortTimeStamp(a, port, time); System.out.println("outpu4=" + getInetAddress(serialized).getHostAddress() + "\t" + getPort(serialized) + "\t" + getTimeStamp(serialized)); byte[] encryptedAndSigned = encryptAndSign(serialized, OneSwarmSslKeyManager .getInstance().getOwnPublicKey()); byte[] decrypted = verifyAndDecrypt(encryptedAndSigned, OneSwarmSslKeyManager .getInstance().getOwnPublicKey()); System.out.println("data verified succesfully"); System.out.println("decry4=" + getInetAddress(decrypted).getHostAddress() + "\t" + getPort(decrypted) + "\t" + getTimeStamp(decrypted)); a = InetAddress.getByName("fe80::223:32ff:fed5:1f20"); port = 17; time = System.currentTimeMillis(); System.out.println("input6=" + a.getHostAddress() + "\t" + port + "\t" + time); serialized = convertToIPPortTimeStamp(a, port, time); System.out.println("outpu6=" + getInetAddress(serialized).getHostAddress() + "\t" + getPort(serialized) + "\t" + getTimeStamp(serialized)); encryptedAndSigned = encryptAndSign(serialized, OneSwarmSslKeyManager.getInstance() .getOwnPublicKey()); decrypted = verifyAndDecrypt(encryptedAndSigned, OneSwarmSslKeyManager.getInstance() .getOwnPublicKey()); System.out.println("decry6=" + getInetAddress(decrypted).getHostAddress() + "\t" + getPort(decrypted) + "\t" + getTimeStamp(decrypted)); System.out.print("testing data modification detection (expect exception)... "); byte oldval = encryptedAndSigned[0]; encryptedAndSigned[0] = 100; try { decrypted = verifyAndDecrypt(encryptedAndSigned, OneSwarmSslKeyManager .getInstance().getOwnPublicKey()); } catch (Exception e) { System.out.println("test successful (error caught: " + e.getMessage() + ")"); } encryptedAndSigned[0] = oldval; System.out.print("testing data modification detection (expect exception)... "); oldval = encryptedAndSigned[56]; encryptedAndSigned[56] = 100; try { decrypted = verifyAndDecrypt(encryptedAndSigned, OneSwarmSslKeyManager .getInstance().getOwnPublicKey()); } catch (Exception e) { System.out.println("test successful (error caught: " + e.getMessage() + ")"); } encryptedAndSigned[56] = oldval; System.out.print("testing data modification detection (expect exception)... "); encryptedAndSigned[149] = 100; try { decrypted = verifyAndDecrypt(encryptedAndSigned, OneSwarmSslKeyManager .getInstance().getOwnPublicKey()); } catch (Exception e) { System.out.println("test successful (error caught: " + e.getMessage() + ")"); } } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private static byte[] shaAndXor(FriendInvitation invitation, InetAddress ip, int port) { int addrLength = ip.getAddress().length; byte[] dhtBytes = new byte[addrLength + 2]; /* * copy in the inet address */ System.arraycopy(ip.getAddress(), 0, dhtBytes, 0, addrLength); /* * add the port */ byte[] portBytes = intToByteArray(port); dhtBytes[addrLength] = portBytes[2]; dhtBytes[addrLength + 1] = portBytes[3]; return invitation.xorDhtValue(dhtBytes); } private static byte[] verifyAndDecrypt(byte[] data, PublicKey friendsPublicKey) throws Exception { boolean verify = OneSwarmSslKeyManager.getInstance().signVerify(data, 0, ENCRYPTED_LENGTH, friendsPublicKey, data, ENCRYPTED_LENGTH, SIGN_LENGTH); if (!verify) { throw new Exception("Got bad data (signature did not verify)"); } byte[] decrypted = OneSwarmSslKeyManager.getInstance().decrypt(data, 0, ENCRYPTED_LENGTH); return decrypted; } private class FriendConnectorRunnable extends TimerTask { private boolean firstDhtRunCompleted = false; private final Logger logger = Logger.getLogger(FriendConnectorRunnable.class.getName()); @Override public void run() { logger.fine("Running friend connector"); if (!overlayManager.getFilelistManager().isInitialFileListGenerated()) { logger.fine("skipping friend connector run, file list not done yet"); return; } boolean returnedFromStandby = false; if ((System.currentTimeMillis() - lastConnectorRunTime) > 5 * 1000 + CONNECTOR_FREQUENCY) { returnedFromStandby = true; logger.finer("time travel detected (returned from standby?), friend connection attempt triggered"); } synchronized (this) { logger.finest("Checking if dht is available"); boolean firstDhtRun = false; final AtomicBoolean dhtAvailable = new AtomicBoolean(false); if (getDht().isAvailable()) { dhtAvailable.set(true); if (!firstDhtRunCompleted) { firstDhtRun = true; firstDhtRunCompleted = true; } } int attempts = 0; logger.finer("dht available=" + dhtAvailable); publishLocationInfo(); logger.finer("publish completed, connecting to friends"); // then try to connect to friends that are disconnected List<Friend> disconnectedFriends = overlayManager.getDisconnectedFriends(); /* * if the dht is unavailable, sort by last connect date * * else sort by last dht lookup date */ Collections.sort(disconnectedFriends, new Comparator<Friend>() { @Override public int compare(Friend o1, Friend o2) { if (dhtAvailable.get() == false) { return lastConnectCompare(o1, o2); } else { /* * if o1 never been looked up in the dht, do so * first */ Long o1Checked = lastDhtLookupForFriend.get(o1); Long o2Checked = lastDhtLookupForFriend.get(o2); if (o1Checked == null && o2Checked == null) { // neither is checked, sort by last connect return lastConnectCompare(o1, o2); } if (o1Checked != null && o2Checked == null) { // o1 has been checked before return -1; } else if (o1Checked == null && o2Checked != null) { return 1; } else { /* * sort by last dht lookup time */ return o1Checked.compareTo(o2Checked); } } } private int lastConnectCompare(Friend o1, Friend o2) { if (o1.getLastConnectDate() != null) { if (o2.getLastConnectDate() == null) { return -1; } else { return -1 * o1.getLastConnectDate() .compareTo(o2.getLastConnectDate()); } } else { return 1; } } }); for (Friend friend : disconnectedFriends) { boolean connectAllowed; if (!friend.isBlocked()) { if (firstDhtRun) { /* * we can connect if the dht just came up */ connectAllowed = true; } else if (returnedFromStandby) { /* * or if we just returned from standby */ connectAllowed = true; } else if (connectAttemptAllowed(friend)) { /* * or if we are allowed :-) */ connectAllowed = true; } else { connectAllowed = false; } if (connectAllowed) { connectToFriend(friend, dhtAvailable.get()); attempts++; } } } logger.fine("friend connector task completed, attempts: " + attempts); lastConnectorRunTime = System.currentTimeMillis(); } } } private class InvitationConnectorRunnable extends TimerTask { private boolean firstDhtRunCompleted = false; private final Logger logger = Logger.getLogger(InvitationConnectorRunnable.class.getName()); @Override public void run() { logger.fine("Running invitation connector"); if (!overlayManager.getFilelistManager().isInitialFileListGenerated()) { logger.fine("skipping invitation connector run, file list not done yet"); return; } boolean returnedFromStandby = false; if ((System.currentTimeMillis() - lastConnectorRunTime) > 5 * 1000 + CONNECTOR_FREQUENCY) { returnedFromStandby = true; logger.fine("time travel detected (returned from standby?), friend connection attempt triggered"); } synchronized (this) { logger.finest("Checking if dht is available"); boolean firstDhtRun = false; boolean dhtAvailable = false; if (getDht().isAvailable()) { dhtAvailable = true; if (!firstDhtRunCompleted) { firstDhtRun = true; firstDhtRunCompleted = true; } } int attempts = 0; logger.fine("dht available=" + dhtAvailable); publishLocationInfo(); logger.fine("publish completed, connecting to friends"); // then try to connect to invitations List<FriendInvitation> invitations = invitationManager.getInvitations(); for (FriendInvitation invitation : invitations) { boolean connectAllowed; if (firstDhtRun) { /* * we can connect if the dht just came up */ connectAllowed = true; } else if (returnedFromStandby) { /* * or if we just returned from standby */ connectAllowed = true; } else if (invitation.connectAttemptsAllowed()) { /* * or if we are allowed :-) */ connectAllowed = true; } else { connectAllowed = false; } if (connectAllowed) { connectToInvitation(invitation); } logger.fine("friend connector task completed, attempts: " + attempts); lastConnectorRunTime = System.currentTimeMillis(); } } } } }