package lbms.plugins.mldht.kad; import java.io.IOException; import java.net.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import lbms.plugins.mldht.kad.DHT.DHTtype; import lbms.plugins.mldht.kad.DHT.LogLevel; import lbms.plugins.mldht.kad.messages.MessageBase; import lbms.plugins.mldht.kad.messages.MessageDecoder; import lbms.plugins.mldht.kad.messages.PingRequest; import lbms.plugins.mldht.kad.messages.MessageBase.Type; import org.gudy.azureus2.core3.util.BDecoder; /** * @author Damokles * */ public class RPCServer implements Runnable, RPCServerBase { private DatagramSocket sock; private DHT dh_table; private ConcurrentMap<ByteWrapper, RPCCallBase> calls; private Queue<RPCCallBase> call_queue; private short next_mtid; private volatile boolean running; private Thread thread; private int numReceived; private int numSent; private int port; private RPCStats stats; public RPCServer(DHT dh_table, int port) throws SocketException { //, Object parent this.port = port; this.dh_table = dh_table; createSocket(); calls = new ConcurrentHashMap<ByteWrapper, RPCCallBase>(80, 0.75f, 3); call_queue = new ConcurrentLinkedQueue<RPCCallBase>(); stats = new RPCStats(); } public DHT getDHT() { return dh_table; } private synchronized void createSocket() throws SocketException { InetAddress addr = null; try { switch (dh_table.getType()) { case IPV4_DHT: // ipv4-only any local addr = InetAddress.getByAddress(new byte[]{0, 0, 0, 0}); break; case IPV6_DHT: for (NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces())) { for (InterfaceAddress ifaceAddr : iface.getInterfaceAddresses()) { if (!(ifaceAddr.getAddress() instanceof Inet6Address)) { continue; } Inet6Address tempAddr = (Inet6Address) ifaceAddr.getAddress(); // only accept globally reachable IPv6 unicast addresses if (tempAddr.isIPv4CompatibleAddress() || tempAddr.isLinkLocalAddress() || tempAddr.isLoopbackAddress() || tempAddr.isMulticastAddress() || tempAddr.isSiteLocalAddress()) { continue; } // found one! if (addr == null) { addr = tempAddr; continue; } byte[] raw = addr.getAddress(); // prefer other addresses over teredo if (raw[0] == 0x20 && raw[1] == 0x01 && raw[2] == 0x00 && raw[3] == 0x00) { addr = tempAddr; continue; } } } break; default: break; } } catch (Exception e) { // should not happen } if (sock != null) { sock.close(); } sock = new DatagramSocket(null); sock.setReuseAddress(true); sock.bind(new InetSocketAddress(addr, port)); // prevent sockets from being bound to the wrong interfaces if (addr == null) { sock.close(); } } public int getPort() { return port; } /** * @return external addess, if known (only ipv6 for now) */ public InetAddress getPublicAddress() { if (sock.getLocalAddress() instanceof Inet6Address && !sock.getLocalAddress().isAnyLocalAddress()) { return sock.getLocalAddress(); } return null; } /* (non-Javadoc) * @see java.lang.Runnable#run() */ /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#run() */ public void run() { int delay = 1; byte[] buffer = new byte[DHTConstants.RECEIVE_BUFFER_SIZE]; while (running) { DatagramPacket packet = new DatagramPacket(buffer, buffer.length); try { if (sock.isClosed()) { // don't try to receive on a closed socket, attempt to create a new one instead. Thread.sleep(delay * 100); if (delay < 256) { delay <<= 1; } createSocket(); continue; } sock.receive(packet); } catch (Exception e) { if (running) { DHT.log(e, LogLevel.Error); sock.close(); } continue; } try { handlePacket(packet); if (delay > 1) { delay--; } } catch (Exception e) { if (running) { DHT.log(e, LogLevel.Error); } } } DHT.logInfo("Stopped RPC Server"); } /* * (non-Javadoc) * * @see lbms.plugins.mldht.kad.RPCServerBase#start() */ public void start() { DHT.logInfo("Starting RPC Server"); running = true; thread = new Thread(this, "mlDHT RPC Thread " + dh_table.getType()); thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); thread.start(); } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#stop() */ public void stop() { running = false; sock.close(); thread = null; DHT.logInfo("Stopping RPC Server"); } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#doCall(lbms.plugins.mldht.kad.messages.MessageBase) */ public RPCCall doCall(MessageBase msg) { RPCCall c = new RPCCall(this, msg); short mtid = next_mtid++; short start = mtid; while (calls.putIfAbsent(new ByteWrapper(mtid), c) != null) { mtid = next_mtid++; if (next_mtid == start) // if this happens we cannot do any calls { call_queue.add(c); // System.out.println("Queueing RPC call, no slots available at the moment"); return c; } } msg.setMTID(mtid); sendMessage(msg); c.start(); return c; } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#timedOut(byte) */ public void timedOut(byte[] mtid) { // delete the call ByteWrapper w = new ByteWrapper(mtid); RPCCallBase c = calls.get(w); if (c != null) { stats.addTimeoutMessageToCount(c.getRequest()); calls.remove(w); dh_table.timeout(c.getRequest()); } doQueuedCalls(); } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#ping(lbms.plugins.mldht.kad.Key, java.net.InetSocketAddress) */ public void ping(Key our_id, InetSocketAddress addr) { PingRequest pr = new PingRequest(our_id); pr.setDestination(addr); doCall(pr); } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#findCall(byte) */ public RPCCallBase findCall(byte[] mtid) { return calls.get(new ByteWrapper(mtid)); } /// Get the number of active calls /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#getNumActiveRPCCalls() */ public int getNumActiveRPCCalls() { return calls.size(); } /** * @return the numReceived */ public int getNumReceived() { return numReceived; } /** * @return the numSent */ public int getNumSent() { return numSent; } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#getStats() */ public RPCStats getStats() { return stats; } private void handlePacket(DatagramPacket p) { numReceived++; stats.addReceivedBytes(p.getLength()); if (DHT.isLogLevelEnabled(LogLevel.Verbose)) { try { DHT.logVerbose(new String(p.getData(), 0, p.getLength(), "UTF-8")); } catch (Exception e) { e.printStackTrace(); } } try { Map<String, Object> bedata = BDecoder.decode(p.getData(), 0, p.getLength()); MessageBase msg = MessageDecoder.parseMessage(bedata, this); if (msg != null) { DHT.logDebug("RPC received message [" + p.getAddress().getHostAddress() + "] " + msg.toString()); stats.addReceivedMessageToCount(msg); msg.setOrigin(new InetSocketAddress(p.getAddress(), p.getPort())); msg.apply(dh_table); // erase an existing call if (msg.getType() == Type.RSP_MSG && calls.containsKey(new ByteWrapper(msg.getMTID()))) { RPCCallBase c = calls.get(new ByteWrapper(msg.getMTID())); if (c.getRequest().getDestination().equals(msg.getOrigin())) { // delete the call, but first notify it of the response c.response(msg); calls.remove(new ByteWrapper(msg.getMTID())); doQueuedCalls(); } else { DHT.logInfo("Response source (" + msg.getOrigin() + ") mismatches request destination (" + c.getRequest().getDestination() + "); ignoring response"); } } } else { try { DHT.logDebug("RPC received message [" + p.getAddress().getHostAddress() + "] Decode failed msg was:" + new String(p.getData(), 0, p.getLength(), "UTF-8")); } catch (Exception e) { e.printStackTrace(); } } // } catch (IOException e) { DHT.log(e, LogLevel.Debug); } } /* (non-Javadoc) * @see lbms.plugins.mldht.kad.RPCServerBase#sendMessage(lbms.plugins.mldht.kad.messages.MessageBase) */ public void sendMessage(MessageBase msg) { try { stats.addSentMessageToCount(msg); send(msg.getDestination(), msg.encode()); // System.out.println("RPC send Message: [" + msg.getDestination().getAddress().getHostAddress() + "] " + msg.toString()); } catch (IOException e) { e.printStackTrace(); } } private void send(InetSocketAddress addr, byte[] msg) throws IOException { if (!sock.isClosed()) { DatagramPacket p = new DatagramPacket(msg, msg.length); p.setSocketAddress(addr); try { sock.send(p); } catch (BindException e) { if (NetworkInterface.getByInetAddress(sock.getLocalAddress()) == null) { createSocket(); sock.send(p); } else { throw e; } } stats.addSentBytes(msg.length); numSent++; } } private void doQueuedCalls() { while (call_queue.peek() != null && calls.size() < DHTConstants.MAX_ACTIVE_CALLS) { RPCCallBase c; MessageBase msg; if ((c = call_queue.poll()) == null) { return; } short mtid = next_mtid++; short start = mtid; boolean canStart = true; while (calls.putIfAbsent(new ByteWrapper(mtid), c) != null) { mtid = next_mtid++; if (next_mtid == start) { canStart = false; break; } } if (!canStart) { call_queue.add(c); return; } msg = c.getRequest(); msg.setMTID(mtid); sendMessage(msg); c.start(); } } }