Fragmentado de paquetes con jpcap

De ChuWiki
Saltar a: navegación, buscar

JPcap nos pone un límite en el envío de paquetes UDP de 1472 bytes de datos. Si queremos enviar con Jpcap paquetes más grandes, debemos enviarlos fragmentados. El protocolo IP soporta fragmentado de paquetes, así que podemos hacerlo usando los campos correspondientes de la cabecera IP.

El método setIPv4Parameter() de la clase IPPacket de jpcap nos permite modificar estos campos. Los parámetros a modificar son:

  • boolean rsv_frag : está reservado, debe ser false
  • boolean dont_frag : indica si el paquete no se debe fragmentar. Como estamos fragmentando, debemos poner false.
  • boolean more_frag : Indica si hay más fragmentos pendientes del paquete. Los primeros fragmentos que mandemos deben tener este flag a true. El último debe tenerlo a false.
  • int offset : Es la posición dentro del mensaje global que ocupan los bytes que estemos mandando en este paquete. Ese offset se mide de 8 en 8 bytes. Me explico. Si queremos mandar un total de 32 bytes fragmentado en paquetes de 8 bytes, los offset de cada paquete serían 0, 1 y 2. Si queremos mandar 32 bytes en dos fragmentos de 16, los offset sería 0 y 2
  • int ident : Es un entero cualquiera para identificar el mensaje. Todos los paquetes de un mismo mensaje deben llevar el mismo número y deben ser distintos de los de otros mensajes.

El fragmetado se hace a nivel de paquetes IP, por lo que si queremos enviar paquetes UDP, la cabecera UDP forma parte de los datos a enviar y debemos mezclarla con los nuestros. Para un paquete IP, los datos son cabecera UDP + datos de la aplicación.

La cabecera UDP consta de cuatro campos:

  • Un short con el puerto de orígen del paquete.
  • Un short con el puerto de destino del paquete.
  • Un short con la longitud de los datos, incluida esta misma cabecera UDP, es decir, los byte de datos + 8.
  • Un short con el checksum. En este ejemplo no nos vamos a complicar calculándolo. Baste saber que si ponemos todo cero, los protocolos de red ignoran este campo y no comprueba el checksum.

Si trabajamos desde Java (jpcap) debemo dar la vuelta a los dos bytes de los short, ya que la ordenación de bytes es distinta a la que presupone jpcap que le van a pasar.

El siguiente código, para un microprocesador INTEL en el que debemos alterar el orden de los bytes, construye el array más grande con los 8 bytes de la cabecera UDP. El código es muy mejorable, usando Arrays o alguna otra forma de cambiar los bytes, pero para el ejemplo vale. Es el siguiente:

   // Los datos
   byte [] datos = new byte[] {0x1, 0x2, .... };

   // Array total de bytes los datos + 8 de cabecera UDP
   byte [] cabeceraUdp = new byte[8+datos.length];

   // Rellenamos los 8 primeros bytes con la cabecera UDP.
   // El bucle es para dos bytes (todos los campos son short)
   // Y aprovechamos cada iteración para rellenar el byte correspondiente de
   // cada uno de los cuatro campos.
   short puertoOrigen=2345;
   short puertoDestino=4321;
   int mascara = 0x00ff;
   int longitud = datos.length+8;
   for (int i = 0; i < 2; i++)
   {
      cabeceraUdp[i] = (byte) ((puertoOrigen & mascara) >>> (8 * i));
      cabeceraUdp[i+2] = (byte) ((puertoDestino & mascara) >>> (8 * i));
      cabeceraUdp[i+4] = (byte) ((longitud & mascara)>>>(8*i));
      cabeceraUdp[i+6] = 0;
      mascara = mascara << 8;
   }

   // Damos la vuelta a los bytes de dos en dos.
   for (int i=0;i<8;i+=2){
      byte aux = cabeceraUdp[i];
      cabeceraUdp[i]=cabeceraUdp[i+1];
      cabeceraUdp[i+1]=aux;
   }

   // Copiamos los datos dentro del array grande cabeceraUdp
   for (int i=0;i<datos.length;i++){
      cabeceraUdp[i+8]=datos[i];
   }
   
   datos=cabeceraUdp
   // datos es ahora la cabecera UDP + los datos que queremos enviar.

Ahora vamos a enviar este array de bytes grande en varios paquetes IP.

   private static final int TAMANO_MAXIMO_DATOS_PARA_UDP = 1472;

   ....

      // Todos los paquetes IP del mismo mensaje deben llevar un identificador unico.
      // Aqui elegimos un numero aletario y lo usaremos en todos los paquetes IP.
      short ident = (short) (Math.random() * Short.MAX_VALUE);

      // Bucle para partir el array de datos en grupos de 1472 bytes.
      // offset sera el primer byte del array a enviar
      for (int offset = 0; offset*TAMANO_MAXIMO_DATOS_PARA_UDP < datos.length; offset++)
      {
         // calculo del ultimo byte a enviar.
         int to = (offset + 1) * TAMANO_MAXIMO_DATOS_PARA_UDP >= datos.length ? (datos.length)
               : (offset + 1) * TAMANO_MAXIMO_DATOS_PARA_UDP;

         // copia en un array separado del trozo a enviar.
         byte [] datoParcial = Arrays.copyOfRange(datos, offset
               * TAMANO_MAXIMO_DATOS_PARA_UDP, to);

         // Paquete IP, ver detalle más abajo.
         IPPacket p = new MiIPPacket();
         
         // Se rellenan los parámetros.
         p.setIPv4Parameter(0,
               false,
               false,
               false,
               tos,
               false,
               false,
               (offset + 1) * TAMANO_MAXIMO_DATOS_PARA_UDP < datos.length,  // false si es el ultimo paquete
               offset * TAMANO_MAXIMO_DATOS_PARA_UDP / 8,    // numero del primer byte, dividido por 8
               ident,   // el identificador de mensaje
               10,
               IPPacket.IPPROTO_UDP,    // aunque el paquete es IP, el protocolo que pretendemos usar es UDP
               InetAddress.getLocalHost(),
               InetAddress.getByName(ipDestino));
         p.data = datoParcial;

         // El siguiente trozo entre llaves sólo es necesario si hemos
         // obtenido la instancia de JPCapSender con JPCapSender.openDevice().
         // Si lo hemos obtenido con JPCapSender.openRawSocket(), no hace falta
         // ya que se encarga el sistema operativo de rellenarlo.
         {
            EthernetPacket ether = new EthernetPacket();
            ether.frametype = EthernetPacket.ETHERTYPE_IP;
            ether.src_mac = devices[indiceTarjeta].mac_address;

            InetAddress addressDestino = InetAddress.getByName(ipDestino);

            // Dirección de destino en bytes, presuponemos que será una
            // dirección multicast.
            byte [] bytesIp = addressDestino.getAddress();

            // mac de destino de una dirección multicast. Poner 0xff en
            // todas las posiciones si no se conoce la mac de destino.
            ether.dst_mac = new byte [] { (byte) 0x01, (byte) 0x00,
                  (byte) 0x5e, (byte) (bytesIp[1] & ((byte) 0x7f)),
                  bytesIp[2], bytesIp[3] };

            p.datalink = ether;
         }

         // Envío del paquete. sender es una instancia de JPcapSender.
         sender.sendPacket(p);
      }

Aquí hay un detalle curioso y es que no funcionara. La versión 0.7 de JPcap (última en el momento de escribir esto), tiene aparentemente un bug en el método IPPacket.setIPv4Parameter y es que no hace caso del campo offset, por lo que todos los paquetes saldrán con el offset a cero. Este es el código de dicha clase obtenido de JPcap

       public void setIPv4Parameter(int priority, boolean d_flag, boolean
t_flag,
                       boolean r_flag, int rsv_tos, boolean rsv_frag, boolean dont_frag,
                       boolean more_frag, int offset, int ident, int ttl, int protocol,
                       InetAddress src, InetAddress dst) {
               this.version = 4;
               this.priority = (byte) priority;
               this.d_flag = d_flag;
               this.t_flag = t_flag;
               this.r_flag = r_flag;
               // added by Damien Daspit 5/7/01
               this.rsv_tos = (byte) rsv_tos;
               // *****************************
               this.rsv_frag = rsv_frag;
               this.dont_frag = dont_frag;
               this.more_frag = more_frag;
               offset = (short) offset;       //  ¡¡¡ falta un this delante de offset !!!!
               this.ident = ident;
               this.hop_limit = (short) ttl;
               this.protocol = (short) protocol;
               if(src instanceof Inet6Address || dst instanceof Inet6Address)
                       throw new IllegalArgumentException("Address must be Inet4Address");
               this.src_ip = src;
               this.dst_ip = dst;
       }

Asi que si te fijas en el código para el envío de paquetes, en vez de IPPacket estoy usando MiIPPacket, es una clase hecha para tratar de corregir este error. El código de esa clase es

package com.chudiang.jpacp;

import java.net.Inet6Address;
import java.net.InetAddress;

import jpcap.packet.UDPPacket;

public class MiIPPacket extends IPPacket
{
   @Override
   public void setIPv4Parameter( int priority, boolean d_flag, boolean t_flag,
         boolean r_flag, int rsv_tos, boolean rsv_frag, boolean dont_frag,
         boolean more_frag, int offset, int ident, int ttl, int protocol,
         InetAddress src, InetAddress dst)
   {
      // TODO Auto-generated method stub
      this.version = 4;
      this.priority = (byte) priority;
      this.d_flag = d_flag;
      this.t_flag = t_flag;
      this.r_flag = r_flag;
      // added by Damien Daspit 5/7/01
      this.rsv_tos = (byte) rsv_tos;
      // *****************************
      this.rsv_frag = rsv_frag;
      this.dont_frag = dont_frag;
      this.more_frag = more_frag;

      // Esto es lo del offset cambiado.
      // El "berenjenal" de bits y mascaras es para que funcione en windows
      // intel usa un ordenamiento de bytes distinto del de java y es necesario
      // cambira de orden los dos bytes del short.
      short aux = (short)(offset&0x1fff);  // del offset, segun protocolo IP, sólo se usan los 13 primeros bits.
      this.offset = (short)((aux<<8)&0xff00);
      this.offset = (short)(this.offset | (short)((aux&0xff00)>>8));

      this.ident = ident;
      this.hop_limit = (short) ttl;
      this.protocol = (short) protocol;
      if(src instanceof Inet6Address || dst instanceof Inet6Address)
         throw new IllegalArgumentException("Address must be Inet4Address");
      this.src_ip = src;
      this.dst_ip = dst;
   }
   
}