package edu.washington.cs.oneswarm.f2f.network; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.logging.Logger; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.peer.PEPeerManager; import org.gudy.azureus2.core3.peer.PEPeerSource; import org.gudy.azureus2.core3.peer.impl.PEPeerControl; import org.gudy.azureus2.core3.peer.impl.PEPeerTransport; import org.gudy.azureus2.core3.peer.impl.PEPeerTransportFactory; import org.gudy.azureus2.core3.util.AENetworkClassifier; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.DirectByteBuffer; import org.gudy.azureus2.core3.util.DirectByteBufferPool; import org.gudy.azureus2.core3.util.HashWrapper; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.EventWaiter; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import com.aelitis.azureus.core.networkmanager.ProtocolEndpoint; import com.aelitis.azureus.core.networkmanager.Transport; import com.aelitis.azureus.core.networkmanager.TransportEndpoint; import com.aelitis.azureus.core.networkmanager.impl.NetworkConnectionImpl; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessageDecoder; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTMessageEncoder; import edu.washington.cs.oneswarm.f2f.OSF2FAzSwtUi; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelDataMsg; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHashSearch; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHashSearchResp; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMessage; import edu.washington.cs.oneswarm.f2f.share.DownloadManagerStarter; import edu.washington.cs.oneswarm.f2f.share.DownloadManagerStarter.DownloadManagerStartListener; public class OverlayTransport extends OverlayEndpoint implements Transport { static final String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; private final static int HANDSHAKE_INFO_HASH_END_POS = 48; private final static int HANDSHAKE_INFO_HASH_START_POS = 28; private final static int HANDSHAKE_PEER_ID_POS = 48; private final static int HANDSHAKE_RESERVED_BITS_END_POS = 28; private final static int HANDSHAKE_RESERVED_BITS_START_POS = 20; public final static byte[] ID_BYTES = new String("-OS-F2F-").getBytes(); private final static int HANDSHAKE_PEER_ID_KEEP = ID_BYTES.length; private static final int HANDSHAKE_END_POS = HANDSHAKE_PEER_ID_POS + 20; private static final int HANDSHAKE_PEER_ID_START_MOD_POS = HANDSHAKE_PEER_ID_POS + HANDSHAKE_PEER_ID_KEEP; private final static Logger logger = Logger.getLogger(OverlayTransport.class.getName()); // all operations on this object must be in a synchronized block private final LinkedList<OSF2FChannelDataMsg> bufferedMessages; private final byte[] channelPeerId; private ByteBuffer data_already_read = null; private final byte[] infoHash; private int posInHandshake = 0; private final List<EventWaiter> readWaiter = new LinkedList<EventWaiter>(); private final byte[] remoteHandshakeInfoHashBytes = new byte[20]; private volatile boolean remoteHandshakeRecieved; private int transport_mode; private final List<EventWaiter> writeWaiter = new LinkedList<EventWaiter>(); public OverlayTransport(FriendConnection connection, byte[] infohash, int pathID, boolean outgoing, long overlayDelayMs, OSF2FHashSearch search, OSF2FHashSearchResp response) { super(connection, pathID, overlayDelayMs, search, response, outgoing); this.infoHash = infohash; this.bufferedMessages = new LinkedList<OSF2FChannelDataMsg>(); logger.fine(getDescription() + ": Creating overlay transport"); this.channelPeerId = generatePeerId(); } @Override protected void cleanup() { // not used. } @Override public void connectedInbound() { throw new RuntimeException("not implemented"); } public void connectOutbound(ByteBuffer initial_data, ConnectListener listener) { throw new RuntimeException("not implemented"); } @Override public void connectOutbound(ByteBuffer initial_data, ConnectListener listener, boolean high_priority) { throw new RuntimeException("not implemented"); } private void createPeerTransport(DownloadManager downloadManager) { // final check, we only allow this if the osf2f network is enabled, and // osf2f friend search is a valid peer source boolean allowed = checkOSF2FAllowed(downloadManager.getDownloadState().getPeerSources(), downloadManager.getDownloadState().getNetworks()); if (!allowed) { Debug.out("denied request to create a peer"); this.closeConnectionClosed(null, "access denied when creating overlay"); return; } PEPeerManager manager = downloadManager.getPeerManager(); PEPeerControl control = (PEPeerControl) manager; // set it up the same way as an incoming connection final NetworkConnection overlayConn = new NetworkConnectionImpl(this, new BTMessageEncoder(), new BTMessageDecoder()); PEPeerTransport pt = PEPeerTransportFactory.createTransport(control, PEPeerSource.PS_OSF2F, overlayConn, null); // start it pt.start(); // and add it to the control control.addPeerTransport(pt); // add the friend pt.setData(OSF2FAzSwtUi.KEY_OVERLAY_TRANSPORT, this); } @Override protected void destroyBufferedMessages() { synchronized (bufferedMessages) { while (bufferedMessages.size() > 0) { bufferedMessages.removeFirst().destroy(); } } } public byte[] generatePeerId() { byte[] peerId = new byte[20]; System.arraycopy(ID_BYTES, 0, peerId, 0, ID_BYTES.length); for (int i = HANDSHAKE_PEER_ID_KEEP; i < 20; i++) { int pos = (int) (Math.random() * chars.length()); peerId[i] = (byte) chars.charAt(pos); } return peerId; } @Override public String getEncryption() { return ("FriendToFriend over SSL"); } @Override public int getMssSize() { return OSF2FMessage.MAX_MESSAGE_SIZE; } @Override public TransportEndpoint getTransportEndpoint() { return new TransportEndpoint() { @Override public ProtocolEndpoint getProtocolEndpoint() { final ProtocolEndpoint p = new OverlayProtocolEndpoint(); p.getConnectionEndpoint().addProtocol(p); return p; } }; } @Override public int getTransportMode() { return transport_mode; } @Override protected void handleDelayedOverlayMessage(final OSF2FChannelDataMsg msg) { synchronized (bufferedMessages) { bufferedMessages.add(msg); if (readWaiter.size() > 0) { // Log.log("Overlay transport: notifying readwaiter"); for (EventWaiter w : readWaiter) { w.eventOccurred(); } readWaiter.clear(); } } } @Override public boolean isEncrypted() { return (true); } @Override public boolean isReadyForRead(EventWaiter waiter) { // we need the layers above to get the exception if (closed) { return true; } if (data_already_read != null) { return true; } synchronized (bufferedMessages) { if (bufferedMessages.size() > 0) { return true; } if (waiter != null) { readWaiter.add(waiter); } return false; } } @Override public boolean isReadyForWrite(final EventWaiter waiter) { // if this is an incoming connection, we to wait // for the incoming handshake before we // send our own see PEPeerTransportProtocol.java:~390 if (!remoteHandshakeRecieved && !outgoing) { if (waiter != null) { writeWaiter.add(waiter); } return false; } if (closed) { return false; } if (!friendConnection.isReadyForWrite(new WriteQueueWaiter() { @Override public void readyForWrite() { if (waiter != null) { logger.finest(getDescription() + ": connection ready, notifying waiter"); waiter.eventOccurred(); } } })) { logger.finest(getDescription() + ": connection not ready, adding waiter"); return false; } logger.finest(getDescription() + ": connection ready"); return true; } @Override public boolean isTCP() { return true; } private byte modifyIncomingHandShake(byte b) { if (posInHandshake == -1) { return b; } else { if (posInHandshake >= HANDSHAKE_PEER_ID_START_MOD_POS && posInHandshake < HANDSHAKE_END_POS) { b = channelPeerId[posInHandshake - HANDSHAKE_PEER_ID_START_MOD_POS]; } else if (posInHandshake >= HANDSHAKE_RESERVED_BITS_START_POS && posInHandshake < HANDSHAKE_RESERVED_BITS_END_POS) { b = (byte) 0; } else if (posInHandshake >= HANDSHAKE_INFO_HASH_START_POS && posInHandshake < HANDSHAKE_INFO_HASH_END_POS) { remoteHandshakeInfoHashBytes[posInHandshake - HANDSHAKE_INFO_HASH_START_POS] = b; } else if (posInHandshake == HANDSHAKE_INFO_HASH_END_POS) { // check if the info hash sent is what we // expected, see PEPeerTransportProtocol:~390 if (!Arrays.equals(infoHash, remoteHandshakeInfoHashBytes)) { logger.warning(getDescription() + ": WARNING in " + friendConnection + " :: remote host different infohash " + "than what we expected ,expected:\n " + new String(Base64.encode(infoHash) + " got\n" + new String(Base64.encode(remoteHandshakeInfoHashBytes)))); } else { logger.finer(getDescription() + ": remote handshake matches, notifying waiter"); remoteHandshakeRecieved = true; for (EventWaiter waiter : writeWaiter) { waiter.eventOccurred(); } writeWaiter.clear(); } } posInHandshake++; if (posInHandshake > HANDSHAKE_END_POS) { posInHandshake = -1; } } return b; } private int putInBuffer(ByteBuffer sources[], int array_offset, int length, DirectByteBuffer target) { int copied = 0; ByteBuffer t = target.getBuffer(DirectByteBuffer.SS_MSG); for (int i = array_offset; i < array_offset + length; i++) { ByteBuffer source = sources[i]; if (t.remaining() == 0) { break; } if (source.remaining() == 0) { continue; } int numBytesToCopy = Math.min(t.remaining(), source.remaining()); if (t.remaining() < source.remaining()) { // we need to set the limit to avoid buffer overflow int oldLimit = source.limit(); source.limit(source.position() + t.remaining()); t.put(source); source.limit(oldLimit); } else { t.put(source); } copied += numBytesToCopy; } return copied; } /** * This function is used when reading from the network * * @param source * @param targets * @param array_offset * @param length * @return */ private int putInBuffers(ByteBuffer source, ByteBuffer targets[], int array_offset, int length) { /* * check if we are past the handshake, in that case do the efficient * copy */ int copied = 0; if (posInHandshake == -1) { for (int i = array_offset; i < array_offset + length; i++) { ByteBuffer t = targets[i]; if (source.remaining() == 0) { break; } if (t.remaining() == 0) { continue; } int numBytesToCopy = Math.min(t.remaining(), source.remaining()); if (t.remaining() < source.remaining()) { // we need to set the limit to avoid buffer overflow int oldLimit = source.limit(); source.limit(source.position() + t.remaining()); t.put(source); source.limit(oldLimit); } else { t.put(source); } copied += numBytesToCopy; } } else { for (int i = array_offset; i < array_offset + length; i++) { // int start = copied; while (targets[i].hasRemaining() && source.hasRemaining()) { byte b = source.get(); // in the beginning we need to mod the handshake some b = modifyIncomingHandShake(b); targets[i].put(b); // System.out.print(b + ","); copied++; } // System.out.println("copied " + (copied - start) // + " bytes into buffer " + i + " offset=" + array_offset // + " len=" + length); } } return copied; } @Override public long read(ByteBuffer[] buffers, int array_offset, int length) throws IOException { int totalRead = 0; int totalSpace = 0; for (int i = array_offset; i < array_offset + length; i++) { totalSpace += buffers[i].remaining(); } // check if we have any pushback data if (data_already_read != null) { totalRead += putInBuffers(data_already_read, buffers, array_offset, length); if (!data_already_read.hasRemaining()) { data_already_read = null; } } synchronized (bufferedMessages) { while (bufferedMessages.size() > 0 && totalRead < totalSpace) { // get first message in buffer OSF2FChannelDataMsg msg = bufferedMessages.getFirst(); DirectByteBuffer data = msg.getPayload(); // Log.log("ready to read: " // + data.remaining(DirectByteBuffer.SS_MSG) + " space: " // + totalSpace); totalRead += putInBuffers(data.getBuffer(DirectByteBuffer.SS_MSG), buffers, array_offset, length); // check if we read the entire message if (totalRead < totalSpace) { // if we did, we can remove it and destroy it bufferedMessages.removeFirst().destroy(); } } } if (closed && totalRead == 0) { throw new IOException("Channel closed: " + getDescription() + " reason: " + closeReason); } downloadRateAverage.addValue(totalRead); return totalRead; } @Override public void setAlreadyRead(ByteBuffer bytes_already_read) { if (data_already_read != null) { Debug.out("push back already performed"); } if (bytes_already_read != null && bytes_already_read.hasRemaining()) { data_already_read = bytes_already_read; } } @Override public void setReadyForRead() { throw new RuntimeException("not implemented"); } @Override public void setTrace(boolean on) { throw new RuntimeException("not implemented"); } @Override public void setTransportMode(int mode) { this.transport_mode = mode; } @Override public void start() { logger.fine("Starting overlay transport"); started = true; // get the PEPeerMananger AKA PEPeerControl final DownloadManager downloadManager = AzureusCoreImpl.getSingleton().getGlobalManager() .getDownloadManager(new HashWrapper(infoHash)); // check if the download is stopped, in that case start it startDownloadManager(downloadManager); } private void startDownloadManager(final DownloadManager downloadManager) { DownloadManagerStarter.startDownload(downloadManager, new DownloadManagerStartListener() { @Override public void downloadStarted() { createPeerTransport(downloadManager); } }); } @Override public long write(ByteBuffer[] buffers, int array_offset, int length) throws IOException { if (closed) { // when closed just ignore the write requests // hopefully the peertransport will read everything in the buffer // and get the exception there when done return 0; } int totalToWrite = 0; int totalWritten = 0; for (int i = array_offset; i < array_offset + length; i++) { totalToWrite += buffers[i].remaining(); } logger.finest(getDescription() + "got write request for: " + totalToWrite); // only write one packet at the time if (isReadyForWrite(null)) { DirectByteBuffer msgBuffer = DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG, Math.min(totalToWrite, OSF2FMessage.MAX_PAYLOAD_SIZE)); this.putInBuffer(buffers, array_offset, length, msgBuffer); msgBuffer.flip(DirectByteBuffer.SS_MSG); totalWritten += writeMessageToFriendConnection(msgBuffer); } logger.finest("wrote " + totalWritten + " to overlay channel " + channelId); uploadRateAverage.addValue(totalWritten); return totalWritten; } /** * Checks if a download allows both OSF2F network, and osf2f search peers * * @param downloadManager * @return */ public static boolean checkOSF2FAllowed(String[] peerSources, String[] netSources) { boolean allowed = true; // check if the download allows osf2f peers boolean peerSourceOk = false; for (int i = 0; i < peerSources.length; i++) { String source = peerSources[i]; if (source.equals(PEPeerSource.PS_OSF2F)) { peerSourceOk = true; } } boolean networkOk = false; for (int i = 0; i < netSources.length; i++) { String network = netSources[i]; if (network.equals(AENetworkClassifier.AT_OSF2F)) { networkOk = true; } } if (peerSourceOk == false || networkOk == false) { allowed = false; } return allowed; } public static InetSocketAddress getRandomAddr() { byte[] randomAddr = new byte[16]; randomAddr[0] = (byte) 0xfc; randomAddr[1] = 0; Random r = new Random(); byte[] rand = new byte[randomAddr.length - 2]; r.nextBytes(rand); System.arraycopy(rand, 0, randomAddr, 2, rand.length); InetAddress addr; try { addr = InetAddress.getByAddress(randomAddr); InetSocketAddress remoteFakeAddr = new InetSocketAddress(addr, 1); return remoteFakeAddr; } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } private static class OverlayProtocolEndpoint implements ProtocolEndpoint { private ConnectionEndpoint connectionEndpoint; public OverlayProtocolEndpoint() { connectionEndpoint = new ConnectionEndpoint(getRandomAddr()); } @Override public Transport connectOutbound(boolean connect_with_crypto, boolean allow_fallback, byte[][] shared_secrets, ByteBuffer initial_data, boolean high_priority, ConnectListener listener) { Debug.out("tried to create outgoing OverlayTransport, this should never happen!!!"); throw new RuntimeException("not implemented"); } public Transport connectOutbound(boolean connect_with_crypto, boolean allow_fallback, byte[][] shared_secrets, ByteBuffer initial_data, ConnectListener listener) { Debug.out("tried to create outgoing OverlayTransport, this should never happen!!!"); throw new RuntimeException("not implemented"); } @Override public ConnectionEndpoint getConnectionEndpoint() { return connectionEndpoint; } @Override public String getDescription() { return "PROTOCOL_TCP"; } @Override public int getType() { return PROTOCOL_TCP; } @Override public void setConnectionEndpoint(ConnectionEndpoint ce) { this.connectionEndpoint = ce; } } public interface WriteQueueWaiter { public void readyForWrite(); } @Override protected boolean isService() { return false; } }