Pregunta

Tengo un requisito un tanto extraño para poder escuchar varias interfaces de red de Java en una máquina Linux y determinar si una de ellas recibe paquetes UDP de cierto tipo. Los datos de salida que necesito son la dirección IP de la interfaz en cuestión. ¿Hay alguna forma de hacer esto en Java?

Escuchar la dirección comodín (nuevo DatagramSocket (puerto)) no ayuda porque, aunque obtengo los paquetes de difusión, no puedo determinar la dirección IP local de la interfaz por la que pasaron. Escuchar transmisiones mientras está vinculado a una determinada interfaz (nuevo DatagramSocket (puerto, dirección)) no recibe los paquetes en absoluto. Este caso merece un ejemplo de código que muestra lo que estoy tratando de hacer:

Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
  NetworkInterface ni = (NetworkInterface) interfaces.nextElement();
  Enumeration addresses = ni.getInetAddresses(); 
  while (addresses.hasMoreElements()) { 
    InetAddress address = (InetAddress)addresses.nextElement();
    if (address.isLoopbackAddress() || address instanceof Inet6Address) 
      continue; //Not interested in loopback or ipv6 this time, thanks
    DatagramSocket socket = new DatagramSocket(PORT, address);
     //Try to read the broadcast messages from socket here
  }
}

También traté de inicializar el socket con la dirección de difusión construida en base al comienzo de la IP real de la interfaz y el resto de acuerdo con la máscara de red correcta:

byte [] mask = { (byte)255, 0, 0, 0 };
byte[] addrBytes = InetAddress.getByName("126.5.6.7").getAddress();
for (int i=0; i < 4; i++) {
  addrBytes[i] |= ((byte)0xFF) ^ mask[i];
}
InetAddress bcastAddr = InetAddress.getByAddress(addrBytes);

Eso solo arroja una BindException al construir el DatagramSocket.

EDIT: BindException (java.net.BindException: No se puede asignar la dirección solicitada) al llamar al constructor de DatagramSocket con una dirección de difusión (por ejemplo, 126.255.255.255) solo viene con el último Ubuntu 9.04 (probablemente no Ubuntu, pero problema específico de la versión del kernel). Con Ubuntu 8.10 esto funcionó, así como con la versión de Red Hat (RHEL 4.x) con la que estoy tratando.

Aparentemente, no recibir los paquetes mientras está vinculado a una determinada IP local es el comportamiento correcto , aunque en Windows esto funciona. Necesito que funcione en Linux (RHEL y Ubuntu). Con el código C de bajo nivel hay una solución alternativa setsockopt (SO_BINDTODEVICE) que no puedo encontrar en las API de Java. Sin embargo, esto no me hace explotar con optimismo: -)

¿Fue útil?

Solución

Este fue un problema de kernel de Linux IPV6 al final. Por lo general, he deshabilitado IPV6, porque causa todo tipo de dolores de cabeza. Sin embargo, en Ubuntu 9.04 es tan difícil deshabilitar IPV6 que me di por vencido, y eso me mordió.

Para escuchar mensajes de difusión desde una interfaz determinada, primero crearé la "versión de difusión". de la dirección IP de la interfaz:

byte [] mask = { (byte)255, 0, 0, 0 };
byte[] addrBytes = InetAddress.getByName("126.5.6.7").getAddress();
for (int i=0; i < 4; i++) {
  addrBytes[i] |= ((byte)0xFF) ^ mask[i];
}
InetAddress bcastAddr = InetAddress.getByAddress(addrBytes);

Por supuesto, esto realmente no me vincula a una determinada interfaz si muchas interfaces tienen una IP que comienza con la misma parte de red, pero para mí esta solución es suficiente.

Luego creo el datagramsocket con esa dirección (y el puerto deseado), y funciona. Pero no sin pasar las siguientes propiedades del sistema a la JVM:

-Djava.net.preferIPv6Addresses=false -Djava.net.preferIPv4Stack=true 

No tengo idea de cómo IPV6 logra interrumpir la escucha de transmisiones, pero lo hace, y los parámetros anteriores lo arreglan.

Otros consejos

Para volver a expresar su problema, debe determinar en qué interfaz se recibieron los paquetes UDP de difusión.

  • Si se une a la dirección comodín, recibe las transmisiones, pero no hay forma de determinar en qué dirección de red se recibió el paquete.
  • Si se une a una interfaz específica, sabe en qué dirección de interfaz está recibiendo, pero ya no recibe las transmisiones (al menos en la pila TCP / IP de Linux).

Como otros han mencionado, hay bibliotecas de sockets sin procesar de terceros para Java, como RockSaw o Jpcap , que puede ayudarlo a determinar la dirección de la interfaz real.

No estoy seguro si esto ayuda, pero sé que para obtener una lista de todas las interfaces de red:

Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();

¿Quizás puedas unirte a cada uno independientemente?

Acabo de encontrar algunos ejemplos excelentes sobre el uso de getNetworkInterfaces ().

Hasta donde sé, la única forma de hacerlo sería con el

IP_RECVDSTADDR

opción de socket. Se supone que esta opción se asegura de que la dirección dst de la interfaz en la que vino el paquete esté disponible cuando está vinculada a la dirección comodín. Por lo tanto, debería funcionar con la transmisión también, supongo.

Aquí hay un ejemplo de C que tomé de Internet:

Cómo obtener el destino UDP dirección en paquetes entrantes

Leería en recvmsg y luego intente averiguar si esta interfaz está disponible en Java.

Editar:

Me acabo de dar cuenta de que puede tener una opción más si es compatible con Java. Es posible que aún necesite la opción de socket IP_RECVDSTADDR (no está seguro), pero en lugar de usar recvmsg, podría usar un socket sin procesar y obtener la dirección de destino del encabezado IP.

Abra su socket como usando SOCK_RAW y obtendrá el encabezado IP completo al comienzo de cada mensaje, incluidas las direcciones de origen y destino.

Aquí hay un ejemplo del uso de UDP con un socket en bruto en C en Linux:

TCP / IP avanzado: EJEMPLOS DEL PROGRAMA DE ENTRADAS CRUDAS

Me sorprendería si este método no funciona también en Java.

Editar2

Una idea más. ¿Hay alguna razón por la que no puede usar Multicast o una razón específica por la que eligió Broadcast en lugar de Multicast? Según tengo entendido con Multicast, siempre sabrás en qué interfaz se reciben los paquetes, ya que siempre te unes a una interfaz específica cuando te unes a un grupo de Multicast (especialmente con IP4 donde te unes a la interfaz a través de una de sus direcciones IP).

No puedo comentar, así que agregue esto como respuesta.

Eso es interesante. Aunque tengo curiosidad por qué lo haces

byte[] addrBytes = InetAddress.getByName("126.5.6.7").getAddress();

en lugar de solo

byte[] addrBytes = {126, 5, 6, 7);

¿o es que las direcciones de la interfaz te llegan como String?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top