Emulación en NS3

El proceso de simulación en el simulador NS3 es el más ampliamente utilizado. Ya lo hemos visto en entradas anteriores. Una característica menos conocida y menos utilizada que te permite NS3 es la emulación. Efectivamente, NS3 te permite crear un escenario (nodos, pila de protocolos y aplicaciones) y que el tráfico generado por ese escenario «salga» al mundo real. Por ejemplo, podemos crear un sensor en ns3 que genere tráfico de forma que el servidor que recoge la información no distinga si es un sensor real o no. Las aplicaciones de la emulación son muy numerosas, por ejemplo, para testear servicios software reales , probar configuraciones, crear perfiles de tráfico, etc.

Vamos a ver un ejemplo de cómo crear este tipo de escenarios, y luego iremos viendo cosas concretas. Nuestro objetivo es generar tráfico LoraWan de un nodo a una pasarela en NS3 y que ese tráfico salga por una interfaz virtual que se crea en el ordenador que ejecuta la simulación.

La parte de la simulación LoraWan ya la hemos visto y tenemos un ejemplo en los repositorios de este blog. Sigue las instrucciones para hacer el ejemplo si no lo has hecho todavía.

Vamos a partir de ese ejemplo de LoraWan básico que constaba de un nodo lora representando un sensor y una pasarela o hub que recibía ese tráfico.

Bien, vamos a coger ese ejemplo y vamos a modificarlo para que ese tráfico lora salga por una interfaz de red virtual que se crea en el ordenador que ejecuta la simulación. Para ello necesitamos dos cosas, una es añadir un nodo que denominaremos «fantasma» que recoja el tráfico de la red que llega a la pasarela LoraWan y la saque por la interfaz virtual. El otro elemento que necesitamos es una aplicación que reciba el tráfico de la interfaz LoRa y lo reenvíe por una interfaz CSMA (Ethernet). Esa aplicación la instalaremos en la pasarela LoraWan y le añadiremos una interfaz CSMA. Al otro extremo de la interfaz CSMA conectaremos el nodo ghost que será al que tendremos que conectar la interfaz virtual del tipo «TapBridge».

Para ello, creamos una interfaz tapBridge, con su asistente:

TapBridgeHelper tapBridge;
tapBridge.SetAttribute ("Mode", StringValue ("ConfigureLocal"));
tapBridge.SetAttribute ("DeviceName", StringValue ("virtualtwin"));

Ahora vamos a crear un asistente para crear una red Ethernet que conecte el Ghostnode, y el hub que recoge el tráfico LoraWan, éste ghostnode será el que cree la interfaz virtual. Podríamos hacer que el hub que recoge el tráfico LoraWan creara la interfaz virtual, pero realmente los gateways/hub realmente recogen el tráfico y lo reenvian a una interfaz Ethernet y queríamos modelar también ese paso:


NodeContainer ghostNode;
ghostNode.Create(1);
ghostNode.Add(hub.Get(0));
CsmaHelper csma;
csma.SetChannelAttribute ("DataRate", StringValue ("100Mbps"));
csma.SetChannelAttribute ("Delay", TimeValue (NanoSeconds (6560)));

NetDeviceContainer hubDevice = csma.Install(ghostNode);

Básicamente se añade el hub (que ya lo habíamos creado previamente) al nuevo contenedor de nodos y se conectan todos a un canal csma. La expresión hub.Get(0) devuelve el nodo en la posición 0 del asistente de creador de nodos o contenedor de nodos, hub. Ese contenedor sólo tenía un nodo.

Asignamos la dirección IP en ese canal:

InternetStackHelper stack;
stack.Install (ghostNode);
Ipv4AddressHelper address;
address.SetBase ("10.0.0.0", "255.255.255.0");
Ipv4InterfaceContainer p2pInterfaces;
p2pInterfaces = address.Assign(hubDevice);

tapBridge.Install(hub.Get(0),hubDevice.Get(0));

y ya solo nos falta instalar las aplicaciones pertinentes, la primera es la aplicación que simulará el tráfico del sensor, ya la hemos visto:

PeriodicSenderHelper periodicSenderHelper;
periodicSenderHelper.SetPeriod (Seconds (12));
periodicSenderHelper.Install (nodes);

También necesitamos una aplicación que recoja toda la información que le llega a la interfaz lorawan del hub y la reenvíe por la interfaz csma que le hemos añadido. Por defecto, esa aplicación no existe, por lo que la hemos tenido que crear junto con su asistente para instalarlo. En otra entrada de blog la analizaremos, por el momento vamos a ver que se «instala» en el hub como otra aplicación cualquiera:

ForwardercdmaHelper ForwardercdmaHelper;
ForwardercdmaHelper.Install (hub);

Hemos incluido el código de esa aplicación (ForwaredercdmaHelper) en el ejemplo de esta entrada.

Al ejecutar la simulación podemos ver cómo se genera una interfaz «virtualtwin» mediante el comando ifconfig.

Created with GIMP

En esa interfaz sale todo el tráfico que mandemos al nodo ghost, entre ellos, el tráfico lorawan que hemos reenviado en el hub.

El ejemplo completo lo podemos encontrar en el directorio emulación del repositorio github

Creando un nuevo módulo en NS3

[Actualizado a cmake, versión 3.36.1 (documentación oficial ns3.36)]
Crear un nuevo protocolo o algoritmo en NS3 es un paso que implica la creación de un nuevo módulo. Obviamente, a partir del tipo de protocolo o algoritmo a implementar, los pasos a seguir y los protocolos que nos servirán como referencia cambiarán. En cualquier caso, siempre necesitaremos generar un nuevo módulo e incluirlo en la compilación de NS3 para poder hacer simulaciones con posterioridad.

Para crear un nuevo módulo, NS3 nos proporciona un script python, create-module.py, dentro de la carpeta src de nuestra instalación de ns3.
Si por ejemplo queremos crear un nuevo protocolo inalámbrico, pongamos el protocolo weightlessp, si ejecutamos:

$utils/create-module.py weightless
Creating module /home/felix/tools/ns-3-dev-git/contrib/weightless
Successfully created new modules
Run './ns3 configure' to include them in the build

Se nos crea la plantilla dentro de src:

$cd contrib/weightless/
felix@homer-esi:~/tools/ns-3-dev-git/contrib/weightless$ ls
CMakeLists.txt doc examples helper model test

donde vemos las carpetas habituales para meter la documentación, los ejemplos, los asistentes, el modelo propiamente dicho y los test. También me genera el archivo CMakeList.txt que utiliza ns3 para compilar los módulos. Para ver que se incluye correctamente en el proceso de compilación:

$./ns3 configure --enable-example --enable-test
$./ns3 build
$./test.py -s weightlessp
.
PASS: TestSuite weightlessp
1 of 1 tests passed (1 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)
.

Ahora falta, obviamente, lo difícil, que es programar el modelo propiamente dicho identificando cada una de las partes. Lo más recomendable es estudiar cuidadosamente un protocolo existente de la misma capa que el que queremos programar y copiar su estructura adaptando el comportamiento a la funcionalidad deseada.

Argumentos de entrada en simulaciones de NS3

Para ahorrarte trabajo, puedes configurar mediante argumentos de entrada muchos parámetros o atributos de tu simulación, tanto de los módulos que usas de NS3, como definir tus propios parámetros. Por ejemplo, si vas a simular el tráfico en una red lorawan con topología en estrella, con la pasarela en el medio, puedes poner como parámetro configurable el número de nodos o el radio de la estrella. Este ejemplo es el que vamos a utilizar para explicar cómo gestionar los argumentos de entrada. El archivo que usaremos es el lorawan.cc del repositorio. Necesitarás instalar el módulo de lorawan como indicamos aquí

Si copiamos el archivo lorawan.cc al directorio scratch de nuestra instalación NS3 podemos ver qué argumentos podemos definir por la linea de comandos mediante la opción –PrintHelp, esto es:

$ ./waf --run "lorawan --PrintHelp"
lorawan [Program Options] [General Arguments]
[...]
Program Options:
--radio: Radio of the disc where random put the nodes [20]
--numnodes: Num. nodes in the grid for simulating [20]


General Arguments:
--PrintGlobals: Print the list of globals.
--PrintGroups: Print the list of groups.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintTypeIds: Print all TypeIds.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintHelp: Print this help message.

Como podemos ver, después de los mensajes de compilación (sustituidos por […]), podemos ver que el programa lorawan tiene definidos dos opciones, el radio y el numnodes, indicando el radio de la estrella y el número de nodos respectivamente. El número por defecto en ambos casos y si no indicamos esas opciones es 20. Si queremos simular 3 nodos en una topología en estrella con un radio de 10 metros, solo tendremos que indicarlo en línea de argumentos:
$ ./waf --run "lorawan --numnodes=3 --radio=10"
[...]

Si analizamos el archivo lorawan.cc, la definición de argumentos es relativamente sencilla, en primer lugar se indica que vamos a parsear la línea de comandos en busca de atributos y se definen las variables que almacenarán los valores que se pasen por línea de comandos:

#include "ns3/command-line.h"
[...]
double radio = 20.0;
int numnodes = 20;

A continuación se definen los valores esperados en línea de argumentos, en nuestro ejemplo, lo hacemos mediante una función que definimos antes del main y añadimos al final. En esta función creamos la variable que almacenará y parseará los argumentos y añadimos nuestros dos argumentos esperados mediante AddValue:

CommandLine setupcmd(){
CommandLine cmd;
cmd.AddValue ("radio", "Radio of the disc where random put the nodes", radio);
cmd.AddValue ("numnodes", "Num. nodes in the grid for simulating",numnodes);
return cmd;
}

como puedes ver indicamos el nombre, una pequeña ayuda y las variables que van a almacenar los valores.
Finalmente, parseamos la línea de comandos:

[..]
CommandLine cmd =setupcmd();
cmd.Parse (argc, argv);
[..]

Con lo que en las variables definidas a tal efecto, guardaremos los valores que le pasemos.

Aparte de poder definir nuestros propios argumentos de entrada para configurar nuestras simulaciones, muchos módulos que usamos tienen sus propios atributos configurables por líneas de comandos. Por ejemplo, si queremos ver un listado de todos los módulos disponibles, podemos verlos con la opción –PrintTypeIds

./waf --run "lorawan --PrintTypeIds"
Waf: Entering directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.112s)
Registered TypeIds:
ns3::A2A4RsrqHandoverAlgorithm
ns3::A3RsrpHandoverAlgorithm
[..]
ns3::LoraChannel
ns3::LoraInterferenceHelper
ns3::LoraNetDevice
ns3::LoraPhy
ns3::LoraRadioEnergyModel
ns3::LoraTag
ns3::LoraTxCurrentModel
[...]

En esa larga lista, podemos explorar qué atributos son configurables mediante la opción –PrintAttributes y el identificador o nombre del módulo. Por ejemplo, si queremos saber qué atributos puedo especificar mediante línea de comandos del módulo de ns3 LoraRadioEnergyModel, podemos ejecutar:

$ ./waf --run "lorawan --PrintAttributes=ns3::LoraRadioEnergyModel"
Waf: Entering directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Waf: Leaving directory `/home/felix/tools/ns-allinone-3.30/ns-3.30/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.063s)
Attributes for TypeId ns3::LoraRadioEnergyModel
--ns3::LoraRadioEnergyModel::RxCurrentA=[0.0112]
The radio Rx current in Ampere.
--ns3::LoraRadioEnergyModel::SleepCurrentA=[1.5e-06]
The radio Sleep current in Ampere.
--ns3::LoraRadioEnergyModel::StandbyCurrentA=[0.0014]
The default radio Standby current in Ampere.
--ns3::LoraRadioEnergyModel::TxCurrentA=[0.028]
The radio Tx current in Ampere.
--ns3::LoraRadioEnergyModel::TxCurrentModel=[0]
A pointer to the attached tx current model.



Donde podemos ver que podemos definir la corriente de envío, recepción, dormir, etc.

Programando en NS3

NS3 puede tener una curva de aprendizaje considerable dado que es un simulador de eventos discretos centrado en redes y, por lo tanto, tiene mucha variedad de combinaciones

Para programar cualquier ejemplo, ten en cuenta los siguientes consejos para acortar el tiempo de aprendizaje:

  • Por supuesto, el tutorial oficial es el mejor punto de partida
  • Los ejemplos (en el directorio examples) y los test (en el directorio test de cada módulo en el directorio src) del propio ns3 son la mejor fuente de información y puedes utilizarlos como plantillas para comenzar a programar
  • Para muchas de las tareas de configuración existen clases asistentes (terminada en *helper). MobilityHelper, BasicEnergySourceHelper, OlsrHelper, etc.
  • Genera los resultados en algún estándar como pcap y podrás usar herramientas externas para analizar tus resultados.
  • Utiliza un objeto de la clase NodeContainer para guardar todos tus nodos y luego utilizarlo para aplicar las mismas operaciones a todos tus nodos. Por ejemplo, poner un mismo tipo de netdevice a 100 nodos
  • A la hora de depurar, puede ser interesante habilitar el sistema de log de los diversos componentes para obtener mas información: por ejemplo LogComponentEnable («UdpClient», LOG_LEVEL_INFO); habilita los mensajes del componente UdpClient al nivel de info. Hay varios niveles de log. Pero ten cuidado, a mas log mas lenta va la simulación
  • Estructura tu simulación de forma que los parámetros que quieras cambiar entre simulación y simulación puedan ser pasados como argumento. Aquí tienes un ejemplo de cómo hacerlo.
  • Separa la generación de resultados, del procesamiento y análisis de los resultados. De esta forma, no tendrás que ejecutar toda la simulación de nuevo para cambiar algo del análisis
  • Para crear un nuevo modelo, lee cuidadosamente la parte del manual de soporte
  • Aunque hay muchos módulos, generalmente solo hay que configurar los módulos comunes y estructurar tu escenario de simulación. ¡Puedes ser productivo en un tiempo relativamente corto!

    Simulaciones

    Una simulación es una representación simplificada de la realidad ejecutada en un computador. Esta simulación se hace con el ánimo de estudiar y analizar un aspecto concreto que, con el análisis adecuado, se puede exportar a cómo se comportará la realidad.

    ¿Para qué usamos las simulaciones?, bueno, a menudo para ahorrarnos tiempo y dinero en estudiar el comportamiento de diversos parámetros de la vida real. Otras veces por que sencillamente, no podemos probar algo en la vida real pero queremos ver qué pasaría.

    En redes de computadores es habitual simular redes para probar nuevos protocolos, estudiar la viabilidad de nuevas topologías, estudiar que pasa si se rompe un enlace, estudiar las prestaciones de algoritmos, y un largo etc.

    Mapa conceptual simulación

    La simulación de redes de computadores pretende simular topologías y tráfico de red sin tener que desplegar dichas redes físicamente.

    Tal y como podemos ver en el mapa conceptual, abajo a la derecha en naranja, cuando queremos simular algo el primer paso es hacer las preguntas correctas. Esto es importantísimo por que no podemos simularlo todo con mucha precisión por lo que debemos simplificar aquellos aspectos que no son relevantes. Por ejemplo, si queremos analizar las prestaciones de un algoritmo de enrutado en cuanto a si encuentra rutas óptimas o no, el canal de comunicación puede ser muy simple y nunca perder paquetes si no es relevante para lo que queremos analizar. Simular un canal de comunicación sin errores siempre es mas simple y sencillo que no un canal de comunicación mas realista y con errores.

    Cuando tenemos claro qué vamos a probar/mejorar o qué queremos estudiar, necesitamos hacer un modelo, este modelo puede ser teórico (es decir, ecuaciones) o simulado (es decir, simulación). El modelo simulado lo podemos simular y producto de esta simulación obtendremos unos resultados en crudo que debemos analizar e interpretar.

    Los resultados de la simulación se deben validar, es decir debemos asegurarnos que son correctos, comparándolos con resultados experimentales de parámetros reales y/o con los resultados de un modelo teórico. Solo después de esta validación podremos decir que nuestro modelo de simulación se acerca a la realidad y es válido.

    Conceptos básicos NS3

    El simulador ns3 es un simulador de eventos discretos utilizado ampliamente en investigación y en docencia.
    Se pueden desarrollar todo tipo de simulaciones relativas a tecnologías, protocolos y aplicaciones atendiendo a la configuración que hagamos en un archivo C++.

    Los elementos básicos de una simulación vienen representados por clases c++ que simulan los elementos básicos hardware/software de un escenario real, son los siguientes:

    1. Node: representa un dispositivo (computador, servidor, teléfono móvil, portátil, sensor, cámara, etc.)
    2. Channel: representa un canal de comunicación y su comportamiento (cable, inalámbrico, punto a punto, etc.)
    3. Net Device : Dispositivo de red, es una tarjeta de red inalámbrica (e.j wifi) o cableada (e.j ethernet)
    4. Application: es una aplicación software. Desde el punto de vista de una simulación es un generador/consumidor
      de paquetes de información. Un servidor web, un navegador, etc. son ejemplos de aplicaciones
    5. Protocol stack: es una pila de protocolos de comunicación. El mas conocido, la pila de protocolos TCP/IP que hace posible la comunicación en Internet
    6. El simulador propiamente dicho: se encarga de lanzar y gestionar la simulación.

    Con estos cinco elementos podemos simular cualquier escenario de red. En nuestro archivo de C++ debemos tener estos elementos configurados apropiadamente para simular un escenario real.
    Cuando analizemos un escenario ns3 veremos que en C++ vamos realizando los mismos pasos que haríamos en un escenario real.

    1. Creamos los objetos C++ que representen a los nodos, el canal, la aplicación y la pila de protocolos que necesitemos
    2. A los nodos, les «instalamos» los dispositivos de red (Net Devices)
    3. «Conectamos» el channel a los dispositivos de red
    4. Instalamos en cada nodo la pila de protocolos y la configuramos apropiadamente. En TCP/IP le damos la dirección IP, máscara de subred, etc.
    5. Instalamos en cada nodo la aplicación y la configuramos
    6. Configuramos y lanzamos la simulación

    Hay elementos secundarios que nos facilitan la labor de gestionar simulaciones complejas (argumentos, log, configuración de muchos elementos) con
    muchos nodos y/o aplicaciones.