2009-05-07 5 views
6

Tengo un requisito algo extraño para poder escuchar una serie de interfaces de red desde Java en una máquina Linux y determinar si una de ellas recibe paquetes UDP de una 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?Java en Linux: escuchar mensajes de difusión en una dirección local encuadernada

Escuchar en la dirección de comodín (nuevo DatagramSocket (puerto)) no ayuda porque mientras obtengo los paquetes de difusión, no puedo determinar la dirección IP local de la interfaz que vinieron. Escuchar transmisiones mientras se 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 probé a inicializar el zócalo con la dirección de difusión construida sobre la base del principio 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 sólo arroja una BindException al construir la DatagramSocket.

EDIT: BindException (java.net.BindException: No se puede asignar la dirección solicitada) de llamar al constructor de DatagramSocket con una emisión de direcciones (por ejemplo, 126.255.255.255) sólo viene con la última versión de Ubuntu 9.04 (probablemente no es Ubuntu, pero kernel sin embargo, problema específico de la conversión). Con Ubuntu 8.10 esto funcionó, al igual que con la versión de Red Hat (RHEL 4.x) con la que estoy lidiando.

Al parecer, no recibir los paquetes mientras está vinculado a un determinado IP local es el correct behaviour, aunque en Windows esto funciona. Necesito hacerlo funcionar en Linux (RHEL y Ubuntu). Con código C de bajo nivel hay una solución provisional setsockopt (SO_BINDTODEVICE) que no puedo encontrar en las API de Java. This exactamente no me hace reventar con optimismo, aunque :-)

+0

Parece que el error no se ha solucionado en 10 años. ¡Loca! : D –

Respuesta

4

Esto fue un problema de kernel de IPV6 Linux al final. Normalmente he desactivado IPV6, porque causa todo tipo de dolores de cabeza. Sin embargo, en Ubuntu 9.04 es tan difícil desactivar IPV6 que me rendí, y eso me mordió.

Para escuchar mensajes de difusión a partir de una determinada interfaz, voy a crear por primera vez la "versión de la 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 no realmente me unen a una determinada interfaz si muchas interfaces tienen una IP que comienza con la misma parte de la 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 a las siguientes propiedades del sistema para la JVM:

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

no tengo ni idea de cómo se las arregla para romper IPV6 escuchar emisiones, pero lo hace, y los parámetros anteriores solucionarlo.

0

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

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

Tal vez usted puede unirse a cada uno de forma independiente?

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

+0

Looping a través de interfaces/direcciones es exactamente lo que estoy haciendo, pero el problema es que no puedo escuchar los mensajes de difusión. No obtengo nada con ninguna de las direcciones, solo cuando creo el DatagramSocket con el constructor con el parámetro de puerto simple (usando la "dirección comodín"). – auramo

0

A lo mejor de mi conocimiento la única manera de hacer esto sería con la opción de conector

IP_RECVDSTADDR

. Se supone que esta opción se asegura de que la dirección dst de la interfaz en la que entró el paquete esté disponible cuando se vincula a la dirección de comodín. Entonces, debería funcionar también con transmisión.

Aquí está un ejemplo C cogí fuera de internet:

How to get UDP destination address on incoming packets

Me gustaría leer sobre recvmsg y luego tratar de averiguar si esta interfaz está disponible en Java.

Editar:

Me acabo de dar cuenta que es posible que tenga una opción más si está soportada en Java. Es posible que aún necesite la opción de socket IP_RECVDSTADDR (no estoy seguro), pero en lugar de usar recvmsg puede usar un socket sin formato 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 de destino.

He aquí un ejemplo del uso de UDP con un conector directo en C en Linux:

Advanced TCP/IP - THE RAW SOCKET PROGRAM EXAMPLES

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

Edit2

Una idea más. ¿Hay algún motivo por el que no pueda utilizar la multidifusión o una razón específica por la que elige Transmitir por multidifusión? Por lo que entiendo con Multicast, siempre sabrá en qué interfaz se reciben los paquetes, ya que siempre se vincula a una interfaz específica cuando se une a un grupo de multidifusión (especialmente con IP4 donde se vincula a la interfaz a través de una de sus direcciones IP).

+0

Parece que podría terminar en Java 7 algún día. – auramo

+0

Me acabo de dar cuenta de que puede tener una opción más si es compatible con Java. Eche un vistazo a mi edición. –

+0

El uso de sockets sin formato de Java requeriría código nativo y llamadas JNI o ​​JNA. No quiero ir allí, porque es bastante complicado. Prefiero recurrir a mi plan B, que consiste en configurar estáticamente la interfaz que se utilizará. Tiene problemas de usabilidad, pero supera la opción de código nativo. – auramo

1

Para replantear su problema, debe determinar en qué interfaz se transmitieron los paquetes UDP.

  • Si se asocie a la dirección comodín que reciben las emisiones, pero no hay manera de determinar qué direcciones de red se recibió el paquete.
  • Si se vincula a una interfaz específica, usted 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 terceros conectores directos para Java como RockSaw o Jpcap, que pueden ayudar a determinar la dirección de la interfaz real.

+0

Gracias, verifico esas bibliotecas y veo que tan portátiles son. Lo que quiero es copiar los archivos binarios entre win <-> unix sin preocupaciones. – auramo

0

No se puede comentar, por lo tanto, agregue esto como respuesta en su lugar.

Eso es interesante. Aunque tengo curiosidad por qué haces

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

en lugar de sólo

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

o es que las direcciones de interfaz de llegar a usted como cadena?

+0

Sin razón alguna: tiene razón en que enumerar los bytes en una matriz es más elegante. – auramo

Cuestiones relacionadas