6 Uso de Frame
En PDRouter, el tipo Frame representa una trama de red Ethernet. Se utiliza para almacenar una trama completa, incluyendo la cabecera Ethernet y los datos que contiene.
Este documento explica el funcionamiento de los programas test_frame_01.rs y test_frame_02.rs. Están disponibles en pdrouter/src/bin, y su propósito es ilustrar el uso del tipo Frame.
Estos dos programas no están en el directorio src junto con el código de PDRouter, sino en src/bin porque son programas independientes del programa principal. Cargo considera cada fichero .rs en src/bin como un ejecutable independiente. De esta forma podemos utilizarlos para probar y experimentar con Frame sin modificar el programa principal de PDRouter.
Estos programas y otros programas sencillos similares que tendrás que hacer, aunque son binarios independientes de PDRouter, pertenecen al proyecto de PDRouter. Por tanto, nunca los crearemos con cargo new.
Los podemos ejecutar con los comandos de la shell:
cargo run --bin test_frame_01
cargo run --bin test_frame_02Observa que no se debe indicar la extensión
cargo run --bin test_frame_01.rs # ¡ERROR!6.1 Ficheros
El fichero frame.rs contiene la declaración del tipo Frame.
Para utilizarlo desde un programa de prueba debemos incluir los módulos necesarios mediante:
#[path = "../arp.rs"]
mod arp;
#[path = "../eth.rs"]
mod eth;
#[path = "../ipv4.rs"]
mod ipv4;
#[path = "../utils.rs"]
mod utils;
#[path = "../frame.rs"]
mod frame;A continuación importamos el tipo Frame:
use crate::frame::Frame;6.2 Crear una trama Ethernet
La forma más sencilla de crear una trama es utilizar Frame::from_eth().
La función recibe cuatro parámetros:
- dirección MAC de destino;
- dirección MAC de origen;
- tipo de protocolo Ethernet (EtherType);
- contenido de la trama.
Por ejemplo:
let dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
let src = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
let ethertype = [0x12, 0x34];
let payload = [1, 2, 3, 4, 5];
let frame = Frame::from_eth(
dst,
src,
ethertype,
&payload,
);En este ejemplo se crea una trama Ethernet con:
- MAC origen
00:11:22:33:44:55; - MAC destino
ff:ff:ff:ff:ff:ff; - EtherType
0x1234; - cinco bytes de datos.
6.2.1 Valores hexadecimales
En este ejemplo aparecen valores como 0xff y 0x1234. En muchos lenguajes de programación, incluyendo Rust, el prefijo 0x indica que el número está escrito en hexadecimal, es decir, en base 16. Como sabes, el sistema hexadecimal utiliza los símbolos del 0 al 9 y las letras a a f, que representan los valores del 10 al 15. Es especialmente cómodo para trabajar con datos de red porque cada dígito hexadecimal representa exactamente cuatro bits. Por tanto, dos dígitos hexadecimales representan un byte.
Por ejemplo:
0x00 = 0000 0000
0x0f = 0000 1111
0xff = 1111 1111
Por este motivo es habitual representar en hexadecimal direcciones MAC, tipos Ethernet y otros campos que están formados por bytes.
El valor 0x1234 no corresponde a ninguno de los protocolos que actualmente interpreta Frame. Lo utilizamos en este primer ejemplo porque permite centrarnos en la estructura básica de una trama Ethernet.
6.3 Acceso a la información Ethernet
Una vez creada la trama podemos obtener una vista de ella desde el punto de vista de Ethernet mediante eth().
6.3.1 El concepto de vista
Una vista es una forma de acceder a unos datos que ya existen sin crear una copia de ellos. Por ejemplo, podemos tener una colección grande de datos y crear una vista que permita consultar solamente una parte de esa colección, trabajando directamente sobre los datos originales.
En Rust este concepto aparece con mucha frecuencia. Los tipos que contienen referencias, como &[u8], permiten acceder a datos que pertenecen a otro objeto sin copiarlos. EthView utiliza precisamente este mecanismo: la vista mantiene una referencia a los datos de Frame.
El uso de vistas resulta especialmente útil en PDRouter porque una trama puede contener muchos bytes, mientras que normalmente solo queremos consultar o interpretar una parte de ellos. Podemos hacerlo sin duplicar los datos de la trama.
6.3.2 La vista EthView
En los ejemplos usamos la asignación
let eth = frame.eth();El resultado es un EthView. Este tipo está definido en el fichero eth.rs y permite acceder a la información de la cabecera Ethernet de una trama Frame.
EthView no contiene una copia de los datos de la trama. Internamente contiene una referencia a ellos, de modo que podemos consultar los datos originales mediante los métodos que proporciona EthView, como src_mac(), dst_mac() o ether_type().
Por tanto, Frame es el objeto que contiene los datos de la trama, mientras que EthView proporciona una forma de interpretarlos y consultarlos desde el punto de vista de Ethernet:
Frame → contiene los datos de la trama
EthView → permite consultar esos datos como una trama Ethernet
6.3.3 Dirección MAC de origen
let src = eth.src_mac();
println!("Origen: {:?}", src);src_mac() devuelve la dirección MAC de origen.
6.3.4 Dirección MAC de destino
let dst = eth.dst_mac();
println!("Destino: {:?}", dst);dst_mac() devuelve la dirección MAC de destino.
6.3.5 Tipo Ethernet
let ethertype = eth.ether_type();
println!("Tipo: {:02x?}", ethertype);ether_type() devuelve los dos bytes que identifican el protocolo transportado por Ethernet.
6.3.6 Contenido de la trama
El contenido que aparece después de la cabecera Ethernet se puede obtener mediante payload():
let payload = eth.payload();
println!("Payload: {:?}", payload);payload() devuelve directamente los bytes contenidos en la trama. No intenta interpretar esos bytes.
Por tanto, tenemos una distinción importante:
eth.payload() → datos sin interpretar
eth.show() → descripción legible de los datos
6.4 Comprobar el protocolo
EthView proporciona también dos funciones que permiten comprobar si la trama contiene alguno de los protocolos conocidos:
println!("¿Es IPv4?: {}", eth.is_ipv4());
println!("¿Es ARP?: {}", eth.is_arp());Estas funciones devuelven un valor bool.
Por ejemplo, en nuestro primer ejemplo:
¿Es IPv4?: false
¿Es ARP?: false
Esto se debe a que hemos utilizado deliberadamente el EtherType 0x1234.
6.5 Mostrar una trama
También podemos obtener una descripción completa de la trama mediante show():
println!("{}", frame.show());show() devuelve un String. No escribe directamente en la salida estándar.
Por ejemplo, para nuestra trama obtendremos algo parecido a:
Frame
Longitud: 19 bytes
Ethernet
Origen: 00:11:22:33:44:55
Destino: ff:ff:ff:ff:ff:ff
Tipo: desconocido (0x1234)
Podemos utilizar show() tanto desde Frame como desde EthView:
println!("{}", frame.show());
let eth = frame.eth();
println!("{}", eth.show());En ambos casos obtenemos la representación textual de la trama.
Frame::show() es simplemente una forma cómoda de solicitar esta información directamente a la trama.
6.6 Tramas con protocolos conocidos
El ejemplo anterior utilizaba un EtherType desconocido. Sin embargo, una trama Ethernet normalmente transporta información perteneciente a algún protocolo de red.
Frame reconoce actualmente, entre otros, los siguientes casos:
Ethernet
├── ARP
└── IPv4
└── ICMP
Cuando show() encuentra una trama ARP o IPv4, continúa interpretando su contenido.
Por ejemplo, en una trama IPv4 que contiene ICMP, la información se muestra de forma jerárquica:
Frame
Ethernet
...
Tipo: IPv4
IPv4
Origen: ...
Destino: ...
TTL: ...
Protocolo: ICMP
ICMP
Tipo: Echo Request
Esto permite inspeccionar una trama completa sin tener que acceder manualmente a cada uno de los bytes que forman las diferentes cabeceras.
6.7 Visualización del contenido de la trama
El programa test_frame_01.rs contiene una trama con una carga de prueba, no realista. Mientras que test_frame_02.rs ilustra un caso más realista, con la misma API, pero construye diferentes tipos de tramas para comprobar esta interpretación. Entre ellas se incluyen:
- una petición ARP;
- un ICMP Echo Request;
- un ICMP Echo Reply;
- un ICMP Network Unreachable;
- un ICMP Host Unreachable;
- un ICMP Time Exceeded.
Se puede observar cómo una misma operación:
println!("{}", frame.show());obtiene información cada vez más detallada a medida que la trama contiene protocolos que Frame sabe interpretar.