private DHT[] getDHTs() {
    if (dhts == null) {

      try {
        PluginManager pm = core.getPluginManager();

        if (pm.isInitialized()) {

          PluginInterface dht_pi = pm.getPluginInterfaceByClass(DHTPlugin.class);

          if (dht_pi == null) {

            dhts = new DHT[0];

          } else {

            DHTPlugin plugin = (DHTPlugin) dht_pi.getPlugin();

            if (!plugin.isInitialising()) {

              if (plugin.isEnabled()) {

                dhts = ((DHTPlugin) dht_pi.getPlugin()).getDHTs();

              } else {

                dhts = new DHT[0];
              }
            }
          }
        }
      } catch (Throwable e) {

        dhts = new DHT[0];
      }
    }

    return (dhts);
  }
  private void start() {
    synchronized (this) {
      if (started) {

        return;
      }

      started = true;
    }

    N_3072 =
        fromHex(
            "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08"
                + "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B"
                + "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9"
                + "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6"
                + "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8"
                + "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D"
                + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C"
                + "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718"
                + "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D"
                + "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D"
                + "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226"
                + "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C"
                + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC"
                + "E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF");

    G_3072 = BigInteger.valueOf(5);

    try {
      PluginInterface dht_pi = core.getPluginManager().getPluginInterfaceByClass(DHTPlugin.class);

      if (dht_pi == null) {

        throw (new Exception("DHT Plugin not found"));
      }

      DHTPlugin dht_plugin = (DHTPlugin) dht_pi.getPlugin();

      if (!dht_plugin.isEnabled()) {

        throw (new Exception("DHT Plugin is disabled"));
      }

      DHT[] dhts = dht_plugin.getDHTs();

      List<DHTNATPuncher> punchers = new ArrayList<DHTNATPuncher>();

      for (DHT dht : dhts) {

        int net = dht.getTransport().getNetwork();

        if (net == DHT.NW_MAIN) {

          DHTNATPuncher primary_puncher = dht.getNATPuncher();

          if (primary_puncher != null) {

            punchers.add(primary_puncher);

            nat_punchers_ipv4.add(primary_puncher);

            for (int i = 1; i <= 2; i++) {

              DHTNATPuncher puncher = primary_puncher.getSecondaryPuncher();

              punchers.add(puncher);

              nat_punchers_ipv4.add(puncher);
            }
          }
        } else if (net == DHT.NW_MAIN_V6) {

          /*
           * no point in this atm as we don't support v6 tunnels

          DHTNATPuncher puncher = dht.getNATPuncher();

          if ( puncher != null ){

          	punchers.add( puncher );

          	nat_punchers_ipv6.add( puncher );

          	puncher = puncher.getSecondaryPuncher();

          	punchers.add( puncher );

          	nat_punchers_ipv6.add( puncher );
          }
          */
        }
      }

      if (punchers.size() == 0) {

        throw (new Exception("No suitable DHT instances available"));
      }

      for (DHTNATPuncher p : punchers) {

        p.forceActive(true);

        p.addListener(
            new DHTNATPuncherListener() {
              public void rendezvousChanged(DHTTransportContact rendezvous) {
                System.out.println("active: " + rendezvous.getString());

                synchronized (PairingManagerTunnelHandler.this) {
                  if (update_event == null) {

                    update_event =
                        SimpleTimer.addEvent(
                            "PMT:defer",
                            SystemTime.getOffsetTime(15 * 1000),
                            new TimerEventPerformer() {
                              public void perform(TimerEvent event) {
                                synchronized (PairingManagerTunnelHandler.this) {
                                  update_event = null;
                                }

                                System.out.println("    updating");

                                manager.updateNeeded();
                              };
                            });
                  }
                }
              }
            });
      }

      core.getNATTraverser()
          .registerHandler(
              new NATTraversalHandler() {
                private Map<Long, Object[]> server_map =
                    new LinkedHashMap<Long, Object[]>(10, 0.75f, true) {
                      protected boolean removeEldestEntry(Map.Entry<Long, Object[]> eldest) {
                        return size() > 10;
                      }
                    };

                public int getType() {
                  return (NATTraverser.TRAVERSE_REASON_PAIR_TUNNEL);
                }

                public String getName() {
                  return ("Pairing Tunnel");
                }

                public Map process(InetSocketAddress originator, Map data) {
                  if (SRP_VERIFIER == null || !active) {

                    return (null);
                  }

                  boolean good_request = false;

                  try {

                    Map result = new HashMap();

                    Long session = (Long) data.get("sid");

                    if (session == null) {

                      return (null);
                    }

                    InetAddress tunnel_originator;

                    try {
                      tunnel_originator = InetAddress.getByAddress((byte[]) data.get("origin"));

                    } catch (Throwable e) {

                      Debug.out("originator decode failed: " + data);

                      return (null);
                    }

                    System.out.println(
                        "PairManagerTunnelHander: incoming message - session="
                            + session
                            + ", payload="
                            + data
                            + " from "
                            + tunnel_originator
                            + " via "
                            + originator);

                    SRP6Server server;
                    BigInteger B;

                    synchronized (server_map) {
                      Object[] entry = server_map.get(session);

                      if (entry == null) {

                        long diff = SystemTime.getMonotonousTime() - last_server_create_time;

                        if (diff < 5000) {

                          try {
                            long sleep = 5000 - diff;

                            System.out.println("Sleeping for " + sleep + " before starting srp");

                            Thread.sleep(sleep);

                          } catch (Throwable e) {
                          }
                        }

                        server = new SRP6Server();

                        server.init(
                            N_3072,
                            G_3072,
                            SRP_VERIFIER,
                            new SHA256Digest(),
                            RandomUtils.SECURE_RANDOM);

                        B = server.generateServerCredentials();

                        server_map.put(session, new Object[] {server, B});

                        last_server_create_time = SystemTime.getMonotonousTime();

                        total_servers++;

                      } else {

                        server = (SRP6Server) entry[0];
                        B = (BigInteger) entry[1];
                      }
                    }

                    Long op = (Long) data.get("op");

                    if (op == 1) {

                      result.put("op", 2);

                      result.put("s", SRP_SALT);

                      result.put("b", B.toByteArray());

                      good_request = true;

                      if (data.containsKey("test")) {

                        manager.recordRequest(
                            "SRP Test", originator.getAddress().getHostAddress(), true);
                      }
                    } else if (op == 3) {

                      boolean log_error = true;

                      try {
                        long diff = SystemTime.getMonotonousTime() - last_server_agree_time;

                        if (diff < 5000) {

                          try {
                            long sleep = 5000 - diff;

                            System.out.println("Sleeping for " + sleep + " before completing srp");

                            Thread.sleep(sleep);

                          } catch (Throwable e) {
                          }
                        }

                        BigInteger A = new BigInteger((byte[]) data.get("a"));

                        BigInteger serverS = server.calculateSecret(A);

                        byte[] shared_secret = serverS.toByteArray();

                        Cipher decipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

                        byte[] key = new byte[16];

                        System.arraycopy(shared_secret, 0, key, 0, 16);

                        SecretKeySpec secret = new SecretKeySpec(key, "AES");

                        decipher.init(
                            Cipher.DECRYPT_MODE,
                            secret,
                            new IvParameterSpec((byte[]) data.get("enc_iv")));

                        byte[] dec = decipher.doFinal((byte[]) data.get("enc_data"));

                        String json_str = new String(dec, "UTF-8");

                        if (!json_str.startsWith("{")) {

                          log_error = false;

                          throw (new Exception("decode failed"));
                        }

                        JSONObject dec_json = (JSONObject) JSONUtils.decodeJSON(json_str);

                        String tunnel_url = (String) dec_json.get("url");

                        String service_id = new String((byte[]) data.get("service"), "UTF-8");

                        String endpoint_url = (String) dec_json.get("endpoint");

                        boolean ok =
                            createTunnel(
                                tunnel_originator,
                                session,
                                service_id,
                                secret,
                                tunnel_url,
                                endpoint_url);

                        result.put("op", 4);
                        result.put("status", ok ? "ok" : "failed");

                        good_request = true;

                      } catch (Throwable e) {

                        result.put("op", 4);
                        result.put("status", "failed");

                        // filter usual errors on bad agreement

                        if (e instanceof BadPaddingException
                            || e instanceof IllegalBlockSizeException) {

                          log_error = false;
                        }

                        if (log_error) {

                          e.printStackTrace();
                        }
                      } finally {

                        last_server_agree_time = SystemTime.getMonotonousTime();
                      }
                    }

                    return (result);

                  } finally {

                    if (!good_request) {

                      manager.recordRequest("SRP", originator.getAddress().getHostAddress(), false);
                    }
                  }
                }
              });

      SimpleTimer.addPeriodicEvent(
          "pm:tunnel:stats",
          30 * 1000,
          new TimerEventPerformer() {
            public void perform(TimerEvent event) {
              synchronized (tunnels) {
                if (tunnels.size() > 0) {

                  System.out.println("PairTunnels: " + tunnels.size());

                  for (PairManagerTunnel t : tunnels.values()) {

                    System.out.println("\t" + t.getString());
                  }
                }
              }
            }
          });

    } catch (Throwable e) {

      Debug.out(e);

      init_fail = Debug.getNestedExceptionMessage(e);

      manager.updateSRPState();
    }
  }