Устранение неполадок при отказе сетевого подключения Java RMI Client - PullRequest
1 голос
/ 08 марта 2012

Это никогда не работало раньше - я настраиваю среду впервые.

НЕСКОЛЬКО ОБНОВЛЕНИЙ НИЖЕ

Мой RMI-сервер запущен в типе процесса «демон», потому что я хочу, чтобы он запускался при запуске системы как служба (без использования функции xinitd), поэтому я могу использовать SSL и давать команды к демону, относящемуся к тому, что входит в реестр через отдельный механизм. (IOW, я не использую что-то вроде Runtime.getRuntime().exec("rmiregistry 2020");)

Я знаю, что сервер RMI запущен и работает, потому что это работает:

telnet localhost <port>

Мой клиентский код кодирует множество переменных среды для SSL в командную строку, чтобы запустить его - позже я сделаю это программно, но сейчас он начинается с:

$ java -Djavax.net.ssl.trustStore=/var/RMIssl/truststore -Djavax.net.ssl.trustStorePassword=<pwd> -Djava.rmi.server.hostname=localhost my.package.Test

Сначала все, что я получил, было:

java.rmi.ConnectException: Connection refused to host: localhost; nested excepti
on is: 
        java.net.ConnectException: Connection refused

, который был в значительной степени бесполезен, за исключением того, что произошла ошибка. Итак, я дважды проверил все вещи и, наконец, добавил несколько операторов трассировки со сбросами и добавил это в командную строку:

-Djavax.net.debug=all

Это очень помогло, но было все еще трудно определить, где именно была ошибка, поэтому я поместил подозреваемую строку в цикл, перехватывая исключение, и теперь ясно, что это поиск конкретного объекта в реестре, который проблема, хотя сообщение об ошибке наверняка вводит в заблуждение. Вот что я получил:

$ java -Djavax.net.debug=all -Djavax.net.ssl.trustStore=/var/RMIssl/truststore -Djavax.net.ssl.trustStorePassword=<pwd> -Djava.rmi.server.hostname=localhost my.package.Test
Setup the security manager.
Find the registry.
Lookup testClient.
keyStore is : 
keyStore type is : jks
keyStore provider is : 
init keystore
init keymanager of type SunX509
trustStore is: /var/RMIssl/truststore 
trustStore type is : jks
trustStore provider is : 
init truststore
adding as trusted cert:
  Subject: CN=   <my cert>
   Issuer: CN=   <my cert>
  Algorithm: RSA; Serial number: 0x<number>
  Valid from  <dates>

trigger seeding of SecureRandom
done seeding SecureRandom
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256
java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
        java.net.ConnectException: Connection refused
enter 'quit' to exit:
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256
java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
        java.net.ConnectException: Connection refused
enter 'quit' to exit: 

Обратите внимание, что структура цикла позволяет лучше анализировать ошибки; вывод относительно склада доверенных сертификатов и моего сертификата (ов) не повторяется.

Это говорит о том, что он не находит подходящего набора шифров для использования, но все это в одной системе! Клиент и сервер одновременно ищут одни и те же хранилища доверенных сертификатов и т. Д.

Если кто-то считает это полезным, вот код клиента:

import java.rmi.*;
import java.rmi.registry.*; 
import java.io.*;
import java.net.*;
import javax.rmi.ssl.*;

public class Test 
{   
   private static String ServerHost = "localhost";
   private static int ServerPort = 6543; private static Registry registry;
   private static testClient c;
   public static void main(String[] args) throws Exception 
   {
      msg("Setup the security manager.");
      System.setSecurityManager(new RMISecurityManager());

      msg("Find the registry.");
      registry = LocateRegistry.getRegistry(ServerHost, ServerPort,
       new SslRMIClientSocketFactory());
      msg("Lookup testClient.");
      boolean keepLooping = true;
      while (keepLooping)
      {
         try
         {
            c = (testClient)registry.lookup(testClient.class.getSimpleName());
         }
         catch (Exception e) 
         {
            msg(e.toString());
         }
         keepLooping = !(Prompt("enter 'quit' to exit: ").equals("quit"));
      }
   }
   public static void msg(String message) 
   {
      System.out.println(message); System.out.flush();
   }
   public static String Prompt(String prompt) 
   {
      String out = ""; 
      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
       try 
      {
         System.out.print(prompt);
         out = br.readLine(); 
      }
      catch (IOException e) 
      {
         msg("IO error reading from standard-in: "+e.toString()); 
      } 
      return out; 
   } 
}

ОБНОВЛЕНИЕ ОДИН:

Следуя своей догадке, я обнаружил переменную окружения, используемую для установки наборов шифров, используемых на фабриках сокетов SslRMI. Итак, я добавил это на стороне сервера:

javax.rmi.ssl.client.enabledCipherSuites="TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256"

Это фактически весь список, который был либо "недоступен", либо "не поддерживается" - возможно, это излишне, но я хочу, чтобы сервер брал все, что есть у клиента.

Это значительно изменило вывод! Тем не менее, он все еще зависает от того же оператора, на этот раз, с отключенной трассировкой, мы получаем:

java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: 
        javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

Когда я добавляю трассировку, есть много данных о том, какой протокол был фактически выбран и что он с этим делает, и в конечном итоге он доходит до этого (сильно обрезается перед этим отрывком):

...
0080: 00 0F 00 10 00 11 00 02   00 12 00 04 00 05 00 14  ................
0090: 00 08 00 16 00 0B 00 02   01 00                    ..........
main, received EOFException: error
main, handling exception: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
main, SEND TLSv1 ALERT:  fatal, description = handshake_failure
main, WRITE: TLSv1 Alert, length = 2
[Raw write]: length = 7
0000: 15 03 01 00 02 02 28                               ......(
main, called closeSocket()
java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: 
        javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

ОБНОВЛЕНИЕ ВТОРОЕ:

По предложению EJP, которого я уважаю в этой теме, из его ответов другим, я проверил предпосылку, что сервер на самом деле не работает с реестром, или, по крайней мере, не в комбинации системы / порта, где клиент ищет.

Чтобы проверить эту гипотезу, я просто запускаю и останавливаю сервер, и поведение клиентов существенно меняется. В случае, когда он НЕ работает, я получаю длинный блок поочередно «Игнорирование недоступных» или «Игнорирование неподдерживаемых» наборов шифров, за которыми следуют их имена, как я уже сообщал в первом случае выше.

Когда сервер работает, клиент дает совсем другой ответ, отправляя мне очень подробную информацию об особенностях создания зашифрованного соединения, как я сообщал в моем первом обновлении выше. Кроме того, в нем говорится, что удаленный хост закрыл соединение во время рукопожатия, поэтому очевидно, что рукопожатие происходило, когда оно прекращалось. Дополнительно я получаю это:

$ netstat -n -l | grep <my port number>
tcp        0      0 :::<my port number>         :::*                        LISTEN

Я думаю, это убедительно ...

ОБНОВЛЕНИЕ ТРИ:

Я заметил, что вывод несколько изменился, поэтому я должен уточнить, и т. Д. В настоящее время, когда я тестирую, и я уверен, что на моем порте есть прослушиватель в моей системе из моего реестра, который (надеюсь) запущен правильно, используятот же файл хранилища доверенных сертификатов и хранилища ключей, что и у клиента, я запускаю клиент и получаю МНОГО информации о трассировке из SSL, включая список шифров, как указано выше, а затем в нем перечисляется RandomCookie, пустой идентификатор сеанса, другой список шифров, некоторыеэллиптические кривые, и выводит кучу байтов, вот где появляется следующий текст:

0080: 00 0F 00 10 00 11 00 02   00 12 00 04 00 05 00 14  ................
0090: 00 08 00 16 00 0B 00 02   01 00                    ..........
[Raw read]: length = 5
0000: 15 03 01 00 02                                     .....
[Raw read]: length = 2
0000: 02 28                                              .(
main, READ: TLSv1 Alert, length = 2
main, RECV TLSv1 ALERT:  fatal, handshake_failure
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is: 
        javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

Было бы неплохо, если бы мы получили чистое сообщение об ошибке, ПОЧЕМУ было отброшено рукопожатие.НОТАБИЛЬНО, я не выяснил, как получить аналогичный вывод с сервера - куда это деваться?Я предоставил ему javax.net.debug = all?!?!

ОБНОВЛЕНИЕ ЧЕТВЕРТАЯ:

Я задавался вопросом, была ли проблема с объектом, который я 'Мы зарегистрированы в Реестре RMI.Мой оригинальный дизайн - создать два объектных интерфейса.Первый получает экземпляр и регистрируется.Его работа заключается в создании других объектов, которые были созданы, но НЕ зарегистрированы;клиент получает первый объект из реестра и запрашивает у него второй, и этот экземпляр является частным для этого клиента.Затем клиент вызывает методы, предоставляемые вторым объектом, которые выполняются на стороне сервера, и результаты передаются обратно.

Чтобы убедиться, что это не играет роли, я создал следующее.Обратите внимание, однако, что это ничего не изменило - оно намного короче и, следовательно, более уместно включить сюда для полного раскрытия:

package <myPackage>;

import <myPackage>.*;
import java.rmi.*;

public interface testobject extends Remote
{
   myAPI.Person getObject() throws RemoteException;
}

Затем я использовал интерфейс следующим образом:

package <myPackage>;

import <myPackage>.myAPI;
import <myPackage>.testobject;
import java.rmi.*; 
import java.rmi.server.*; 
public class testObject extends UnicastRemoteObject implements testobject
{

public testObject() throws RemoteException  
{
   // Set the security manager to the RMI security manager, to prevent
   // the client process from using the server in a malicious way.
   try
   {
      if (System.getSecurityManager() == null)
      {
         System.setSecurityManager(new RMISecurityManager());
      }
   }
   catch(Exception e)
   {
      System.out.println("SecurityManager load error: \n"+e);
   }
}

public myAPI.Person getObject() throws RemoteException
{
   myAPI c = new myAPI();
   myAPI.Person TestObject = c.newPerson();
   TestObject.FirstName = "Test";
   TestObject.LastName = "Object";
   return TestObject;
}

}

Затем я обновил код сервера следующими строками:

   try
   {
      testObject TestObject = new testObject();
      OK = true;
   }
   catch (RemoteException e)
   {
      msg("Error instantiating TestObject: \n"+e.toString());
   }
   if (OK)
   {
      name = testObject.class.getSimpleName();
      try
      {
         registry.bind(name, ServerProcess);
         msg("New testObject has been bound to the RMI registry.");
      }
      catch(Exception e)
      {
         msg("The new testObject could not be bound to the RMI registry: \n"+e.toString());
         OK = false;
      }
   }

И, наконец, клиент с:

 testObject to;
 try
 {
    msg("Now trying a 'testObject'...");
    to = (testObject)registry.lookup(testObject.class.getSimpleName());
    msg("Well! WE DID NOT THROW AN EXCEPTION!");
    person = to.getObject();
    msg("Name: "+person.FirstName+" "+person.LastName);
 }
 catch (Exception e) 
 {
    msg(e.toString());
 }

К сожалению, он по-прежнему выдает (то же самое) исключение.

Продолжая, "хммм ..."

...