Учебник по симулятору сети ns-3. Глава 5

Учебник по симулятору сети ns-3. Глава 5
главы 1,2
глава 3
глава 4

5 Настройка
5.1 Использование модуля журналирования
5.1.1 Обзор журналирования
5.1.2 Разрешение журналирования
5.1.3 Добавление журналирования в ваш код
5.2 Использование аргументов командной строки
5.2.1 Переопределение значений атрибутов по умолчанию
5.2.2 Захват ваших собственных команд
5.3 Использование системы трассировки
5.3.1 ASCII Трассировка
Парсинг ASCII трасс
5.3.2 Трассировка PCAP

Глава 5

Настройка

5.1 Использование модуля журналирования

Мы уже вкратце рассмотрели модуль журналирования ns‑3, просматривая скрипт first.cc. В этой главе мы более пристально присмотримся к возможным вариантам использования подсистемы журналирования.

5.1.1 Обзор журналирования

Многие большие системы поддерживают какое-то средство регистрации сообщений, и ns‑3 не является исключением. В некоторых случаях, в «консоль оператора» (которая обычно является stderr в системах на основе Unix) записываются только сообщения об ошибках. В других системах, могут выводиться предупреждающие сообщения, а также более подробная информация. В некоторых случаях, средства журналирования используются для вывода отладочных сообщений, которые могут быстро «замылить» вывод.

Подхрд, используемый в ns‑3, предполагает, что все эти уровни информативности полезны, и мы предоставляем избирательный, многоуровневый подход к регистрации сообщений. Журналирование может быть полностью отключено, включено для отдельных компонентов или в глобальном масштабе. Для этого служат настраиваемые уровни информативности. Модуль журналирования ns‑3 обеспечивает относительно простой способ получения полезной информации из вашей симуляции.

Вы должны понимать, что мы предоставляем механизм общего назначения — трассировку — для извлечения данных из ваших моделей, который должен быть предпочтительным для вывода при моделировании (для получения более подробной информации о нашей системы трассировки см. раздел учебника 5.3). Журналирование должно быть предпочтительным методом получения отладочной информации, предупреждений, сообщений об ошибках или для быстрого вывода сообщений из ваших сценариев или моделей в любой момент.

В настоящее время в системе определено семь уровней (типов) сообщений журнала по возрастанию информативности.

  • LOG_ERROR — регистрация сообщений об ошибках (связанный макрос: NS_LOG_ERROR);
  • LOG_WARN — регистрация предупреждающих сообщений (связанный макрос: NS_LOG_WARN);
  • LOG_DEBUG — регистрация относительно редких специальных сообщений отладки (связанный макрос: NS_LOG_DEBUG);
  • LOG_INFO — регистрация информационных сообщений о ходе выполнения программы (связанный макрос: NS_LOG_INFO);
  • LOG_FUNCTION — регистрация сообщений, описывающих каждую вызванную функцию (два связанных макроса: NS_LOG_FUNCTION, используемый для функций-членов, и NS_LOG_FUNCTION_NOARGS, используемый для статических функций);
  • LOG_LOGIC — регистрация сообщений, описывающих логический поток внутри функции (связанный макрос: NS_LOG_LOGIC);
  • LOG_ALL — регистрация всего упомянутого выше (связанного макроса нет).
    Для каждого типа (LOG_TYPE) есть также тип LOG_LEVEL_TYPE, который, если используется, позволяет регистрировать в дополнение к его собственному уровню все уровни над ним. (Как следствие, LOG_ERROR и LOG_LEVEL_ERROR, а также LOG_ALL и LOG_LEVEL_ALL функционально эквивалентны.) Например, включение LOG_INFO будет разрешать только сообщения предоставляемые макросом NS_LOG_INFO, при включении LOG_LEVEL_INFO также будут включены сообщения, предоставленные макросами NS_LOG_DEBUG, NS_LOG_WARN и NS_LOG_ERROR.

Мы также предоставляем макрос безусловного журналирования, который отображается всегда, независимо от уровня ведения журнала или компонента выбор.

  • NS_LOG_UNCOND — безусловная регистрация связанного сообщения (без связанного уровня журналирования).

Каждый уровень может быть запрошен по отдельности или кумулятивно. Журналирование можно настроить с помощью переменной sh-среды NS_LOG или путем регистрации вызова системной функции. Как было показано ранее, система журналирования имеет Doxygen-документацию и сейчас самое время её просмотреть, если вы это еще не сделали.

Теперь, когда вы прочитали документацию очень подробно, давайте воспользуемся этими знаниями, чтобы получить некоторые интересную информацию из примера сценария scratch/myfirst.cc, который вы уже компилировали.

5.1.2 Разрешение журналирования

Давайте используем переменную окружения NS_LOG, чтобы запустить еще несколько журналов, но сначала, просто для того, чтобы сориентироваться, запустите последний скрипт, как вы делали ранее,

$ ./waf --run scratch/myfirst

Вы должны увидеть уже знакомый вывод первой программы-примера ns‑3

$ Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build' 'build'
finished successfully (0.413s)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

Оказывается, что «отправленные» и «полученные» сообщения, которые вы видите выше, на самом деле это зарегистрированные сообщения от UdpEchoClientApplication и UdpEchoServerApplication. Например, мы можем попросить клиентское приложение, распечатать дополнительную информацию, установив ее уровень журналирования через переменную окружения NS_LOG.

С этого момента я собираюсь предположить, что вы используете sh-подобную оболочку, которая использует синтаксис «VARIABLE = value». Если вы используете csh-подобную оболочку, тогда вам придется преобразовать мои примеры в синтаксис «переменное значение setenv» требуется этими оболочками.

В данный момент, приложение UDP эхо-клиента отвечает на следующую строку кода в scratch/myfirst.cc,

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);

Она активирует уровень журналирования LOG_LEVEL_INFO. Когда мы передаем флаг уровня журналирования, мы на самом деле включаем данный уровень и все более низкие уровни. В данном случае мы включили NS_LOG_INFO, NS_LOG_DEBUG, NS_LOG_WARN и NS_LOG_ERROR. Мы можем повысить уровень журналирования и получить больше информации, без изменений скрипта и перекомпиляция, путем установки переменной окружения NS_LOG следующим образом:

$ export NS_LOG=UdpEchoClientApplication=level_all

Так мы устанавливаем следующее значение переменной NS_LOG sh-оболочки,

UdpEchoClientApplication=level_all

Левая часть присвоения — это имя журналируемого компонента, который мы хотим настроить, а правая часть — это флаг, который мы хотим для этого применить. В данном случае мы собираемся включить в приложении все уровни отладки. Если вы запустите скрипт с установленной таким образом NS_LOG, система журналирования ns‑3 примет изменения и вы должны увидеть следующий вывод:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()

Дополнительная отладочная информация, предоставляемая приложением, теперь соответствует уровню NS_LOG_FUNCTION. Он показывает каждый случай вызова функций во время выполнения скрипта. Как правило, в функциях-методах предпочтительно использовать (как минимум)NS_LOG_FUNCTION (this). Используйте NS_LOG_FUNCTION_NOARGS ()
только в статических функциях. Тем не менее, обратите внимание, что в системе ns‑3 нет требований поддерживать какую-либо функциональность журналирования. Решение о том, сколько информации регистрируется, остается индивидуально за разработчиком модели. В случае эхо-приложений доступно большое количество выходных данных для журналирования.

Теперь вы можете просмотреть журнал вызовов функций, которые были сделаны приложением. Если вы приглядитесь внимательно, то заметите двоеточие между строкой UdpEchoClientApplication и именем метода, в том месте, где вы возможно ожидали увидеть оператор области видимости C++ (: :). Это сделано намеренно.

На самом деле это не имя класса, а имя компонента журналирования. Когда есть соответствие между исходным файлом и классом, обычно это имя класса, но вы должны понимать, что на самом деле это не имя класса, и там одно двоеточие вместо двойной двоеточия. Это способ помочь вам относительно тонким способом концептуально отделить имя компонента журналирования от имени класса.

Тем не менее, в некоторых случаях бывает сложно определить, какой метод на самом деле генерирует сообщение журнала. Если вы посмотрите на текст выше, то у вас возникнет вопрос, откуда взялась строка «Received 1024 bytes from 10.1.1.2». Вы можете решить эту проблему, установив уровень prefix_func в переменную окружения NS_LOG. Попробуй сделать следующее,

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'

Обратите внимание, что кавычки необходимы, поскольку вертикальная черта, которую мы используем для обозначения операции ИЛИ, также является соединителем конвейеров Unix. Теперь, если вы запустите сценарий, вы увидите, что система журналирования гарантирует, что каждое сообщение из данного журнала имеет префикс с именем компонента.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.417s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()

Теперь вы можете видеть, что все сообщения, поступающие от приложения UDP эхо-клиента, идентифицированы как таковые. Сообщение «Received 1024 bytes from 10.1.1.2» теперь четко определено как поступающее от приложения эхо-клиента. Оставшееся сообщение должно поступить из приложения UDP эхо-сервера. Мы можем включить этот компонент, введя список компонентов, разделенных двоеточиями, в переменной среды NS_LOG.

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:
               UdpEchoServerApplication=level_all|prefix_func'

Предупреждение: в приведенном выше примере текста вам нужно будет удалить символ новой строки после двоеточия (:), он использован для форматирования документа. Теперь, если вы запустите сценарий, вы увидите все сообщения журнала из клиентского и серверного эхо-приложений. Вы может увидеть, что это может быть очень полезно при отладке.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.406s)
UdpEchoServerApplication:UdpEchoServer()
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoServerApplication:StartApplication()
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
UdpEchoServerApplication:HandleRead(): Echoing packet
UdpEchoClientApplication:HandleRead(0x624920, 0x625160)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoServerApplication:StopApplication()
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

Также иногда полезно иметь возможность видеть время моделирования, в которое было создано сообщение журнала. Вы можешь сделать это путем добавления по ИЛИ бита prefix_time:

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time: UdpEchoServerApplication=level_all|prefix_func|prefix_time'

Опять же, вам придется удалить выше символ новой строки. Если вы теперь запустите скрипт, то должны увидеть следующий вывод:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.418s)
0s UdpEchoServerApplication:UdpEchoServer()
0s UdpEchoClientApplication:UdpEchoClient()
0s UdpEchoClientApplication:SetDataSize(1024)
1s UdpEchoServerApplication:StartApplication()
2s UdpEchoClientApplication:StartApplication()
2s UdpEchoClientApplication:ScheduleTransmit()
2s UdpEchoClientApplication:Send()
2s UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
2.00369s UdpEchoServerApplication:HandleRead(): Echoing packet
2.00737s UdpEchoClientApplication:HandleRead(0x624290, 0x624ad0)
2.00737s UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
10s UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

Обратите внимание, что конструктор для UdpEchoServer был вызван во время симуляции 0 секунд. Это на самом деле происходит до начала симуляции, но это время отображается как ноль секунд. То же самое верно для сообщение конструктора UdpEchoClient.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.418s)
0s UdpEchoServerApplication:UdpEchoServer()
0s UdpEchoClientApplication:UdpEchoClient()
0s UdpEchoClientApplication:SetDataSize(1024)
1s UdpEchoServerApplication:StartApplication()
2s UdpEchoClientApplication:StartApplication()
2s UdpEchoClientApplication:ScheduleTransmit()
2s UdpEchoClientApplication:Send()
2s UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
2.00369s UdpEchoServerApplication:HandleRead(): Echoing packet
2.00737s UdpEchoClientApplication:HandleRead(0x624290, 0x624ad0)
2.00737s UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
10s UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

Напомним, что скрипт scratch/first.cc запустил приложение эхо-сервера за одну секунду до начала симуляции. Теперь вы можете видеть, что метод StartApplication сервера фактически вызывается на первой секунде. Вы также можете заметить, что эхо-клиент запускается на второй секунде симуляции, как мы и просили в скрипте.

Теперь вы можете следить за ходом симуляции по вызову ScheduleTransmit в клиенте, который вызывает Send обратного вызова HandleRead в приложении эхо-сервера. Обратите внимание, что истекшее время для отправки пакета через двухточечный линк составляет 3,69 миллисекунды. Видно, что эхо-сервер регистрирует сообщение о том, что он отозвался на пакет, а затем, после задержки канала, вы видите, что эхо-клиент получает эхо-пакет в своем методе HandleRead.

В этой симуляции многое происходит незаметно для вас. Но вы можете очень легко отследить весь процесс, включив в системе все компоненты журналирования. Попробуйте установить в переменной NS_LOG следующее значение,

$ export 'NS_LOG=*=level_all|prefix_func|prefix_time'

Звездочка выше является подстановочным символом компонента журналирования. Это включит все записи во всех компонентах используется в симуляции. Я не буду воспроизводить вывод здесь (на момент написания статьи он производит 1265 строк вывода для один эхо-пакет), но вы можете перенаправить эту информацию в файл и просмотреть его в вашем любимом редакторе,

$ ./waf --run scratch/myfirst > log.out 2>&1

Я лично использую эту чрезвычайно многословную версию ведения журнала, когда у меня возникла проблема, и я понятия не имею, где дела пошли не так. Я могу довольно легко следить за выполнением кода, не устанавливая точки останова и пошаговый код в отладчике. Я могу просто отредактировать вывод в моем любимом редакторе и искать то, что я ожидаю, и видеть, что происходит то, чего не ожидал. Когда у меня есть общее представление о том, что идет не так, я перехожу в отладчик для детального изучения проблемы. Этот вид вывода может быть особенно полезен, когда ваш скрипт делает что-то совершенно неожиданное. Если вы пользуетесь только отладчиком, вы можете полностью пропустить неожиданный поворот. Регистрация делает такие повороты заметными.

5.1.3 Добавление журналирования в ваш код

Вы можете добавить новые записи в свои симуляции, совершая вызовы компонента log из нескольких макросов. Давайте сделаем это в сценарии myfirst.cc, который мы имеем в «чистой» директории. Напомним, что мы определили в этом сценарии компонент журналирования:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

Вам известно, что вы можете включить журналирование всех сообщений этого компонента, установив переменную среды NS_LOG на разных уровнях. Давайте продолжим и добавим некоторые записи в скрипт. Макрос, используемый для добавления в журнал сообщений информационного уровня — NS_LOG_INFO. Давайте добавим сообщение (непосредственно перед тем, как мы начнем создавать узлы), которое говорит вам, что сценарий находится на этапе создания топологии ("Creating Topology"). Это делается в следующем фрагменте кода,
Откройте scratch/myfirst.cc в вашем любимом редакторе и добавьте строку,
NS_LOG_INFO ("Creating Topology");
прямо перед строками,

NodeContainer nodes;
nodes.Create (2);

Теперь скомпилируйте скрипт, используя waf, и очистите переменную NS_LOG, чтобы отключить поток журналирования, который мы включили ранее:

$ ./waf
$ export NS_LOG=
Теперь, если вы запустите скрипт,
$ ./waf --run scratch/myfirst

вы не увидите новое сообщение, так как связанный с ним компонент журналирования (FirstScriptExample) не был включен. Чтобы увидеть ваше сообщение, вам нужно включить компонент ведения журнала FirstScriptExample с уровнем не ниже NS_LOG_INFO. Если вы просто хотите увидеть этот конкретный уровень ведения журнала, вы можете включить его так,

$ export NS_LOG=FirstScriptExample=info

Если вы сейчас запустите скрипт, то увидите новое сообщение «Creating Topology»,

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
Creating Topology
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

5.2 Использование аргументов командной строки

5.2.1 Переопределение значений атрибутов по умолчанию

Другой способ изменить поведение сценариев ns‑3 без редактирования и сборки — использование аргументов командной строки. Мы предоставляем механизм для разбора аргументов командной строки и автоматической установки локальных и глобальных переменных на основе результатов.

Первым шагом в использовании системы аргументов командной строки является объявление синтаксического анализатора командной строки. Это сделать довольно просто (в вашей основной программе), как в следующем коде,

int
main (int argc, char *argv[])
{
...
CommandLine cmd;
cmd.Parse (argc, argv);
...
}

Этот простой двухстрочный фрагмент на самом деле очень полезен сам по себе. Он открывает дверь к глобальной переменной ns‑3 и системе атрибутов. Давайте добавим две строки кода в начало главной функции скрипта scratch/myfirst.cc. Двигаясь дальше, компилируем сценарий и запускаем его, при запуске делаем запрос справки следующим образом,

$ ./waf --run "scratch/myfirst --PrintHelp"

Эта команда попросит Waf запустить скрипт scratch/myfirst и передать ему аргумент командной строки —PrintHelp. Кавычки необходимы, чтобы показать, для какой программы предназначен аргумент. Парсер командной строки обнаружит аргумент —PrintHelp и выведет ответ,

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.413s)
TcpL4Protocol:TcpStateMachine()
CommandLine:HandleArgument(): Handle arg name=PrintHelp value=
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.

Теперь рассмотрим опцию —PrintAttributes. Мы уже упоминали систему атрибутов ns‑3, при изучении скрипта first.cc. Мы видели следующие строки кода,

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

и говорили, что DataRate на самом деле является атрибутом PointToPointNetDevice. Давайте применим синтаксический анализатор аргументов командной строки для просмотра атрибутов PointToPointNetDevice. Список помощи говорит, что мы должны предоставить TypeId. Это имя класса, к которому принадлежат интересующие атрибуты. В нашем случае это будет ns3::PointToPointNetDevice. Продолжим двигаться вперед, введите,

$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"

Система распечатает все атрибуты этого типа сетевого устройства. Вы увидите, что среди атрибутов в списке есть,

--ns3::PointToPointNetDevice::DataRate=[32768bps]:
The default data rate for point to point links

Это значение по умолчанию, которое будет использоваться в системе при создании объекта PointToPointNetDevice. Мы переопределим это значение по умолчанию с помощью параметра Attribute в PointToPointHelper выше. Давайте использовать значения по умолчанию для точка-точка устройств и каналов. Для этого удалим вызовы SetDeviceAttribute и SetChannelAttribute из myfirst.cc, который мы имеем в чистой директории.

Ваш скрипт должен теперь просто объявить PointToPointHelper и не выполнять никаких операций по установке, как показано в примере ниже,

...
NodeContainer nodes;
nodes.Create (2);
PointToPointHelper pointToPoint;
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
...

Продолжайте и создайте новый скрипт с Waf (./waf) и давайте вернемся назад и включим некоторую запись из UDP эхо-приложения сервера и включим префикс времени.

$ export 'NS_LOG=UdpEchoServerApplication=level_all|prefix_time'

Если вы запустите скрипт, то должны увидеть следующий вывод:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.405s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.25732s Received 1024 bytes from 10.1.1.1
2.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()

Напомним, что в прошлый раз, когда мы смотрели по времени симуляции, момент получения пакета эхо-сервером, это было в 2,00369 секунд.

2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1

Теперь он получает пакет в 2.25732 секунды. Это потому, что мы просто сбросили скорость передачи данных PointToPointNetDevice с пяти мегабит в секунду до значения по умолчанию, которое составляет 32768 бит в секунду. Если бы мы подставили новый DataRate с помощью командной строки, мы могли бы снова ускорить наше моделирование. Мы сделаем это следующим образом, согласно формуле, подразумеваемой элементом справки:

$ ./waf --run "scratch/myfirst --ns3::PointToPointNetDevice::DataRate=5Mbps"

В результате значение атрибута DataRate по умолчанию вернется к пяти мегабитам в секунду. Вы удивлены результатом? Оказывается, чтобы вернуть исходное поведение сценария, нам нужно также установить задержку канала соответствующую скорости света. Мы можем попросить систему командной строки распечатать атрибуты канала, также как мы делали для сетевого устройства:

$ ./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointChannel"

Мы обнаружим, что атрибут задержки канала установлен следующим образом:

--ns3::PointToPointChannel::Delay=[0ns]:
Transmission delay through the channel

Затем мы можем через систему командной строки установить оба этих значения по умолчанию ,

$ ./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::Delay=2ms"

в этом случае мы восстанавливаем время, которое у нас было, когда мы явно установили DataRate и Delay в сценарии:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.417s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.00369s Received 1024 bytes from 10.1.1.1
2.00369s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()

Обратите внимание, что пакет снова получен сервером через 2,00369 секунд. Мы могли бы на самом деле установить таким образом любой из используемых в сценарии атрибутов. В частности, мы могли бы установить отличные от единицы значения атрибутов MaxPackets UdpEchoClient.

Как бы вы использовали это? Попробуйте. Помните, что вы должны закомментировать место, где мы переопределяем значение атрибута по умолчанию и явно установить MaxPackets в сценарии. Затем вы должны пересобрать скрипт. Вы также можете средствами командной строки получить справку о синтаксисе для установки нового значения атрибута по умолчанию. Разобравшись в этом вы сможете управлять количеством пакетов, отображаемых в командной строке. Поскольку мы прилежные люди, то наша командная строка должна выглядеть примерно так:

$ ./waf --run "scratch/myfirst
--ns3::PointToPointNetDevice::DataRate=5Mbps
--ns3::PointToPointChannel::Delay=2ms
--ns3::UdpEchoClient::MaxPackets=2"

Естественный вопрос, который возникает в этой месте, состоит в том, как узнать о существовании всех этих атрибутов. Опять же, система командной строки имеет функцию помощи в этом вопросе. Если мы запросим помощь командной строки, то мы должны увидеть:

$ ./waf --run "scratch/myfirst --PrintHelp"
myfirst [Program Arguments] [General Arguments]
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.

Если вы выберете аргумент «PrintGroups», то должны увидеть список всех зарегистрированных групп TypeId. Имена групп согласуются с именами модулей в исходной директории (хотя и с заглавной буквы). Распечатка всей информации сразу будет слишком объемной, поэтому доступен дополнительный фильтр для печати информации по группам. Так, снова фокусируясь на модуле «точка-точка»:

./waf --run "scratch/myfirst --PrintGroup=PointToPoint"
TypeIds in group PointToPoint:
ns3::PointToPointChannel
ns3::PointToPointNetDevice
ns3::PointToPointRemoteChannel
ns3::PppHeader

Здесь можно найти доступные имена TypeId для поиска атрибутов, например, в
--PrintAttributes = ns3 :: PointToPointChannel, как показано выше.

Еще один способ узнать об атрибутах — через Doxygen ns‑3. Там есть страница, которая перечисляет все зарегистрированные в симуляторе атрибуты.

5.2.2 Захват ваших собственных команд

Вы также можете через систему командной строки добавить свои собственные хуки. Это делается довольно просто с помощью метод парсера командной строки AddValue.
Давайте используем этой возможность, чтобы указать количество пакетов, которые должны отображаться, совершенно другим способом. Давайте добавим локальную переменную с именем nPackets в функцию main. Мы установим её в единицу, чтобы соответствовать нашему предыдущему поведению по умолчанию. Чтобы позволить парсеру командной строки, изменить это значение, нам нужно захватить это значение в парсер. Мы делаем это, добавив вызов AddValue. Перейдите и измените скрипт scratch/myfirst.cc так, чтобы начать со следующего кода,

int
main (int argc, char *argv[])
{
uint32_t nPackets = 1;
CommandLine cmd;
cmd.AddValue("nPackets", "Number of packets to echo", nPackets);
cmd.Parse (argc, argv);
...

Прокрутите вниз до точки в скрипте, где мы устанавливаем атрибут MaxPackets и измените его так, чтобы он был настроен на переменную nPackets вместо константы 1, как показано ниже.

echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));

Теперь, если вы запустите скрипт и подставите аргумент —PrintHelp, вы должны увидеть новый аргумент пользователя. перечисленный на дисплее справки. Введите,

$ ./waf --run "scratch/myfirst --PrintHelp"
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.403s)
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.
User Arguments:
--nPackets: Number of packets to echo

Если вы хотите изменить количество передаваемых пакетов, вы можете сделать это, установив в командной строке аргумент - -nPackets,

$ ./waf --run "scratch/myfirst --nPackets=2"

Теперь вы должны теперь увидеть

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.25732s Received 1024 bytes from 10.1.1.1
2.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
Sent 1024 bytes to 10.1.1.2
3.25732s Received 1024 bytes from 10.1.1.1
3.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()

Вы теперь оправили два пакета. Довольно просто, не правда ли?
Вы можете видеть, что как пользователь ns‑3, вы можете использовать систему аргументов командной строки для управления глобальными значениями и атрибутами. Если вы автор модели, вы можете добавить новые атрибуты к вашим объектам, и они будут автоматически доступны для настройки вашими пользователями через систему командной строки. Если вы автор сценария, вы можете добавлять новые переменные в ваши скрипты и безболезненно подключать их к системе командной строки.

5.3 Использование системы трассировки

Весь смысл моделирования заключается в создании выходных данных для дальнейшего изучения, а система трассировки ns‑3 является основным механизмом для этого. Поскольку ns‑3 — это программа на C++, то могут быть использованы стандартные средствами генерации выходных данных программы на C++ :

#include <iostream>
...
int main ()
{
...
std::cout << "The value of x is " << x << std::endl;
...
}

Вы даже можете использовать модуль журналирования, чтобы добавить небольшую структуру в ваше решение. Известно много проблем порождаемых таким подходом и поэтому для решения этих проблем мы предоставили общую подсистему трассировки событий.

Основные цели системы трассировки ns‑3:

  • Для базовых задач система трассировки должна позволять пользователю генерировать стандартную трассировку для популярных источников, и выбирать объекты генерируют трассировку;

  • Промежуточные пользователи должны иметь возможность расширять систему трассировки для изменения генерируемого выходного формата или для вставки новых источников трассировки, без модификации ядра симулятора;

  • Опытные пользователи могут модифицировать ядро симулятора для добавления новых источников и приемников трассировки. Система трассировки ns‑3 построена на принципах независимых источников отслеживания и приемников, а также унифицированном механизме подключения источников к потребителям.

Система трассировки ns-3 построена на принципах независимых источников и приемников трассировки, а также унифицированной механизм подключения источников к приемникам. Источниками трассировки являются объекты, которые могут сигнализировать о событиях, происходящих в симуляции и обеспечить доступ к интересующим базовым данным. Например, источник трассировки может указывать, когда сетевое устройство получило пакет и обеспечивает доступ к содержимому пакета для заинтересованных приемников трассировки.

Источники трассировки сами по себе бесполезны, если они не «связаны» с другими частями кода, которые на самом деле делают с информацией, предоставленной приемником, что-то полезное. Трассировщики являются потребителями событий и данных, предоставленных источниками трассировки. Например, можно создать приемник трассировки, который будет (при подключении к источнику трассировки предыдущего пример) выводить на печать интересующие части полученного пакета.

Разумное основание для такого явного разделения состоит в том, чтобы позволить пользователям подключать новые типы приемников к существующим источникам трассировки без необходимости редактирования и перекомпиляции ядра симулятора. Таким образом, в приведенном выше примере пользователь может определить новый трассировщик в своем скрипте и подключить его к существующему источнику трассировки, определенному в ядре симуляции, только редактируя пользовательский скрипт.

В этом руководстве мы пройдемся по некоторым предопределенным источникам и приемникам и покажем, как их можно настроить с наименьшими усилиями со стороны пользователя. См. Руководство ns‑3 или разделы с инструкциями для получения информации о расширенной конфигурации трассировки, включая расширение пространства имен трассировки и создание новых источников трассировки.

5.3.1 ASCII Трассировка

ns‑3 обеспечивает вспомогательный функционал, который предоставляет низкоуровневую систему трассировки, чтобы помочь вам с деталями, при настройке несложных трассировок пакетов. Если вы включите эту функцию, вы увидите выходные данные в файлах ASCII. Для тех, кто знаком с выводом ns-2, этот тип трассировки аналогичен out.tr, который сгенерирован множеством скриптов.

Давайте перейдем к делу и добавим некоторые результаты ASCII трассировки в наш скрипт scratch/myfirst.cc. Прямо перед вызовом Simulator :: Run (), добавьте следующие строки кода:
AsciiTraceHelper ascii;

pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));

Как и во многих других идиомах ns‑3, этот код использует вспомогательный объект для создания трассировок ASCII. Вторая строка содержит два вложенных вызова метода. Метод «внутри» CreateFileStream () использует идиому анонимного объекта для создания объекта потока файлов в стеке (без имени объекта) и передает его вызываемому методу. В будущем мы углубимся в этот вопрос, но все, что вам нужно знать на этом этапе, это то, что вы создаете объект, представляющий файл с именем myfirst.tr и передать его в ns‑3. Мы возлагаем на ns‑3, заботу о созданном объекте на все время его жизни, в ходе которой решаются проблемы, вызванных малоизвестным (преднамеренным) ограничением связанным с копирующими конструкторами потоковых объектов C++.

Внешний вызов EnableAsciiAll() сообщает помощнику, что вы хотите включить в вашей симуляции ASCII трассировку для всех соединений устройства точка-точка и то, что вы хотите, чтобы (указанные) приемники трассировки записывали информацию о перемещении пакетов в формате ASCII.

Для тех, кто знаком с ns-2, отслеживаемые события эквивалентны известным точкам трассировки, которые журналируют события «+», «-», «d» и «r».
Теперь вы можете собрать скрипт и запустить его из командной строки:

$ ./waf --run scratch/myfirst

Как много раз до этого, вы увидите несколько сообщений от Waf, а затем “‘build’ finished successfully” (сборка завершена успешно) с некоторым количеством сообщений от работающей программы.

При работе, программа создаст файл с именем myfirst.tr. Из-за особенностей работы Waf, по умолчанию файл создается не в локальной директории, а в директории верхнего уровня репозитория. Если вы хотите изменить путь, где сохраняются трассы, то чтобы указать его вы можете использовать для Waf параметр --cwd. Мы этого не сделали, то чтобы взглянуть на файл ASCII трассировки myfirst.tr в вашем любимом редакторе, нам потребуется перейти в директорию верхнего уровня нашего репозитория.

Парсинг ASCII трасс

Там очень много информации в довольно плотной форме, но первое, на что нужно обратить внимание, так это то, что файл состоит из отдельных строк. Это станет отчетливо видно, если вы развернете окно просмотра пошире.

Каждая строка в файле соответствует событию трассировки. В данном случае мы трассируем события в очереди передачи, присутствующей в каждом сетевом устройстве точка-точка в симуляции. Очередь передачи — это очередь, через которую должен пройти каждый пакет для канала точка-точка. Обратите внимание, что каждая строка в файле трассировки начинается с одиночного символа (и имеет пробел после него). Этот символ будет иметь следующее значение:

+: в очереди устройства произошла операция постановки в очередь;
-: в очереди устройства произошла операция извлечения элемента;
d: пакет был отброшен, как правило, потому, что очередь заполнена;
r: пакет был получен сетевым устройством.

Давайте более детально рассмотрим первую строку в файле трассировки. Я разобью её на части (с отступами для наглядности) и номером строки слева:

0 +
1 2
2 /NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue
3 ns3::PppHeader (
4   Point-to-Point Protocol: IP (0x0021))
6   ns3::Ipv4Header (
7     tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
8     length: 1052 10.1.1.1 > 10.1.1.2)
9     ns3::UdpHeader (
10      length: 1032 49153 > 9)
11      Payload (size=1024)

Первый раздел этого расширенного события трассировки (строка 0) — это операция. У нас здесь символ +, что соответствует операции постановки в очередь на передачу. Второй раздел (строка 1) — время моделирования, выраженное в секундах. Вы можете вспомнить, что мы попросили UdpEchoClientApplication начать отправку пакетов через две секунды. Здесь мы видим подтверждение того, что это действительно происходит.

В следующем разделе примера трассировки (со строки 2) показывается, какой источник трассировки породил это событие (указывается трассировка пространства имен). Вы можете представлять пространство имен трассировки примерно так же, как пространстве имен файловой системы. Корнем пространства имен является NodeList. Это соответствует контейнеру, управляемому в основном коде ns‑3. В нем содержатся все узлы, которые создаются в скрипте. Так же, как файловая система может иметь в корне директории, в NodeList у нас может быть множество узлов. Таким образом, строка /NodeList/0 ссылается на нулевой узел в NodeList, который мы обычно понимаем как «узел 0». В каждом узле есть список устройств, которые были установлены. Этот список расположен следующим в пространстве имен. Вы можете видеть, что это событие трассировки исходит из DeviceList/0, который является устройством нулевым установленным в узле.

Следующая подстрока, $ ns3 :: PointToPointNetDevice, сообщает, какое устройство находится в нулевой позиции: списка устройств нулевого узла. Напомним, что операция +, находящаяся в строке 0, означала, что в очереди передачи устройства произошло добавление элемента. Это отражено в последних сегментах «пути трассы»: TxQueue/Enqueue.

Остальные разделы в трассировке должны быть достаточно понятны интуитивно. Строки 3-4 указывают, что пакет инкапсулирован в протоколе точка-точка. Строки 5-7 показывают, что пакет имеет заголовок версии IP4 и возник в IP-адресе 10.1.1.1 и предназначен для 10.1.1.2. Строки 8-9 показывают, что этот пакет имеет заголовок UDP и, наконец, строка 10 показывает, что полезная нагрузка — ожидаемые 1024 байта.

Следующая строка в файле трассировки показывает, что тот же пакет был извлечен из очереди передачи на том же узле.

Третья строка в файле трассировки показывает, что пакет принят сетевым устройством на узле с эхо-сервером. Я воспроизвел это событие ниже.

0 r
1 2.25732
2 /NodeList/1/DeviceList/0/$ns3::PointToPointNetDevice/MacRx
3   ns3::Ipv4Header (
4     tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
5     length: 1052 10.1.1.1 > 10.1.1.2)
6     ns3::UdpHeader (
7       length: 1032 49153 > 9)
8       Payload (size=1024)

Обратите внимание, что операция трассировки теперь r, а время симуляции увеличено до 2,25732 секунд. Если вы внимательно следовали инструкциям учебника, то это означает, что вы оставили DataRate сетевых устройств и задержку канала в их значениях по умолчанию. Это время должно быть знакомо, как вы уже видели в предыдущем разделе.

Запись пространства имен источника трассировки (строка 2) была изменена, чтобы отразить, что это событие исходит от узла 1 (/NodeList/1) и пакет принят источником трассировки (/MacRx). Вам должно быть довольно легко отследить за движением пакета через топологию, просматривая оставшиеся в файле трассы.

5.3.2 Трассировка PCAP

Помощники устройств ns‑3 также можно использовать для создания файлов трассировки в формате .pcap. Акроним pcap (обычно пишется в нижнем регистре) обозначает захват пакетов и фактически является API, который включает определение формата файла .pcap. Наиболее популярной программой, которая может читать и отображать этот формат, является Wireshark (ранее называвшаяся Ethereal). Тем не менее, есть много анализаторов трассировки трафика, которые используют этот формат пакета. Мы рекомендуем пользователям использовать множество инструментов, доступных для анализа pcap трасс. В этом руководстве мы сосредоточимся на просмотре pcap трасс с помощью tcpdump.

Включение pcap трассировки, выполняется одной строкой кода.

pointToPoint.EnablePcapAll ("myfirst");

Вставьте эту строку кода после кода трассировки ASCII, который мы только что добавили в scratch/myfirst.cc. Обратите внимание, что мы передали только строку «myfirst», а не «myfirst.pcap» или что-то подобное. Это потому, что параметр это префикс, а не полное имя файла. Во время симуляции помощник фактически создаст файл трассировки для каждого устройства точка-точка. Имена файлов будут построены с использованием префикса, номера узла, номера устройства и суффикса «.pcap».

Для нашего примера сценария мы в конечном итоге увидим файлы с именами «myfirst-0-0.pcap» и «myfirst-1-0.pcap», которые являются pcap трассировками для узла 0-устройство 0 и узла 1-устройство 0 соответственно. После того, как вы добавили строку кода для включения pcap трассировки, вы можете запустить скрипт обычным способом:

$ ./waf --run scratch/myfirst

Если вы посмотрите в директорию верхнего уровня вашего дистрибутива, вы должны увидеть три файла: файл ASCII трассировки myfirst.tr, который мы перед этим изучили, файлы myfirst-0-0.pcap и myfirst-1-0.pcap — новые pcap файлы, которые мы только что сгенерировали.

Чтение вывода с помощью tcpdump

На данный момент для просмотра файлов pcap проще всего будет использовать tcpdump.

$ tcpdump -nn -tt -r myfirst-0-0.pcap
reading from file myfirst-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.514648 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024
tcpdump -nn -tt -r myfirst-1-0.pcap
reading from file myfirst-1-0.pcap, link-type PPP (PPP)
2.257324 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.257324 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024

В дампе myfirst-0-0.pcap (клиентское устройство) вы можете видеть, что эхо-пакет отправляется через 2 секунды симуляции. Если вы посмотрите на второй дамп (myfirst-1-0.pcap), вы увидите, что пакет принимается в момент 2,257324 секунды. Вы увидите во втором дампе, что пакет возвращается в момент 2.257324 секунды, и, наконец, что пакет был получен клиентом обратно в первом дампе в момент 2.514648 секунд.

Чтение вывода с помощью Wireshark

Если вы не знакомы с Wireshark, есть веб-сайт, с которого вы можете скачать программы и документацию: http://www.wireshark.org/. Wireshark — это графический интерфейс пользователя, который можно использовать для отображения этих файлов трассировки. Если у вас есть Wireshark, вы можете открыть любой из файлов трассировки и отобразить содержимое, как если бы вы захватили пакеты, используя анализатор пакетов.

Источник: habr.com

Добавить комментарий