package edu.washington.cs.oneswarm.f2f; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Map; import java.util.logging.Logger; import org.eclipse.swt.SWT; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.BDecoder; 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.plugins.update.UpdateCheckInstance; import org.gudy.azureus2.plugins.update.UpdateCheckInstanceListener; import org.gudy.azureus2.ui.swt.Utils; import org.gudy.azureus2.ui.swt.update.UpdateMonitor; import com.aelitis.azureus.core.AzureusCoreFactory; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import com.aelitis.azureus.core.networkmanager.NetworkManager; import com.aelitis.azureus.core.networkmanager.Transport; import com.aelitis.azureus.core.networkmanager.NetworkManager.RoutingListener; import com.aelitis.azureus.core.networkmanager.impl.TransportHelper; import com.aelitis.azureus.core.peermanager.messaging.MessageStreamDecoder; import com.aelitis.azureus.core.peermanager.messaging.MessageStreamEncoder; import com.aelitis.azureus.core.peermanager.messaging.MessageStreamFactory; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZMessageDecoder; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZMessageEncoder; public class OSF2FNatChecker { private static final String NAT_HANDSHAKE = "OneSwarm NAT"; private final static byte[] legacy_handshake_header = new byte[NAT_HANDSHAKE.getBytes().length + 1]; static { ByteBuffer b = ByteBuffer.wrap(legacy_handshake_header); b.put((byte) NAT_HANDSHAKE.length()); b.put(NAT_HANDSHAKE.getBytes()); } private final static Logger logger = Logger.getLogger(OSF2FNatChecker.class.getName()); private NetworkManager.ByteMatcher natMatcher; private final static byte S = DirectByteBuffer.SS_OTHER; private NatCheckResult lastTcpResult; private NatCheckResult lastUpdResult; private boolean running = false; public OSF2FNatChecker() { COConfigurationManager.addAndFireParameterListener("Perform.NAT.Check", new ParameterListener() { public void parameterChanged(String parameterName) { boolean enabled = COConfigurationManager .getBooleanParameter("Perform.NAT.Check"); if (enabled) { installNatCheckMatcher(); } else { uninstallNatCheckMatcher(); } } }); } public void triggerNatCheck() { if (running) { logger.finer("NAT check triggered, doing update check"); // set last to waiting lastTcpResult = new NatCheckResult(); UpdateMonitor.getSingleton(AzureusCoreFactory.getSingleton()).performCheck(true, false, false, new UpdateCheckInstanceListener() { public void cancelled(UpdateCheckInstance instance) { } public void complete(UpdateCheckInstance instance) { logger.finer("NAT check completed"); } }); } else { logger.finer("NAT check not running, skipping nat check trigger"); } } public NatCheckResult getResult() { return lastTcpResult; } private void uninstallNatCheckMatcher() { synchronized (this) { if (running && natMatcher != null) { logger.fine("removing nat check routing"); NetworkManager.getSingleton().cancelIncomingConnectionRouting(natMatcher); running = false; } } } private void installNatCheckMatcher() { synchronized (this) { if (!running) { Thread t = new Thread(new NatCheckRunnable()); t.setDaemon(true); t.setName("OSF2F protocol matcher loader"); t.start(); } } } private final class NatCheckRunnable implements Runnable { public void run() { synchronized (OSF2FNatChecker.this) { running = true; logger.fine("installing nat check routing"); natMatcher = new OsNatMatcher(); NetworkManager.getSingleton().requestIncomingConnectionRouting(natMatcher, new RoutingListener() { public boolean autoCryptoFallback() { // TODO Auto-generated method stub return false; } @SuppressWarnings("unchecked") public void connectionRouted(NetworkConnection connection, Object routing_data) { logger.fine("connection routed to NAT checker, isTcp=" + connection.getTransport().isTCP()); DirectByteBuffer lenBuffer = DirectByteBufferPool.getBuffer(S, 4); try { final Transport tr = connection.getTransport(); // start by checking the handShake byte[] hsBuffer = new byte[NAT_HANDSHAKE.getBytes().length + 1]; tr.read(new ByteBuffer[] { ByteBuffer.wrap(hsBuffer) }, 0, 1); if (!Arrays.equals(hsBuffer, legacy_handshake_header)) { final String msg = "got wrong handshake to NatChecker, incoming: " + new String(hsBuffer); Debug.out(msg); throw new IOException(msg); } // then read the actual stuff tr.read(new ByteBuffer[] { lenBuffer.getBuffer(S) }, 0, 1); lenBuffer.flip(S); int len = lenBuffer.getInt(S); int MAX_LEN = 1024; if (len > MAX_LEN || len <= 0) { throw new IOException("invalid len specified: " + len); } ByteBuffer b = ByteBuffer.allocate(len); tr.read(new ByteBuffer[] { b }, 0, 1); b.flip(); int read = b.remaining(); logger.finer("read " + read + " bytes for NAT check"); byte[] backingBuffer = b.array(); final Map<String, Object> message = BDecoder.decode( backingBuffer, 0, read); logger.finest("decoded " + message.size() + " keys"); if (!message.containsKey("ip")) { final String msg = "Invalid nat check response, no 'ip' key"; throw new IOException(msg); } if (!message.containsKey("port")) { final String msg = "Invalid nat check response, no 'port' key"; throw new IOException(msg); } String ip = new String((byte[]) message.get("ip")); logger.finest("ip=" + ip); int port = (int) ((Long) message.get("port")).longValue(); logger.finest("port=" + port); boolean isTcp = tr.isTCP(); if (isTcp) { lastTcpResult = new NatCheckResult(ip, port); logger.fine("got tcp nat check: " + lastTcpResult.toString()); } else { lastUpdResult = new NatCheckResult(ip, port); logger.fine("got udp nat check: " + lastUpdResult.toString()); } // and write back the handshake final ByteBuffer outBuffer = ByteBuffer .wrap(legacy_handshake_header); logger.finest("writing " + outBuffer.remaining() + " bytes"); long written = 0; while (tr.write(new ByteBuffer[] { outBuffer }, 0, 1) > 0 && outBuffer.remaining() > 0) { logger.finest("total written: " + written); } connection.close(); } catch (Exception e) { Debug.out("Error during incoming nat check" + e.getMessage()); } } }, new MessageStreamFactory() { public MessageStreamEncoder createEncoder() { return new AZMessageEncoder(false); /* unused */ } public MessageStreamDecoder createDecoder() { return new AZMessageDecoder(); /* unused */ } }); } } } static class NatCheckResult { public final static long NAT_CHECK_TIMEOUT = 20 * 1000; static enum Status { WAITING(0), FAILED(-1), SUCCESS(1); private final int statusCode; Status(int code) { this.statusCode = code; } public int getCode() { return statusCode; } static Status getFromCode(int code) { switch (code) { case -1: return FAILED; case 1: return SUCCESS; default: return WAITING; } } } Status status; String ip; int port; final long time; public NatCheckResult() { this.status = Status.WAITING; this.time = System.currentTimeMillis(); } public NatCheckResult(String ip, int port) { this.status = Status.SUCCESS; this.ip = ip; this.port = port; this.time = System.currentTimeMillis(); } public int hashCode() { return ip.hashCode() ^ port; } public boolean equals(Object o) { if (o instanceof NatCheckResult == false) { return false; } NatCheckResult p = (NatCheckResult) o; if (p.ip == null || !p.ip.equals(ip)) { return false; } if (p.port != port) { return false; } return true; } public String toString() { return ip + ":" + port; } public long getAge() { return System.currentTimeMillis() - time; } public Status getStatus() { if (status == Status.WAITING && getAge() > NAT_CHECK_TIMEOUT) { status = Status.FAILED; } return status; } } static class OsNatMatcher implements NetworkManager.ByteMatcher { private final int size; public OsNatMatcher() { this.size = legacy_handshake_header.length; } public byte[][] getSharedSecrets() { return null; } public int getSpecificPort() { return (-1); } public Object matches(TransportHelper transport, ByteBuffer to_compare, int port) { // logger.finest("looking at: " + new String(to_compare.array())); int old_limit = to_compare.limit(); to_compare.limit(to_compare.position() + maxSize()); boolean matches = to_compare.equals(ByteBuffer.wrap(legacy_handshake_header)); to_compare.limit(old_limit); // restore buffer structure return matches ? "" : null; } public int matchThisSizeOrBigger() { return (maxSize()); } public int maxSize() { return size; } public Object minMatches(TransportHelper transport, ByteBuffer to_compare, int port) { return (matches(transport, to_compare, port)); } public int minSize() { return maxSize(); } } }