close

Вход

Забыли?

вход по аккаунту

Сафонов Дмитрий Сергеевич. Разработка и программная реализация службы Proxy NDP

код для вставки
Аннотация
Дипломная работа на тему «Разработка и программная реализация службы
Proxy NDP». Рассматривается задача восстановления работоспособности IPv6 сети при её фактическом расположении в нескольких сегментах канального уровня
OSI, востребованная вследствие всё большего распространения IPv6 и распространённых практик настройки сети провайдерами и хостерами. Проведён анализ методов восстановления работоспособности таких сетей, выбран наиболее
общий — реализация службы NDP proxy. Реализация выполнена в виде службы
для ОС GNU/Linux. При реализации использованы возможности платформы для
повышения уровня безопасности и язык программирования с направленностью
на безопасность.
Предмет исследования — выбор и реализация наиболее общего метода обеспечения работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального уровня.
Объект исследования — обеспечение работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального уровня.
Цель работы — исследование задачи и разработка безопасного программного обеспечения для обеспечения работы IPv6 сети в условиях её разделения на
части, находящиеся в разных сетях канального уровня.
Метод исследования — анализ литературы и реализация ПО, оптимального
с точки зрения этого анализа.
Работа имеет как теоретическое значение, заключающееся в возможности
применения приведённого анализа для дальнейших исследований работы IPv6 сетей, так и практическое — разработанная служба NDP Proxy может применяться
на практике при построении IPv6 сетей.
Ключевые слова: IPv6, NDP, прокси, компьютерные сети, L2 адреса, Rust,
асинхронное программирование, Linux, raw socket, packet socket.
55 страницы, 6 рисунков, 2 таблицы, 38 использованных источников.
Abstract
Bachelor’s thesis on “Development of an NDP proxy daemon.“ The problem of normalizing the functioning of an IPv6 network being divided by link-level network boundaries
is considered. The problem’s importance is driven up by increasing IPv6 adoption and
by network building practices widespread along ISPs and hosting providers. Several
methods are considered, resulting in the choice of an NDP proxy service use as the
most general. The service was developed as a daemon running on top of GNU/Linux
operating system. The daemon is implemented using Linux’s safety features and is
writtern in Rust — a systems programming language with focus on security.
The subject of the study is the selection and implementation of a method of normalization of the functioning of an IPv6 network being divided by link-level network boundaries.
The object of the study is normalization of the functioning of an IPv6 network being
divided by link-level network boundaries.
The purpose of the work is performing analysis of the problem of normalizing the functioning of an IPv6 network being divided by link-level network boundaries and implementation of the most general solution considered.
The purpose of the work is accomplished by the methods of previous work analysis and
software implementation of its most optimal solution considered.
That thesis’s theoretical significance emerges from the potential applicability of the
shown analisys to future works on IPv6 networks’ functioning. The work’s practical
significance is constituted by the ability to directly use the developed daemon in building IPv6 networks.
Keywords: IPv6, NDP, proxy, computer networks, L2 addresses, Rust, asynchronous
programming, Linux, raw socket, packet socket.
55 pages, 6 figures, 2 tables, 38 previous works referenced.
Оглавление
1
2
3
Лист задания
2
Аннотация
4
Abstract
5
Оглавление
6
Введение
8
Постановка и анализ задачи
1.1 Частичное описание протокола NDP .
1.2 Постановка задачи . . . . . . . . . . .
1.3 Сравнение способов решения задачи
1.4 Язык реализации . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Разработка ПО
2.1 Реализация основных функций . . . . . . . . .
2.1.1 Инициализация . . . . . . . . . . . . .
2.1.2 Асинхронное программирование в Rust
2.1.3 Обработка UNIX сигналов . . . . . . .
2.1.4 Обработка NDP запросов . . . . . . . .
2.2 Безопасность . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
14
16
22
.
.
.
.
.
.
25
25
25
29
31
33
38
Тестирование
44
3.1 Тестовая среда, использовавшаяся при разработке . . . . . . . . . . 44
3.2 Практическая проверка . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.3 Оценка производительности . . . . . . . . . . . . . . . . . . . . . . 48
Заключение
50
Литература
52
8
Введение
Обоснование выбора темы и её актуальность
В связи с возрастающим использованием IPv6 (уже более 20% траффика по
данным Google[1]), исчерпанием запаса нераспределённых IPv4 адресов[2], особой востребованностью пользуются задачи построения, обеспечения работоспособности IPv6 сетей.
При построении IPv6 сетей часто возникает ситуация, когда клиенту выделяется /64 блок адресов и один порт, но эти адреса не маршрутизируются на адрес
клиента, а пакеты, направленный на эти адреса считаются on-link с точки зрения
вышестоящего маршрутизатора. Это — распространённая практика среди хостеров, предоставляющих IPv6 адреса (например, в сети OVH, крупного французского хостера[3]). В такой ситуации, часть протокола NDP, созданная для разрешения
сетевых адресов канального уровня, не может работать в стандартном режиме[4].
Наибольшую актуальность исследованию придаёт факт становления использования /64 префиксов в IPv6 де-факто стандартом и требование такой длины префикса для работы ряда стандартных для IPv6 сетей функций только поддерживает
этот тренд[5].
В данной работе анализируется задача об обеспечении работы IPv6 сети в
условиях её разделения на части, находящиеся в разных сетях канального
уровня.
Степень разработанности проблемы
Ранее предлагались способы замены или исправления работы NDP в подобных условиях, как ethernet bridge, реализация NDP proxy в ядре Linux, ndppd Даниела Адолфсона, но они или не позволяют использовать динамическое выделение адресов в сети, как Linux NDP proxy, или не используют достаточные меры
безопасности, как ndppd, требующий полных root привилегий для работы[6, 7].
9
Предмет исследования
Выбор и реализация наиболее общего метода обеспечения работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального
уровня.
Объект исследования
Обеспечение работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального уровня.
Цель работы
Исследование задачи и разработка безопасного программного обеспечения
для обеспечения работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального уровня.
Основные задачи исследования
• Изучить литературу по рассматриваемому вопросу и сделать заключение о
необходимости той или иной реализации;
• Разработать программный комплекс, работающий на ОС GNU/Linux, для
обеспечения работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального уровня, то есть части, между которыми не проходят NDP пакеты, необходимые для работы разрешения адресов канального уровня OSI и определения доступности «хостов-соседей»
(Neighbor Unreachability Detection).
• Использовать доступных функций платформы для обеспечения безопасности в ходе реализации этого программного комплекса.
• Сделать выводы по результатам исследований.
Метод исследования
Анализ литературы и реализация ПО, оптимального с точки зрения этого
анализа.
10
Структура работы
Во введении обосновывается актуальность решаемой задачи, анализируются труды по данной тематике, ставятся цели и задачи, а также, и основное направление исследования.
В первой главе сравниваются методы достижения цели исследования и делается вывод об оптимальности одного из них, ставится задача о его реализации.
Во второй главе описывается программная реализация выбранного метода.
В третьей главе описывается тестирование разработанного ПО.
В заключении делается вывод о проведённых работах и их результате и перспективах дальнейших исследований в данном направлении.
Приводится список использованных источников.
В приложении приводится полный листинг разработанного программного
обеспечения.
11
1 Постановка и анализ задачи
1.1
Частичное описание протокола NDP
При обмене пакетами между хостами в сети, необходимо знать адрес получателя. Если в IPv6 сети известен IPv6 адрес получателя пакета, это ещё не всегда означает возможность отправки пакета: протокол IPv6 находится на третьем
уровне модели OSI[8] — сетевом и отправка IPv6 пакетов требует формирования
заголовка второго — канального — уровня.
Особенности работы IPv6 с самым распространённым протоколом канального/физического уровня — Ethernet[9] — описываются в [10]. Также, в отдельных RFC описываются особенности работы IPv6 с другими протоколами канального уровня OSI.
Но, в [10] и подобных документах не описывается процесса получения адресов канального уровня (далее — link-layer адресов) для начала отправки пакета,
находящегося on-link и даже link-layer адреса маршрутизатора. Это описывается
в [4].
RFC [4] посвящён протоколу NDP (Neighbor Discovery Protocol), который
отвечает за получение адреса хостом, получение хостами информации о маршрутизаторах в сети, разрешение link-layer адресов on-link хостов и маршрутизаторов,
определение доступности on-link хостов и маршрутизаторов[4].
NDP — часть протокола ICMPv6, представляющего из себя переделанный
и расширенный новыми возможностями для IPv6 ICMP[11, 12].
Интерес в данном исследовании представляют функции NDP разрешения
link-layer адресов и определения доступности on-link хостов и маршрутизаторов.
Эти функции NDP реализуются NDP пакетами типов Neighbor Solicitation и
Neighbor Advertisement.
Общая схема IPv6 пакета представлена в листинге 1, где поле Version всегда
имеет значение 6.
В случае указанных типов NDP пакетов, поле Hop Limit всегда имеет значение 255, что не позволяет им быть маршрутизированными за пределы link-layer
сегмента сети, а Source Address — IPv6 адрес хоста-отправителя пакета. Значе-
12
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class |
Flow Label
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Payload Length
| Next Header |
Hop Limit
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
+
+
|
|
+
Source Address
+
|
|
+
+
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
+
+
|
|
+
Destination Address
+
|
|
+
+
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Листинг 1: Схема IPv6 пакета
ния поля Destination Address зависят от конкретного типа NDP пакета. Поле Next
Header (или поле next header соответствующей IPv6 опции) принимает значение
58, соответствующее протоколу ICMPv6.
Общая схема ICMPv6 пакета представлена в листинге 2.
0
1
2
3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Type
|
Code
|
Checksum
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
+
Message Body
+
|
|
Листинг 2: Схема ICMPv6 пакета
В случае Neighbor Solicitation она принимает вид, приведенный в листинге 3.
Здесь, поле Type имет значение 135, Code — 0. Поле Target Address имеет
значение адреса, для которого разрешается link-layer адрес или доступность которого проверяется.
В конце пакета могут содержаться опции, которых в [4] для NDP Neighbor
13
0
1
2
3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Type
|
Code
|
Checksum
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Reserved
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
+
+
|
|
+
Target Address
+
|
|
+
+
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Options ...
+-+-+-+-+-+-+-+-+-+-+-+-
Листинг 3: Схема пакета NDP Neighbor Solicitation
Solicitation определён один тип — Source link-layer address, в котором указан linklayer адрес отправителя. При разрешении link-layer адреса, эта опция обязательна,
а при определении доступности on-link хоста необязательна, но рекомендована к
включению.
В случае Neighbor Advertisement ICMPv6 пакет принимает вид, приведённый в листинге 4.
Здесь, поле Type принимает значение 136, Code — 0. Флаг R (router) показывает, что отправитель advertisement — маршрутизатор, S (solicited) — что
advertisement отправляется в ответ на solicitation (всегда верно для разрешения
link-layer адресов и Neighbor Unreachability Detection), O (override) — что запись
в neighbor кеше хоста должна быть безусловно перезаписана данными из этого
пакета (не рекомендуется для solicited advertisement, а значит, для нашего случая
в целом). Значение поля Target Address для нашего случая должно быть скопировано из поля Target Address соответствующего Neighbor Solicitation пакета.
В конце пакета могут содержаться опции, которых в [4] для NDP Neighbor
Advertisement определён один тип — Target link-layer address, в котором указан
link-layer адрес отправителя (target advertisement пакета). При разрешении linklayer адреса, эта опция обязательна, а при определении доступности on-link хоста
необязательна, но рекомендована к включению.
Поле Destination Address IPv6 пакета для NDP Neighbor Advertisement инте-
14
0
1
2
3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Type
|
Code
|
Checksum
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|R|S|O|
Reserved
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
+
+
|
|
+
Target Address
+
|
|
+
+
|
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
Options ...
+-+-+-+-+-+-+-+-+-+-+-+-
Листинг 4: Схема пакета NDP Neighbor Advertisement
ресующих нас типов — просто IPv6 адрес хоста-отправителя соответствующего
Neighbor Solicitation.
Для NDP Neighbor Solicitation поле Destination Address принимает значение
solicited-node multicast адреса, соответствующего адресу получателя solicitation
при разрешении link-layer адреса, а при определении доступности on-link хоста
или маршрутизатора — значение аналогичное Target Address.
1.2
Постановка задачи
Для интернет-провайдеров и многих хостеров типичной ситуацией является выделение целых /64 блоков адресов каждому клиенту. Частично это вызвано необходимостью префиксов такого размера для использования рада функций,
стандартных для IPv6, например для работы SLAAC[13] выделения адресов, не
требующего сервера, хранящего состояние, как DHCPv6[14], частично — тем, что
/64 блоки — де-факто стандарт выделения адресов[5].
Таким образом, возникает необходимость маршрутизации целого IPv6 префикса каждому клиенту для полноценного использования полученного адресного пространства. Многие хостеры, такие как Hetzner, Evolution-Host и другие, не
маршрутизируют префикс клиента на его порт, а маршрутизируют более крупные префиксы, подключая множество клиентов на один link-level сегмент. Вся
подсеть клиента, при этом, считается on-link с точки зрения маршрутизатора.
15
Это не вызывает никаких проблем, если все используемые клиентом адреса
назначены непосредственно на интерфейс хоста, подключенный к upstream порту
хостера, но, в случае маршрутизации через него хостов такой подсети, работа сети
оказывается невозможной: Hop Limit поле NDP пакетов должен иметь значение
255, что предотвращает их маршрутизацию[4]. А, значит, маршрутизатор хостера
не может знать link-level адрес назначения при отправке пакетов в такую подсеть.
Работа сети невозможна.
Диаграмма подобной сети приведена в рисунке 1. Здесь, 2001:db8::1:1 не
может связаться с 2001:db8::2:1.
2001:db8::1:0/112
2001:db8::/64
2001:db8::1:1
2001:db8::1:ffff
2001:db8::2:0/112
2001:db8::2:ffff
2001:db8::2:1
Рис. 1: Пример сети, требующей решения поставленной задачи для нормального
функционирования. Секциями в прямоугольниках обозначены различные адреса,
назначенные данному хосту.
Так, попытка пинга 2001:db8::1:1 со стороны 2001:db8::2:1 вызовет такой
результат:
$ ping6 2001:db8::1:1
PING 2001:db8::1:1(2001:db8::1:1) 56 data
From 2001:db8::2:1 icmp_seq=1 Destination
,→
unreachable
From 2001:db8::2:1 icmp_seq=5 Destination
,→
unreachable
From 2001:db8::2:1 icmp_seq=6 Destination
,→
unreachable
^C
bytes
unreachable: Address
unreachable: Address
unreachable: Address
16
--- 2001:db8::1:1 ping statistics --9 packets transmitted, 0 received, +3 errors, 100% packet loss, time
,→
8182ms
А обмен пакетами при этом будет таким (со стороны 2001:db8::2:ffff):
# tcpdump -i rsp-mid-out
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol
,→
decode
listening on rsp-mid-out, link-type EN10MB (Ethernet), capture size
,→
262144 bytes
07:27:14.306522 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:15.320760 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:16.344758 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:17.368832 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:18.393769 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:19.416830 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:20.440819 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:21.464759 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
07:27:22.488760 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
^C
9 packets captured
9 packets received by filter
0 packets dropped by kernel
Способам восстановления работоспособности такой сети, разделённой между несколькими link-level сегментами и посвящена данная работа.
1.3
Сравнение способов решения задачи
В настоящей работе анализируются следующие возможные способы восстановления работоспособности сети, разделённой между несколькими link-level
сегментами:
17
• Ручное добавление link-level адресов next hop для каждого хоста.
• Ручная настройка NDP проксирования с помощью Linux ndp-proxy или аналогов.
• Ethernet bridge.
• VPN, работающая на канальном уровне OSI, создающая единую link-level
сеть.
• NDP proxy/responder, позволяющий настройку целых префиксов IPv6 адресов.
Возможно ручное добавление link-level адресов next hop для всех хостов
сети в настройки сети каждого хоста, что продемонстрировано в рисунке 1.
2001:db8::1:0/112
2001:db8::1::1 (12:23:34:45:56:67)
2001:db8::/64
2001:db8::1:ffff -> 11:22:33:44:55:66
2001:db8::2:ffff -> 11:22:33:44:55:66
2001:db8::2:1 -> 11:22:33:44:55:66
2001:db8::1::ffff (23:34:45:56:67:78)
2001:db8::2::ffff (22:33:44:55:66:77)
2001:db8::2:0/112
2001:db8::1:1 -> 12:23:34:45:56:67
2001:db8::2:1 -> 23:34:45:56:67:78
2001:db8::2::1 (23:34:45:56:67:78)
2001:db8::1:ffff -> 22:33:44:55:66:77
2001:db8::2:ffff -> 22:33:44:55:66:77
2001:db8::1:1 -> 22:33:44:55:66:77
Рис. 1: Пример Ethernet сети с ручным добавлением link-level адресов в neighbor
cache каждого хоста
Такой способ восстановления работоспособности сети требует изменения
настроек на маршрутизаторе хостера в приведённом выше примере, что накладывает значительные ограничения на его применимость. Ещё одним его недостатком
18
можно считать большой размер таблиц IPv6 адрес → link-level адрес даже в случае, когда большинство хостов не взаимодействуют друг с другом.
Ручная настройка NDP проксирования, используя встроенную в ядро Linux
функциональность ndp-proxy лишена упомянутого недостатка, и продемонстрирована на рисунке 2.
2001:db8::1:0/112
2001:db8::1::1
2001:db8::/64
2001:db8::1::2
2001:db8::1::ffff
2001:db8::2::ffff
2001:db8::2:0/112
proxy 2001:db8::1:1
proxy 2001:db8::1:2
proxy 2001:db8::2:1
2001:db8::2::1
Рис. 2: Пример Ethernet сети с ручным добавлением Linux NDP proxy
Она требует добавления информации о каждом хосте, требующем такого
проксирования отдельно, что не позволяет использовать этот метод изолированно при изменении состава сети динамически, например, при настройке L3 VPN,
где хост-маршрутизатор должен отвечать на NDP solicitation запросы для всех адресов, выделяемых клиентам динамически.
В случае автоматизации добавления ndp proxy записей в ядро по мере поступления пакетов, на первый такой пакет придётся отвечать иным механизмом,
так как он поступит раньше добавления ndp proxy записи, что приведёт нас к реализации собственного NDP proxy/responder, что уже́ рассматривается в последнем
пункте.
Настройка Ethernet bridge на хосте-маршрутизаторе работает не требуя дополнительных усилий при динамическом выделении адресов в сети, но при при-
19
менении для восстановления работоспособности VPN, находящейся за этим хостоммаршрутизатором, требует работу этой VPN на канальном уровне, оставляя невозможным создание VPN сетевого уровня OSI: VPN сетевого уровня невозможно
присоединить к мосту.
Решение данной проблемы созданием VPN канального уровня OSI, создающей единую link-level сеть также подвержено как этому недостатку, так и увеличивает нагрузку на рассматриваемый хост-маршрутизатор, так как, в таком случае, весь трафик такой L2 сети будет проходить через него даже при фактическом
нахождении её хостов в одной физической link-level сети.
Разделение сети на подсети, по одной для каждого link-level сегмента и использование NDP proxy/responder, позволяющего настройку целыми подсетями, а
не отдельными хостами, как Linux ndp-proxy, лишено вышеуказанных недостатков других вариантов восстановления работоспособности сети и продемонстрировано в рисунке 3.
2001:db8::1:0/112
2001:db8::1::1
2001:db8::/64
2001:db8::1::2
2001:db8::1::ffff
2001:db8::2::ffff
2001:db8::2:0/112
proxy 2001:db8::1:0/112
proxy 2001:db8::2:0/112
2001:db8::2::1
Рис. 3: Пример сети, использующей NDP proxy/responder
Так, характеристики различных способов приведены в таблице 1.
Таким образом, способом решения задачи, добавляющим наименьшее количество ограничений для структуры сети оказалось использование NDP proxy
20
Таблица 1: Преимущества и недостатки различных способов восстановления работоспособности сети
Возможность Высокая
использонагрузка
на
вания для
«маршрухостинга
L3 VPN
тизатор»
Реализуемо
стандартными
инструментами
на
GNU/Linux
Ручные
настройки
на каждом
хосте
Ручные
настройки
на
«маршрутизаторе»
при
выделении
адресов
Требует
настройки
маршрутизатора
хостера
Ручная
настройка LL
адресов
7
7
7
-
-
7
Ручная
настройка
NDP proxy,
аналогичного
встроенному
в ядро Linux
-
7
-
7
-
7
Ethernet bridge
-
-
-
-
-
7
L2 VPN
7
-
7/-
-
7
-
NDP
proxy/responder,
позволяющий
настройку
целыми
подсетями
-
-
-
7
-
-
или responder, позволяющего настройку целыми подсетями. Такая сеть уже будет
доставлять пакеты (при запуске с 2001:db8::2:1):
$ ping6 2001:db8::1:1
PING 2001:db8::1:1(2001:db8::1:1) 56 data bytes
64 bytes from 2001:db8::1:1: icmp_seq=1 ttl=63 time=1.43 ms
64 bytes from 2001:db8::1:1: icmp_seq=2 ttl=63 time=0.268 ms
64 bytes from 2001:db8::1:1: icmp_seq=3 ttl=63 time=0.070 ms
^C
--- 2001:db8::1:1 ping statistics --3 packets transmitted, 3 received, 0% packet loss, time 2007ms
rtt min/avg/max/mdev = 0.070/0.590/1.432/0.600 ms
Пример обмена пакетами в такой сети приведён в листинге 1.
21
# tcpdump -i rsp-mid-out
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol
,→
decode
listening on rsp-mid-out, link-type EN10MB (Ethernet), capture size
,→
262144 bytes
03:14:58.594479 IP6 2001:db8::2:1 > ff02::1:ff01:1: ICMP6, neighbor
,→
solicitation, who has 2001:db8::1:1, length 32
03:14:58.595642 IP6 2001:db8::2:ffff > ff02::1:ff02:1: ICMP6,
,→
neighbor solicitation, who has 2001:db8::2:1, length 32
03:14:58.595668 IP6 2001:db8::2:1 > 2001:db8::2:ffff: ICMP6,
,→
neighbor advertisement, tgt is 2001:db8::2:1, length 32
03:14:58.595675 IP6 2001:db8::2:ffff > 2001:db8::2:1: ICMP6,
,→
neighbor advertisement, tgt is 2001:db8::1:1, length 32
03:14:58.595802 IP6 2001:db8::2:1 > 2001:db8::1:1: ICMP6, echo
,→
request, seq 1, length 64
03:14:58.595879 IP6 2001:db8::1:1 > 2001:db8::2:1: ICMP6, echo
,→
reply, seq 1, length 64
03:14:59.595877 IP6 2001:db8::2:1 > 2001:db8::1:1: ICMP6, echo
,→
request, seq 2, length 64
03:14:59.596106 IP6 2001:db8::1:1 > 2001:db8::2:1: ICMP6, echo
,→
reply, seq 2, length 64
03:15:00.601788 IP6 2001:db8::2:1 > 2001:db8::1:1: ICMP6, echo
,→
request, seq 3, length 64
03:15:00.601828 IP6 2001:db8::1:1 > 2001:db8::2:1: ICMP6, echo
,→
reply, seq 3, length 64
03:15:03.993792 IP6 fe80::18a8:c9ff:fed5:9933 > 2001:db8::2:ffff:
,→
ICMP6, neighbor solicitation, who has 2001:db8::2:ffff,
,→
length 32
03:15:03.993847 IP6 2001:db8::2:ffff > fe80::18a8:c9ff:fed5:9933:
,→
ICMP6, neighbor advertisement, tgt is 2001:db8::2:ffff,
,→
length 24
^C
12 packets captured
12 packets received by filter
0 packets dropped by kernel
Листинг 1: Пример обмена пакетами в сети с NDP proxy/responder (c
2001:db8::2:ffff)
Подобное ПО уже было реализовано: ndppd[7], но эта реализация не использует средства улучшения безопасности, предоставляемые GNU/Linux платформой, на которой реализовано: работает, используя полные привилегии root
(суперпользователя), не использует capabilities. Также, ndppd написан на языке
C++, а не на языке, созданном с прицелом на безопасность, таком как Rust, Ada
и другие.
22
1.4
Язык реализации
Для классически используемых для системного программирования языков
таких как C, C++ характерны проблемы с безопасностью: для максимальной эффективности в их дизайне отказались от проверки выхода за границы области
памяти, управление памятью происходит вручную и ошибки в нём приводят к
утечкам памяти, падениям программ и уязвимостям. Также, эти языки требуют
от программиста выполнения требований к программе, не проверяемых статически таких как: доступ к памяти только в рамках выделенного объёма, своевременное освобождение памяти, неиспользование освобождённой памяти, и так далее.
Нарушение этих требований не требует каких-либо особых условий и регулярно
происходит в результате незначительных ошибок даже у опытных программистов[15].
С другой стороны, такие языки как Python, Perl, Ruby и другие интерпретируемые не могут обеспечить высокой производительности. Java, Javascript способны обеспечить высокий уровень безопасности и скорость работы благодаря
JIT, но требуют для этого больших объёмов памяти и создают непредсказуемые
задержки исполнения при выполнении сборки мусора[16]. Это может скачаться
на работе сетевых сервисов подобных этому, из-за указания достаточно малых
допустимых задержек в [4].
Для данной работы выбран язык Rust. Он не использует сборку мусора,
компилируется в машинные инструкции и не требует вспомогательного runtime
для исполнения. Ошибки выхода за границы используемой памяти исключаются проверками во время компиляции и во время исполнения, если необходимо.
Проблема управления памятью решена с помощью отслеживания времени жизни объектов во время компиляции, для чего иногда необходимо указывать в коде
соответствующие ограничения[17, 18].
Rust создавался как язык для системного программирования и, соответственно, имеет богатые возможности взаимодействия с кодом на C: код, написанный
на Rust может вызывать C-код с помощью простого вызова функции, для которой
указана сигнатура; из кода на C вызов кода на Rust также представляет из себя
простой вызов функции и не требует инициализации какого-либо runtime за его
отсутствием[17, 18].
Для взаимодействия с кодом на C в Rust существуют unsafe-блоки, в кото-
23
рых возможно поведение, которое не может быть автоматически проверено на
корректность компилятором: указание времени жизни для объектов, полученных
из C-кода, разыменование C-указателей и так далее.
В отличие от C и C++, в Rust неопределённое поведение может возникнуть
только по причинам использования функций, доступных только в unsafe-блоках,
что не рекомендуется делать без необходимости[18].
Также, в Rust отсутствует необходимость в header файлах, что устраняет как
проблему соответствия header файла и исходного кода программы-пользователя,
так и позволяет избежать специфичного для C другого источника неопределённого поведения, заключающегося в генерации кода вызова функции с предполагаемыми типами и количеством аргументов. Реализуется это за счёт того, что единица трансляции в Rust — это не файл, а crate — целая программа или библиотека,
а также хранения (и чтения компилятором) информации о типах в специфичном
для Rust формате объектных файлов — rlib.
Rust позволяет автоматическую кодогенерацию с помощью макросов. Функционирование Rust макросов значительно отличается от макросов C: в Rust синтаксическая роль параметра макроса заранее определена, и если параметр макроса объявлен, например, с типом ident — identifier, то его использование в контексте, где ожидается item — объявление структуры, функции и так далее, приведёт
к ошибке компиляции. Параметры макроса рассматриваются как единое целое а
не подставляются в виде текста как в C и C++, поэтому в Rust не нужно ставить
скобки вокруг аргументов макросов или предпринимать другие меры для предотвращения «неожиданного поведения» макроса.
Также, макросы в Rust могут определять только целые синтаксические конструкции. Например, невозможно определить макросом поле структуры, только
целый тип структуры. Невозможным является и генерация макросом открывающей скобки без закрывающей. Это предотвращает изменение контекста между
двумя макросами, что позволяет избежать многих потенциальных ошибок при
использовании «нестандартных» макросов. Например, нельзя создать вложенный
lexical scope, открыв его одним макросом и закрыв другим[18].
В отличие от Ada и других нишевых языков, ориентированных на безопасность, Rust активно применяется в Open Source проектах, например, в браузере
Mozilla Firefox[19]. На crates.io[20] — основном репозитории открытого кода на
Rust — размещено более 16 тысяч Rust проектов.
24
Rust — относительно новый язык и быстро увеличивает популярность. В
ежегодном опросе разработчиков Stack Overflow, в котором приняло участие более 100 тысяч разработчиков со всего мира, Rust уже три года (2016–2018) занимает место «самого желанного» языка[21], что демонстрирует интерес и желание
разработчиков к его использованию.
25
2
Разработка ПО
2.1
Реализация основных функций
2.1.1
Инициализация
Сервер реализован на языке Rust для работы на ОС GNU/Linux и назван
rsndpproxy. Help сервера приведён в листинге 1.
rsndpproxy 0.1.0
Dmitry Safonov <[email protected]>
Simple NDP proxy implementation, written in Rust.
USAGE:
rsndpproxy [FLAGS] [OPTIONS]
FLAGS:
-d, --daemonize
Forks and sets log to syslog instead of the
,→
console
-h, --help
Prints help information
-V, --version
Prints version information
-v, --verbose
Enables extremely verbose logging when
,→
daemonizing. Use RUST_LOG for the console logging
OPTIONS:
-c, --config <FILE>
Sets configuration file name [default:
,→
/etc/rsndpproxy.conf]
-p, --pid-file <FILE>
Sets pid file name [default:
,→
/run/rsndpproxy.pid]
Листинг 1: Вывод rsndpproxy --help
Опции командной строки обрабатываются с помощью библиотеки clap[22].
Help также генерируется ей автоматически. В листинге 2 приведён код работы с
clap.
Конфигурация задаётся в файле формата TOML. Пример конфигурации приведён в листинге 3.
Комментарии начинаются с знака «#». max_queued — максимальное количество пакетов, стоящее в очереди на отправке.
Разбор (и отладочный вывод) конфигурационного файла сделан с помощью
26
фреймворка сериализации/десериализации serde[23] и реализации формата TOML
toml[24].
let matches = App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.arg(Arg::with_name(”config”)
.short(”c”)
.long(”config”)
.takes_value(true)
.value_name(”FILE”)
.default_value(DEFAULT_CONFIG_PATH)
.help(”Sets configuration file name”)
).arg(Arg::with_name(”daemonize”)
.short(”d”)
.long(”daemonize”)
.help(”Forks and sets log to syslog instead of the console”)
).arg(Arg::with_name(”pid”)
.short(”p”)
.long(”pid-file”)
.takes_value(true)
.value_name(”FILE”)
.default_value(DEFAULT_PID_PATH)
.help(”Sets pid file name”)
).arg(Arg::with_name(”verbose”)
.short(”v”)
.long(”verbose”)
.help(”Enables extremely verbose logging when daemonizing.
,→
\
Use RUST_LOG for the console logging”)
).get_matches();
let config_filename = matches.value_of_os(”config”).unwrap();
let config_filename_str =
,→
config_filename.to_string_lossy().into_owned();
let mut config_file = ::std::fs::File::open(config_filename)
.map_err(|e| Error::FileIo {
name: config_filename_str.clone(),
cause: e
})?;
Листинг 2: Использование библиотеки clap в rsndpproxy
Фреймворк serde позволяет автоматически генерировать код для сериализации и десериализации структур, что использовано для чтения файла конфигурации. Пример (неполный) из rsndpproxy приведён в листинге 4.
Благодаря serde, разбор конфигурационного файла свёлся к аннотированию
27
su = ”rsndpproxy”
# можно указать и группу:
# su = ”user:group”
[[interface]]
name = ”eth0”
max_queued = 42
[[interface.prefix]]
prefix = ”2001:db8::1:/112”
router = true
override = false
[[interface.prefix]]
prefix = ”2001:db8::2:/112”
router = false
override = true
[[interface]]
name = ”ppp42”
max_queued = 100500
[[interface.prefix]]
prefix = ”2001:db8:ffff::/48”
router = true
override = true
Листинг 3: Пример содержания конфигурационного файла rsndpproxy
структуры, его представляющей в коде, вызову ::toml::from_str() и написанию кода
разбора/преобразования в строку типов, для которых это не реализовано в toml и
для которых не подходит автогенерация кода serde.
После чтения конфигурации, производится «демонизация» (от daemonize),
то есть, стандартные процедуры запуска фоновых служб в UNIX-подобных системах: для выхода из текущей process group вызывается fork() и завершается
процесс-родитель, вызовом setsid() производится выход из текущей сессии, с помощью chdir(”/”) происходит выход из текущей директории, stdin, stdout и stderr
перенаправляются в /dev/null и настраивается логгирование в syslog.
Предусмотрена и отладочная возможность работы без демонизации, с логгированием на stderr.
Для логгирования используется фасад log[25] и его реализации env_logger[26]
для логгирования на консоль и syslog[27] для логгирования в syslog.
С помощью этих библиотек, логгирование делается простыми вызовами мак-
28
росов trace!, debug!, info!, warn! и error!, позволяющими также форматировать
свои аргументы. Также, непосредственный код логгирования перестаёт зависить
от конкретного способа логгирования и единственным местом, где он остаётся в
коде оказывается инициализация.
const DEFAULT_CONFIG_PATH: &str = ”/etc/rsndpproxy.conf”;
const DEFAULT_PID_PATH: &str = ”/run/rsndpproxy.pid”;
#[allow(non_snake_case)]
fn DEFAULT_MAX_QUEUED() -> usize { 42 }
#[allow(non_snake_case)]
fn DEFAULT_ROUTER_FLAG() -> Router { Router::Yes }
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
#[serde(skip)] pub config_file: OsString,
#[serde(skip)] pub daemonize: bool,
#[serde(skip)] pub pid_file: OsString,
#[serde(skip)] pub verbose_logging: bool,
pub su: Option<SuTarget>,
#[serde(rename = ”interface”)] pub interfaces:
,→
Vec<InterfaceConfig>
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct InterfaceConfig {
pub name: String,
#[serde(default = ”DEFAULT_MAX_QUEUED”)] pub max_queued: usize,
#[serde(rename = ”prefix”)] pub prefixes: Vec<Arc<PrefixConfig>>
}
// ...
pub fn read_config() -> Result<Config> {
// ...
config_file.read_to_string(&mut config_str)
.map_err(|e| Error::FileIo {
name: config_filename_str.clone(),
cause: e
})?;
let mut config: Config = ::toml::from_str(&config_str)?;
//...
}
Листинг 4: Использование serde
29
При демонизации, сервер также создаёт свой pid-файл — файл, содержащий PID процесса сервера в текстовом виде для управления им со стороны initсистемы — посылки сигналов остановки, проверки существования процесса. Также, он используется для недопущения запуска сразу нескольких экземпляров сервера. Это делается с помощью record locking fcntl F_SETLK, при этом блокировка
берётся на открытый pid-файл целиком.
Для этого необходимы root-привилегии, поэтому pid-файл создаётся до понижения привилегий. Он остаётся открытым для работы F_SETLK блокировки.
Его удаление становится невозможным после сброса привилегий, но это не является проблемой, потому что pid-файлы останавливаемых демонов удаляются
init-системой.
Далее, происходит снижение привилегий: снимаются ненужные для работы
сервера capabilities, происходит переключение на непривилегированного пользователя, если оно настроено, capabilities фиксируются с помощью securebits и
PR_SET_NO_NEW_PRIVS.
После этого запускается асинхронный код, работающий в фреймворке
tokio[28]: обработка UNIX сигналов и обработчики NDP запросов.
2.1.2
Асинхронное программирование в Rust
Поддержка асинхронного выполнения кода в Rust и его стандартной библиотеки пока отсутствует в стабильной версии. На данный момент де-факто стандартом асинхронного программирования в Rust является библиотеки tokio[28],
mio[29] и futures[30], используемые вместе.
Библиотека futures предоставляет типажи для асинхронно выполняемых действий/асинхронно получаемых значений, приведённые в листинге 5.
Future — представляет однократно совершаемое действие/получаемое значение, Stream — поток, состоящий из множества значений, Sink — «обратная сторона» потока, куда можно записывать значения многократно.
Также, для этих типажей в futures предоставлено множество функций-адаптеров для типичных операций, например, буферизация в Stream/Sink, ленивое создание значения типа, реализующего Future, с помощью замыкания и так далее.
mio — библиотека-абстракция над Linux epoll API и низкоуровневыми API
ожидания готовности операций ввода-вывода других ОС.
tokio — фреймворк, связывающий futures и mio и предоставляющий воз-
30
можность абстрагироваться от циклов обработки событий и обращаться с Future
как с подобием потоков ОС — достаточно передать свои Future в spawn() и их
готовность будет опрашиваться tokio автоматически до их завершения. Первый
Future, инициализирующий Task’и приложения, запускается с помощью ::tokio::run()
и, в дальнейшем, run() завершается только когда не остаётся ни одного незавершённого Future, зарегистрировавшегося для уведомления какого-либо Task.
pub trait Future {
type Item;
type Error;
fn poll(
&mut self,
cx: &mut Context
) -> Result<Async<Self::Item>, Self::Error>;
}
pub trait Stream {
type Item;
type Error;
fn poll_next(
&mut self,
cx: &mut Context
) -> Result<Async<Option<Self::Item>>, Self::Error>;
}
pub trait Sink {
type SinkItem;
type SinkError;
fn poll_ready(
&mut self,
cx: &mut Context
) -> Result<Async<()>, Self::SinkError>;
fn start_send(
&mut self,
item: Self::SinkItem
) -> Result<(), Self::SinkError>;
fn poll_flush(
&mut self,
cx: &mut Context
) -> Result<Async<()>, Self::SinkError>;
fn poll_close(
&mut self,
cx: &mut Context
) -> Result<Async<()>, Self::SinkError>;
}
Листинг 5: Типажи Future, Stream и Sink
31
Для того, чтобы какой-либо future был опрошен в будущем, необходимо,
чтобы для его Task пришло уведомление. Объекты, предоставляющие возможности ввода-вывода регистрируют Task, который опрашивал готовность ещё не выполненной операции для уведомления из цикла обработки событий tokio. Иные
объекты, например передающие сообщения между Future, могут сохранять дестриптор опрашивающего Task и вручную отправлять ему уведомление при исполнении опереции/наступлении возможности исполнения операции.
Низкоуровневый ввод-вывод
Для использования функций UNIX API, API, специфичных для Linux, в Rust
необходимы bindings для C функций API. Bindings для большинства необходимых низкоуровневых функций уже предоставлен библиотеками libc[31] и nix[32].
Функции, готовый bindings для которых реализуются в коде вручную.
2.1.3
Обработка UNIX сигналов
Примером такого объекта, отправляющего уведомления заинтересованным
Task вручную в разработанном сервере является канал передачи сообщений от
одного отправителя к многим получателям, создаваемый ::broadcast::broadcaster()
и использующийся для отправки сообщений о поступлении UNIX сигналов процессу.
Так как весь код сервера после инициализации работает под управлением tokio, а обработчики UNIX сигналов не могут производить сложные операции, такие как отправка уведомлений Task, не вызывая неопределённого поведения, необходимо использовать иной механизм получения сигналов, работающий
в tokio. Он реализован в библиотеке tokio-signal[33].
Обработчики сигналов создаются как в листинге 6.
use ::tokio_signal::unix as signal;
// ...
let mut interrupted =
,→
signal::Signal::new(signal::SIGINT).flatten_stream();
let mut terminated =
,→
signal::Signal::new(signal::SIGTERM).flatten_stream();
Листинг 6: Создание Stream для получения UNIX сигналов
Их можно опрашивать обычным способом с помощью poll().
32
В разработанном сервере обрабатываются сигналы, свидетельствующие о
необходимости завершения работы сервера. Чтобы работа сервера завершилась,
необходимо, чтобы завершились все Future, когда-либо переданные spawn(). Для
рассылки подобных уведомлений от одного Task к множеству других не предусмотрено подходящих стандартных средств в tokio, поэтому был реализован модуль ::broadcast.
Структуры данных, определяемые в нём приведены в листинге 7.
struct Inner<T> {
rx_tasks: RwLock<Vec<Option<Task>>>,
tx_task: RwLock<Option<Task>>,
next_id: AtomicUsize,
rx_count: AtomicUsize,
tx_present: AtomicBool,
message: RwLock<Option<T>>
}
pub struct Receiver<T> {
inner: Arc<Inner<T>>,
id: usize
}
pub struct Sender<T> {
inner: Arc<Inner<T>>
}
Листинг 7: Структуры данных модуля ::broadcaster
Функция ::broadcast::broadcaster<T>(max_receivers: usize) -> (Receiver<T>,
Sender<T>) возвращает пару получатель/отправитель уведомлений. В дальнейшем, может быть создано до max_receivers получателей с помощью
::broadcast::Receiver::clone().
Каждому Receiver присваевается при создании id, который соответствует
его индексу в Inner::rx_tasks. Далее, при первой асинхронной операции над Receiver,
в rx_tasks записывается текущий Task.
Id выделяются чтением Inner::next_id и его увеличением на единицу атомарно.
Task в Inner::tx_task также записывается при первой операции.
Это необходимо для возможности создания broadcaster в одном Task и их
передаче в Future, передаваемые в spawn().
33
При отправке нового broadcast, уведомление отправляется всем Task, записанным в rx_tasks.
Также, при удалении последнего Receiver, уведомление отправляется для
tx_task.
Для различения отсутствия tx_task и ситуации, когда broadcast ещё не отправлялись, используется Inner::tx_present, равный true при создании broadcaster,
которому присваевается false при удалении Sender.
Существование Receiver определяется по значению их счётчика
Inner::rx_count.
2.1.4
Обработка NDP запросов
Для получения и отправки пакетов асинхронно, необходимо использовать
не сокеты ОС напрямую, пользоваться обёртками вокруг них, позволяющими использовать event loop, предоставляемый tokio. Для этого, сначала необходимо сделать объекты, представляющие сокеты совместимыми с mio. Для этого необходимо реализовать типаж Evented, приведённый в листинге 8. В случае, если объект,
pub trait Evented {
fn register(
&self,
poll: &Poll,
token: Token,
interest: Ready,
opts: PollOpt
) -> Result<()>;
fn reregister(
&self,
poll: &Poll,
token: Token,
interest: Ready,
opts: PollOpt
) -> Result<()>;
fn deregister(&self, poll: &Poll) -> Result<()>;
}
Листинг 8: Типаж ::mio::event::Evented
представляющий интерес является файловым дескриптором UNIX-подобных систем, для упрощения реализации Evented в mio предусмотрен wrapper EventedFd.
С его помощью, Evented реализуется однотипно для разных объектов, поэтому
представлен в коде макросом gen_evented_eventedfd!, который используя функ-
34
цию as_raw_fd стандартного типажа AsRawFd, передаёт агрументы register(),
reregister() и deregister() в реализацию Evented EventedFd. Такое решение было
принято для упрощения написания типа, представляющего файловый дескриптор
сокета ОС в IPv6RawSocketAdapter и IPv6PacketSocketAdapter (реализация обёртки сокета для работы tokio в rsndpproxy).
Также, для работы объекта, осуществляющего ввод-вывод в tokio, необходимо регистрировать ожидаемые события в Reactor. Для типов, реализующих типаж mio Evented, этот процесс упрощается с помощью PollEvented2 обёртки tokio.
Работа с ней продемонстрирована в листинге 9.
pub fn sendto_direct(
&mut self,
buf: &[u8],
addr: SocketAddrV6,
flags: SendFlags
) -> ::std::result::Result<size_t, ::errors::Error> {
let mut poll_evented = self.0.lock().unwrap();
if let Async::NotReady = poll_evented.poll_write_ready()
.map_err(Error::TokioError)? {
return Err(make_again());
}
match poll_evented.get_mut().sendto(buf, addr, flags) {
Err(e) => {
let err = e.downcast::<Error>().unwrap();
if let Again = err.kind() {
poll_evented.clear_write_ready()
.map_err(Error::TokioError)?;
return Err(err);
}
let new_e: ::failure::Error = err.into();
Err(Error::SocketError(new_e.compat()))
},
Ok(x) => Ok(x)
}
}
Листинг 9: Пример использования PollEvented2 в rsndpproxy (часть реализации
отправки пакета)
Так как для асинхронной работы кода в tokio необходимо представлять операции ввода-вывода как Future, для каждой из них определён свой тип, хранящий необходимые для её завершения данные. В листинге 10 продемонстрирована
структура, представляющая операцию отправки пакета.
35
pub struct IPv6RawSocketSendtoFuture(
Option<IPv6RawSocketSendtoFutureState>
);
struct IPv6RawSocketSendtoFutureState {
sock: IPv6RawSocketPE,
buf: Bytes,
addr: SocketAddrV6,
flags: SendFlags
}
Листинг 10: Представление структуры, хранящей данные для отправки пакета с
помощью IPv6RawSocket, используемой IPv6RawSocketAdapter
Пакеты принимаются с помощью packet socket типа SOCK_DGRAM, что
означает, что заголовок Ethernet не попадает в принимаемые данные. Принимается в неизменном виде весь IPv6 пакет со всеми полями заголовка.
Для разбора пакетов, в rsdpproxy применяется библиотека pnet[34], а именно — её часть — pnet_packet. Особенность pnet_packet в том, что в её исходном
коде формат пакетов описывается декларативно, используя кодогенерацию, вместо процедурного разбора, что уменьшает вероятность наличия ошибок в ней.
Представление NDP Neighbor Solicitation и Neighbor Advertisement пакетов
в коде сервера приведено в листинге 11.
#[derive(Debug)]
pub struct Advertisement {
pub src: Ipv6Addr,
pub dst: Ipv6Addr,
pub target: Ipv6Addr,
pub ll_addr_opt: Option<MacAddr>
}
#[derive(Debug)]
pub struct Solicitation {
pub src: Ipv6Addr,
pub dst: Ipv6Addr,
pub target: Ipv6Addr,
pub ll_addr_opt: Option<MacAddr>
}
Листинг 11: Представление NDP Neighbor Solicitation и Neighbor Advertisement в
rsndpproxy
Task, непосредственно ответственные за обработку NDP запросов представлены как в листинге 12.
36
type StreamE<T> = dyn(Stream<Item = T, Error = ::failure::Error>);
pub struct Server {
recv_sock: futures::IPv6PacketSocketAdapter,
send_sock: futures::IPv6RawSocketAdapter,
input: SendBox<StreamE<(Solicitation, Arc<PrefixConfig>)>>,
quit: Receiver<::QuitKind>,
drop_allmulti: DropAllmulti,
ifname: String,
queued_sends: Arc<AtomicUsize>,
max_queued: usize
}
gen_boolean_enum!(DropAllmulti);
Листинг 12: Обработчик NDP запросов
Реализация типажа Future для Server каждый poll() опрашивает quit Receiver
и Stream input и отправляет NDP Neighbor Advertisement при получении соответствующего запроса.
Stream Server::input представляет собой поток валидированных пакетов NDP
Neighbor Solicitation в разобранном виде вместе со ссылками на настройки соответствующего интерфейса.
Чтобы получать NDP Neighbor Solicitation пакеты, не адресованные данному хосту, необходимо настроить сетевой интерфейс, с которого осуществляется
приём для приёма всех multicast пакетов. Это делается с помощью установки флага интерфейса IFF_ALLMULTI. Флаг Server::drop_allmulti необходим для возвращения старого значения IFF_ALLMULTI при завершении работы rsndpproxy.
Server::ifname — кешированная строка с именем интерфейса, на котором
запущен Server, необходимая для логгирования.
Server::queued_sends — счётчик находящихся в процессе отправки пакетов,
необходим для реализации настройки max_в конфигурационном файле, также хранящейся в Server::max_queued.
Код отправки NDP Neighbor Advertisement приведён в листинге 13.
37
let adv = Advertisement {
src: solicit.target,
dst: solicit.src,
target: solicit.target,
ll_addr_opt: Some(self.recv_sock.get_interface_mac())
};
let adv_packet = adv.solicited_to_packet(
prefix_conf.override_flag,
prefix_conf.router_flag
);
let queued_sends = self.queued_sends.clone();
let queued = queued_sends.fetch_add(1, Ordering::Relaxed);
if queued >= self.max_queued {
warn!(
”Maximum queued packet number ({}) \
for interface {} exceeded.”,
self.max_queued,
self.ifname
);
queued_sends.fetch_sub(1, Ordering::Relaxed);
continue;
}
let dst = SocketAddrV6::new(
solicit.src,
0,
0,
self.recv_sock.get_interface_index() as u32
);
::tokio::spawn(
self.send_sock.sendto(
adv_packet,
dst,
SendFlags::empty()
).map(
|_| ()
).map_err(
|e| log_err(Error::LinuxNetworkError(e).into())
).inspect(move |_| {
queued_sends.fetch_sub(1, Ordering::Relaxed);
})
);
Листинг 13: Отправка NDP Neighbor Advertisement
38
2.2
Безопасность
Несмотря на то, что в языке Rust выполняются многие гарантии безопасности, всё равно остаются возможности для возникновения уязвимостей: взаимодействие с кодом, написанным на C, ошибки в компиляторе, использование unsafe
конструкций в коде и так далее[18].
Для предотвращения эксплуатации возможных уязвимостей необходимо ограничить программу в правах, но для использования ICMPv6 и изменения настроек
сетевых устройств, необходимо иметь часть привилегий root, но для безопасности
использования программы нужно отказаться от максимального количества привилегий.
Традиционный и переносимый способ временного получения привилегий
root — это установка эффективного UID равным 0 только на короткие промежутки времени, в ходе которых выполняются привилегированные операции и полный
отказ от root прав по истечении такой необходимости. Данному серверу необходима часть прав root не только при инициализации, но и при завершении для возвращения настроек сетевого интерфейса в прежнее состояние, поэтому полностью
отказаться от root прав на какой-то стадии исполнения не получится. Тогда программа получает возможность пользоваться полными правами root по запросу,
что представляет потенциальную опасность эксплуатации.
В Linux есть возможность избежать этого: привилегии root разбиты на множество частей, таких как: доступ к произвольным файлам, управление устройствами в /dev и так далее. Для разработанного сервера необходимы привилегии
CAP_NET_RAW — возможность принимать и отправлять сетевые пакеты произвольного содержания — и CAP_NET_ADMIN — возможность изменять настройки сетевых устройств[35].
Сервер стартует с правами root, далее отказывается от них всех, кроме указанных выше CAP_NET_RAW и CAP_NET_ADMIN.
Сервер имеет возможность полностью отказаться от UID 0, если UID:GID
для его работы указаны в конфигурационном файле, что позволит отказаться от
прав доступа к файлам и папкам, на операции с которыми имеет права root.
При старте сервера также происходит отказ от дополнительных групп (auxiliary groups), что приведёт к полному отказу от всех прав, которые не имеет обычный пользователь, а, возможно, и от их части.
39
Но, при использовании capabilities остаётся возможность повышения привилегий с помощью исполнения других программ с file capabilities или установленный SUID/SGID битом. Эту возможность отключается, использованием Linux
prctl-команды PR_SET_NO_NEW_PRIVS и securebits[6, 35].
Отказ от привилегий происходит следующим образом: сначала устанавливаются необходимые значения всех securebits, кроме SECBIT_KEEP_CAPS
и SECBIT_KEEP_CAPS_LOCKED:
let mut bits =
SecBits::NoSetuidFixup
| SecBits::NoSetuidFixupLocked
| SecBits::NoRoot
| SecBits::NoRootLocked
| SecBits::NoCapAmbientRaise
| SecBits::NoCapAmbientRaiseLocked;
set_securebits(bits)
.map_err(|e| Error::SecurebitsError(
::failure::Error::from(e).compat())
)?;
Затем происходит отказ от auxiliary groups и переключение UID и GID на
непривилегированные, если это настроено в конфигурационном файле:
drop_supplementary_groups().context(”cannot drop supplementary
,→
groups”)?;
debug!(”dropped supplementary groups”);
if let Some(ref su) = *su {
let bits = get_securebits()
.map_err(|e| Error::SecurebitsError(
::failure::Error::from(e).compat())
)?
| SecBits::KeepCaps;
set_securebits(bits)
.map_err(|e| Error::SecurebitsError(
::failure::Error::from(e).compat())
)?;
switch::set_current_gid(su.gid).map_err(Error::PrivDrop)?;
switch::set_current_uid(su.uid).map_err(Error::PrivDrop)?;
debug!(”dropped uid and gid 0”);
} else {
40
warn!(”consider changing user with \”su = user:group\” option”);
}
Здесь SECBIT_KEEP_CAPS используется для сохранения привилегий при потере
UID 0.
После этого процессу (и его детям) запрещается получать новые capabilities:
bits.remove(SecBits::KeepCaps);
bits.insert(SecBits::KeepCapsLocked);
set_securebits(bits)
.map_err(|e| Error::SecurebitsError(
::failure::Error::from(e).compat())
)?;
debug!(”securebits set to {:?}”, bits);
set_no_new_privs().context(”cannot set NO_NEW_PRIVS”)?;
debug!(”PR_SET_NO_NEW_PRIVS set”);
После этого процесс отказывается от лишних capabilities:
let mut caps = Capabilities::new().map_err(Error::PrivDrop)?;
let req_caps = [
Capability::CAP_NET_ADMIN,
Capability::CAP_NET_RAW
];
if !caps.update(&req_caps, Flag::Permitted, true) {
return Err(Error::PrivDrop(io::Error::new(
io::ErrorKind::Other,
”cannot update a capset”
)).into());
}
caps.apply().map_err(Error::PrivDrop)?;
if !caps.update(&req_caps, Flag::Effective, true) {
return Err(Error::PrivDrop(io::Error::new(
io::ErrorKind::Other,
”cannot update a capset”
)).into());
}
caps.apply().map_err(Error::PrivDrop)?;
debug!(”dropped linux capabilities”);
41
сначала удаляя их из permitted set, потом — из effective set.
В вышеприведённом коде для работы с Linux capabilities использовалась
Rust библиотека capabilities[36].
Таким образом, программа получит привилегии, необходимые для низкоуровневой работы с сетью, будучи запущенной root, но не сможет выполнять
каких-либо других действий, недоступных простому пользователю после отказа
от лишних привилегий.
Ещё, при запуске осуществляется выход из текущей process group, сессии и
перенаправление stdin, stdout и stderr в /dev/null, что позволяет максимально избавиться от возможного влияния других процессов в системе.
Также, делается невозможным получение с помощью используемого packet
socket каких-либо пакетов, кроме NDP Neighbor Solicitation: сокет создаётся с указанием ethertype IPv6, что делает возможным принятие только IPv6 пакетов.
Далее, на этом сокете используется BPF фильтр. BPF (Berkeley Packet Filter)
представляет собой байткод, интерпретируемый ядром, которому доступны для
чтения все данные пакетов, приходящих на сокет, и принимающий решение, должны ли эти пакеты передаваться процессу, читающему из сокета. Создание спользуемого в данном сервере BPF фильтр приведён в листинге 1.
fn create_filter() -> Box<BpfProg> {
use ::linux_network::BpfCommandFlags as B;
use ::linux_network::raw::*;
use ::nix::libc::*;
bpf_filter!(
bpf_stmt!(B::LD | B::B | B::ABS, 6);
bpf_jump!(B::JMP | B::JEQ | B::K, IPPROTO_ICMPV6, 0, 3);
bpf_stmt!(B::LD | B::B | B::ABS, 40);
bpf_jump!(B::JMP | B::JEQ | B::K, ND_NEIGHBOR_SOLICIT, 0,
,→
1);
bpf_stmt!(B::RET | B::K, ::std::u32::MAX);
bpf_stmt!(B::RET | B::K, 0);
)
}
Листинг 1: Функция, создающая используемый в сервере BPF фильтр
Он пропускает только IPv6 пакеты, являющиеся ICMPv6 пакетами, также
42
являющиеся NDP Neighbor Solicitation пакетами, то есть, пропускает только те
типы пакетов, которые должны обрабатываться разработанным сервером далее.
Далее, была заблокирована возможность изменения этого фильтра для данного
сокета.
Для сокета, используемого для отправки NDP Neighbor Advertisement, установлена фильтрация пакетов, не позволяющая с его помощью получать никакие
пакеты: это – raw socket, при создании которого указан протокол ICMPv6, что позволяет принимать и отправлять с его помощью только ICMPv6 пакеты. Далее, для
него установлен ICMPV6_FILTER, запрещающий приём любых ICMPv6 пакетов.
Был написан модуль SELinux[37] (Security Enhanced Linux) — механизма
обеспечения безопасности Linux, работающего по принципу «запрещено всё, что
не разрешено», — для rsndpproxy. Это позволяет ограничить процесс сервера теми операциями, которые необходимы ему для нормального функционирования, и
исключить выполнение иных при эксплуатации возможной уязвимости. Файловые контексты и правила SELinux приведены в листинге 2.
43
/etc/rsndpproxy.conf
-,→
gen_context(system_u:object_r:rsndpproxy_conf_t,s0)
/usr/local/sbin/rsndpproxy -,→
gen_context(system_u:object_r:rsndpproxy_exec_t,s0)
/var/run/rsndpproxy.pid
-,→
gen_context(system_u:object_r:rsndpproxy_var_run_t,s0)
policy_module(rsndpproxy, 0.0.1)
require {
class capability { net_admin net_raw setgid setuid };
class netlink_route_socket { create nlmsg_read read write };
class packet_socket { bind create ioctl read setopt };
class process setcap;
class rawip_socket { create ioctl setopt write };
class udp_socket { create ioctl };
type etc_t;
type var_run_t;
}
type rsndpproxy_t;
type rsndpproxy_exec_t;
type rsndpproxy_var_run_t;
type rsndpproxy_conf_t;
init_daemon_domain(rsndpproxy_t, rsndpproxy_exec_t)
files_pid_file(rsndpproxy_var_run_t)
manage_files_pattern(rsndpproxy_t, var_run_t, rsndpproxy_var_run_t)
rw_files_pattern(rsndpproxy_t, var_run_t, rsndpproxy_var_run_t)
filetrans_pattern(rsndpproxy_t, var_run_t, rsndpproxy_var_run_t,
,→
file)
files_type(rsndpproxy_conf_t)
read_files_pattern(rsndpproxy_t, etc_t, rsndpproxy_conf_t)
auth_read_passwd(rsndpproxy_t)
miscfiles_read_localization(rsndpproxy_t)
dev_read_sysfs(rsndpproxy_t)
logging_send_syslog_msg(rsndpproxy_t)
allow rsndpproxy_t self : capability { net_admin net_raw setgid
,→
setuid };
allow rsndpproxy_t self : process setcap;
allow rsndpproxy_t self : rawip_socket { create ioctl setopt write
,→
};
allow rsndpproxy_t self : packet_socket { bind create ioctl read
,→
setopt };
corenet_raw_sendrecv_generic_if(rsndpproxy_t)
allow rsndpproxy_t self : netlink_route_socket { create nlmsg_read
,→
read write };
allow rsndpproxy_t self : udp_socket { create ioctl };
Листинг 2: Файловые контексты и правила SELinux для rsndpproxy
44
3
3.1
Тестирование
Тестовая среда, использовавшаяся при
разработке
Тестовая среда, использовавшаяся в процессе разработки была развёрнута
на локальной машине с использованием Linux network namespaces. Это механизм
изоляции приложений, с помощью которого можно развернуть несколько сетей
на одной машине. В только созданном namespace существует только интерфейс
lo, представляющий собой связь «с самими собой», которая позволяет хосту отправлять пакеты на localhost.
После создания namespace, в нём можно создавать или передавать в него
извне другие сетевые интерфейсы.
Разные network namespace могут связываться с помощью virtual ethernet pair
(veth), создаваемого в паре. Каждый конец veth необходимо переместить в соответствующий namespace. Заметим, что через обыкновенный bridge пакеты между
namespace передать невозможно, так как переданный в другой namespace интерфейс удалится из моста, тогда как пакеты, посылаемые через один конец veth, всегда будут доставлены на другой и в случае нахождения в разных namespaces.
Также, в namespaces действуют свои таблицы файрволла ip(6)tables, пустые
и разрешающие любую передачу по-умолчанию. Forwarding sysctl и другие сетевые настройки также зависят от namespace.
Разворачивание осуществлялось таким образом (листинг 1): создавалась сеть,
в которой были два ethernet сегмента с диапазонами адресов fc00::/64 с хостом
fc00::2:2 и fc00::1:0/112 с хостом fc00::1:1. Они связывались хостом с адресами
fc00::1:ffff и fc00::2:ffff. На fc00::1:1 был настроен шлюз по умолчанию fc00::1:ffff.
Для отправки пакетов из сегмента с IPv6 сетью fc00::/64 в сегмент с сетью
fc00::1:0/112, необходима корректная работа rsndpproxy на сетевом интерфейсе с
адресом fc00::2:ffff, чего и было достигнуто. Удачный тестовый обмен был аналогичен приведённому в секции 1.2.
45
ip link add rsp-inner type veth peer name rsp-mid-in
ip link add rsp-outer type veth peer name rsp-mid-out
ip netns add rsp-inner
ip netns add rsp-middle
ip netns add rsp-outer
ip
ip
ip
ip
link
link
link
link
set
set
set
set
rsp-inner netns rsp-inner
rsp-mid-in netns rsp-middle
rsp-mid-out netns rsp-middle
rsp-outer netns rsp-outer
ip netns exec rsp-inner ip link set lo up
ip netns exec rsp-middle ip link set lo up
ip netns exec rsp-outer ip link set lo up
ip
ip
ip
ip
netns
netns
netns
netns
exec
exec
exec
exec
rsp-inner ip link set rsp-inner up
rsp-middle ip link set rsp-mid-in up
rsp-middle ip link set rsp-mid-out up
rsp-outer ip link set rsp-outer up
ip netns exec rsp-middle ip addr add fc00::1:ffff/112 dev rsp-mid-in
ip netns exec rsp-middle ip addr add fc00::2:ffff/64 dev rsp-mid-out
ip netns exec rsp-inner ip addr add fc00::1:1/112 dev rsp-inner
ip netns exec rsp-inner ip -6 route add default via fc00::1:ffff dev
,→
rsp-inner
ip netns exec rsp-outer ip addr add fc00::2:1/64 dev rsp-outer
ip netns exec rsp-inner sysctl -q net.ipv6.conf.all.forwarding=0
ip netns exec rsp-middle sysctl -q net.ipv6.conf.all.forwarding=1
ip netns exec rsp-outer sysctl -q net.ipv6.conf.all.forwarding=0
Листинг 1: Разворачивание тестовой среды
3.2
Практическая проверка
Для проверки работы разработанного ПО на практике, оно было использовано на удалённом VPN сервере, конфигурация сети хостера которого (evolutionhost.com, реселлер OVH) создавала проблему, описанную и решаемую в этой работе. Работоспособность сети была восстановлена. Пример результатов тестирования работоспособности сети с помощью сервиса test-ipv6.com без использования rsndpproxy приведён в скриншотах на рисунке 1, а результаты при работе
rsndpproxy — в скриншотах на рисунке 2.
46
Рис. 1: Пример выполнения теста VPN без rsndpproxy
Рис. 2: Пример выполнения теста VPN с работающим rsndpproxy
Проверка надёжности
В ходе работы, разработанный сервер не отвечал на NDP пакеты, адресованные хостам, не находящимся в сконфигурированном префиксе, не завершал
47
работы аварийно. Деградации сервиса при длительной работе без перезапуска не
наблюдалось.
Были инициированы ошибки чтения пакетов из сокета с помощью запрета
на это действие с помощью изменения правил selinux. Это привело к появлению
таких сообщений в системном журнале:
Jun 07 22:10:32 ers rsndpproxy[724]: io error ocurred on a socket:
,→
io error
Jun 07 22:10:32 ers rsndpproxy[724]: rsndpproxy stopping
При этом, сервер нормально завершил работу.
Были инициированы ошибки отправки пакетов с помощью запрета на это
действие с помощью изменения правил selinux. Это привело к появлению таких
сообщений в системном журнале:
Jun 07 23:03:11 ers rsndpproxy[908]:
,→
socket: io error
Jun 07 23:03:12 ers rsndpproxy[908]:
,→
socket: io error
Jun 07 23:03:13 ers rsndpproxy[908]:
,→
socket: io error
Jun 07 23:03:14 ers rsndpproxy[908]:
,→
socket: io error
Jun 07 23:03:15 ers rsndpproxy[908]:
,→
socket: io error
Jun 07 23:03:16 ers rsndpproxy[908]:
,→
socket: io error
io error: io error ocurred on a
io error: io error ocurred on a
io error: io error ocurred on a
io error: io error ocurred on a
io error: io error ocurred on a
io error: io error ocurred on a
После возврата корректных правил selinux, сервер продолжил работу в нормальном режиме.
Таким образом, ошибки операций с сокетами не вызывают некорректной работы rsndpproxy или аварийного его завершения. Изначальное состояние сетевых
устройств восстанавливается сервером при завершении.
Использование некорректного конфигурационного файла или некорректных
параметров коммандной строки при старте сервера также приводят лишь к печати соответствующего сообщения об ошибке и нормальному завершению работы
сервера.
48
3.3
Оценка производительности
Для оценки производительности разработанного сервера было произведено измерение задержки от отправки NDP запроса до получения хостом ответа.
Задержка была измерена 10 раз. Была также измерена задержка получения ответа
от встроенного в ядро Linux NDP сервера (не NDP proxy). Для сравнения, измерена задержка ответа ядром Linux на простой ping пакет. Все измерения производились на одном и том же оборудовании. «Разогрева» с помощью повышенной
нагрузки на процессор или повышения частоты его работы не производилось, поэтому полученные измерения ограничивают снизу оценку производительности
сервера. Измеренные значения приведены в таблице 1.
Таблица 1: Измеренные значения задержки получения ответа, мкс
1
rsndpproxy 536
Linux NDP 93
ping
118
2
1005
56
66
3
696
91
67
4
5
816 500
60 66
233 70
6
7
8
705 37 959
221 24 46
70 73 67
9
706
67
71
10
608
91
65
среднее
656
81
90
Можно увидеть, что результаты ping не отличаются значительно от результатов Linux реализации NDP, что подтверждает корректность выбранного метода
измерений.
Разработанный сервер отвечает в несколько раз медленнее, чем встроенная
в ядро стандартная реализация NDP. Это объясняется работой rsndpproxy в пользовательском процессе, что уменьшает эффективность его работы.
При этом, обработка запроса за 656мс соответствует минимальной производительности в ∼1524pps (packet per second — пакет в секунду). При возникновении такой ситуации в реальных нагрузках, сервер скорее всего будет перегружен
трафиком, проходящим через него, для которого и понадобилось совершать все
эти NDP запросы. Так, при наличии соединения 10Gbit/s на интерфейсе маршрутизатора, на котором работает rsndpproxy, одному клиенту (а NDP запрос говорит,
о том, что клиент активен и обменивается трафиком через него) достанется всего
6,65Mbit полосы пропускания, причём это — верхняя оценка.
Эта оценка возможной производительности rsndpproxy на одном интерфейсе. Каждый интерфейс обрабатывается в отдельном tokio task и работа с ним может происходить параллельно с работой на других интерфейсах многопоточно,
что увеличивает возможную общую производительность.
49
Таким образом, производительности разработанного сервера достаточно при
его использовании на распространённом сетевом оборудовании — с сетевыми интерфейсами пропускной способности 10GBit/s.
50
Заключение
В данной работе рассматривается задача обеспечения работы IPv6 сети в
условиях её разделения на части, находящиеся в разных сетях канального уровня, то есть части, между которыми не проходят NDP пакеты, необходимые для
работы разрешения адресов канального уровня OSI и определения доступности
«хостов-соседей» (Neighbor Unreachability Detection). Подобные задачи являются
востребованными в связи с всё бо́льшим распространением IPv6 и распространением практики выделения больших подсетей клиентам, как и стандартов частично её обуславливающих.
Очевидно, для решения подобных задач должно быть разработано решение,
подходящее для одной из наиболее распространённых серверных платформ, из
которых для реализации была выбрана ОС GNU/Linux.
Достигнута следующая цель: исследована задачи обспечения работы IPv6
сети в условиях её разделения на части, между которыми не проходят NDP пакеты, необходимые для работы разрешения адресов канального уровня OSI и определения доступности «хостов-соседей» (Neighbor Unreachability Detection) и программно реализован выбранный оптимальный вариант её решения.
Были выполнены следующие задачи:
• Была изучена литература по рассматриваемому вопросу, сделано заключение о необходимости реализации задачи обеспечения работы IPv6 сети в заданных условиях в виде сервера для ОС GNU/Linux, обрабатывающего NDP
Neighbor Solicitation запросы и отвечающего NDP Neighbor Advertisement
пакетами.
• Разработан сервер в соответствии с заключением о необходимой реализации.
• Использованы доступные на выбранной для реализации платформе функции обеспечения безопасности.
• Сделаны выводы по результатам исследования.
Таким образом, выполнены все поставленные задачи данной работы.
51
Выбранный для реализации метод решения поставленной задачи — сервер
протокола NDP — обеспечивает бо́льшую простоту настройки и/или потенциально бо́льшую производительности, чем иные рассмотренные методы.
Работа выполнена на языке Rust[38], который является языком системного программирования, разработанным с фокусом на безопасности и производительности (абстракциях, не несущих с собой потерь производительности — zerocost abstractions)[18]. Также, используются библиотеки с crates.io[20] — стандартного репозитория Rust-библиотек и утилит. В их числе — фреймворки mio[29],
tokio[28], serde[23] определившие во многом вид реализации.
В реализации использованы такие функции обеспечения безопасности ОС
GNU/Linux, как capabilities (в том числе, securebits), и, соответственно, работа
от имени непривилегированного пользователя, группы; сброс привилегий, связанных с дополнительными группами (auxiliary groups); использование предварительной фильтрации получаемых сетевых пакетов на сокетах, в том числе —
запрет получения на raw сокете, используемом для отправки пакетов.
Разработанное ПО было протестировано, выполняет свои функции нормально, дефектов не выявлено.
Работа имеет теоретическое и практическое значение, так как полученный
анализ методов обеспечения работы IPv6 сети в условиях её разделения на части, находящиеся в разных сетях канального уровня, между которыми не проходят NDP пакеты, необходимые для работы разрешения адресов канального уровня
OSI и определения доступности «хостов-соседей» (Neighbor Unreachability Detection), может быть использован в дальнейших исследованиях работы IPv6 сетей, а
разработанный сервер NDP протокола имеет прямое практическое применение.
52
Литература
[1] IPv6 Adoption [Text], Google LLC. — [S. l. : s. n.]. — Access mode:
https://www.google.com/intl/en/ipv6/statistics.html (online; accessed: 2018-06-06).
[2] IANA IPv4 Address Space Registry [Text], IANA (Internet Assigned Numbers
Authority). — [S. l. : s. n.]. — Access mode: https://www.iana.org/assignments/ipv4address-space/ipv4-address-space.xhtml (online; accessed: 2018-06-06).
[3] Iamartino, Daniele. IPv6 setup in two hosting providers compared: awful (OVH)
and awesome (Online.net) [Text]. — [S. l. : s. n.], 2016. — Feb. — Access mode:
https://otacon22.com/2016/02/21/two-hosting-providers-ipv6-setups-compared-ovh-online-net/.
[4] Narten, T. Neighbor Discovery for IP version 6 (IPv6) [Text], Internet Engineering
Task Force (IETF) Network Working Group. — [S. l. : s. n.], 2007. — Sep. —
Access mode: https://tools.ietf.org/html/rfc4861.
[5] Chown, T. Analysis of the 64-bit Boundary in IPv6 Addressing [Text], Ed. by
B. Carpenter ; Internet Engineering Task Force (IETF). — [S. l. : s. n.], 2015. —
Jan. — Access mode: https://tools.ietf.org/html/rfc7421.
[6] The Linux kernel documentation [Text]. — [S. l. : s. n.]. — Access mode:
https://git.kernel.org/.
[7] Adolfsson, Daniel. ndppd github repository [Text]. — [S. l. : s. n.]. — Access
mode: https://github.com/DanielAdolfsson/ndppd.
[8] The OSI reference model [Text] // Proceedings of the IEEE. — 1983. —
Dec. — Vol. 71, no. 12. — P. 1334–1340. — Access mode:
http://dx.doi.org/10.1109/proc.1983.12775.
[9] IEEE Standard for Ethernet [Text] // IEEE Std 802.3-2015 (Revision of IEEE Std 802.32012). — 2016. — Mar. — P. 1–4017.
[10] Crawford, M. Transmission of IPv6 Packets over Ethernet Networks [Text], Internet Engineering Task Force (IETF) Network Working Group. — [S. l. : s. n.],
1998. — Dec. — Access mode: https://tools.ietf.org/html/rfc2464.
53
[11] Postel, J. INTERNET CONTROL MESSAGE PROTOCOL [Text], Internet Engineering Task Force (IETF). — [S. l. : s. n.], 1981. — Sep. — Access mode:
https://tools.ietf.org/html/rfc792.
[12] Conta, A. Internet Control Message Protocol (ICMPv6) for the Internet Protocol
Version 6 (IPv6) Specification [Text], Internet Engineering Task Force (IETF). —
[S. l. : s. n.], 2006. — Mar. — Access mode: https://tools.ietf.org/html/rfc4443.
[13] Thomson, S. IPv6 Stateless Address Autoconfiguration [Text], Internet Engineering Task Force (IETF) Network Working Group. — [S. l. : s. n.], 2007. — Sep. —
Access mode: https://tools.ietf.org/html/rfc4862.
[14] Bound, J. Dynamic Host Configuration Protocol for IPv6 (DHCPv6) [Text], Ed. by
R. Droms ; Internet Engineering Task Force (IETF) Network Working Group. —
[S. l. : s. n.], 2003. — Jul. — Access mode: https://tools.ietf.org/html/rfc3315.
[15] Erickson, Jon. Hacking: The Art of Exploitation [Text] / Jon Erickson. — Second
edition. — [S. l.] : No Starch Press, 2008. — Feb. — ISBN: 978-1593271442.
[16] Hertz, Matthew. Quantifying the Performance of Garbage Collection vs. Explicit Memory
Management [Text] / Matthew Hertz, Emery D. Berger // Proceedings of the 20th
Annual ACM SIGPLAN Conference on Object-oriented Programming, Systems,
Languages, and Applications. — OOPSLA ’05. — New York, NY, USA : ACM,
2005. — P. 313–326. — Access mode: http://doi.acm.org/10.1145/1094811.1094836.
[17] Brown, Neil. A taste of Rust [Text] // [LWN.net]. — [S. l. : s. n.], 2013. — Apr. —
Access mode: https://lwn.net/Articles/547145/.
[18] The Rust programming language FAQ [Text]. — [S. l. : s. n.]. — Access mode:
https://www.rust-lang.org/en-US/faq.html.
[19] Herman, Dave. Shipping Rust in Firefox [Text], Mozilla Corporation. — [S. l. :
s. n.], 2016. — Jul. — Access mode: https://hacks.mozilla.org/2016/07/shipping-rust-infirefox/.
[20] The Rust community’s crate registry [Text]. — [S. l. : s. n.]. — Access mode:
https://crates.io/.
54
[21] Stack Overflow Developer Survey 2018 [Text]. — [S. l. : s. n.], 2018. — Jan. —
Access mode: https://insights.stackoverflow.com/survey/2018.
[22] clap — Fast, Configurable. Argument Parsing for Rust [Text]. — [S. l. : s. n.]. —
Access mode: https://clap.rs.
[23] Serialization framework for Rust [Text]. — [S. l. : s. n.]. — Access mode:
https://serde.rs/.
[24] Crichton, Alex. A TOML encoding/decoding library for Rust [Text]. — [S. l. : s.
n.]. — Access mode: https://github.com/alexcrichton/toml-rs.
[25] Logging implementation for Rust [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/rust-lang-nursery/log.
[26] Magrí, Sebastián. A logging implementation for ‘log‘ which is configured
via an environment variable [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/sebasmagri/env_logger.
[27] Couprie, Geoffroy. Send syslog messages from Rust [Text]. — [S. l. : s. n.]. —
Access mode: https://github.com/Geal/rust-syslog.
[28] A runtime for writing reliable, asynchronous, and slim applications with
the Rust programming language [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/tokio-rs/tokio.
[29] Lerche, Carl. Metal IO library for Rust [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/carllerche/mio.
[30] Zero-cost futures and streams in Rust [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/rust-lang-nursery/futures-rs.
[31] Raw bindings to platform APIs for Rust [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/rust-lang/libc.
[32] Rust friendly bindings to *nix APIs [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/nix-rust/nix.
[33] Crichton, Alex. Unix signal handling for tokio [Text]. — [S. l. : s. n.]. — Access
mode: https://github.com/alexcrichton/tokio-signal.
55
[34] Cross-platform, low level networking using the Rust programming language
[Text]. — [S. l. : s. n.]. — Access mode: https://github.com/libpnet/libpnet.
[35] The Linux man pages [Text], The Linux man-pages project. — [S. l. : s. n.]. —
Access mode: https://www.kernel.org/doc/man-pages/.
[36] Murphy, Grant. rust bindings to libcap [Text]. — [S. l. : s. n.]. — Access mode:
https://github.com/gcmurphy/rust-capabilities.
[37] SELinux
Project
[Text]. — [S.
https://github.com/SELinuxProject.
l.
:
s.
n.]. — Access
mode:
[38] The Rust programming language [Text]. — [S. l. : s. n.]. — Access mode:
https://www.rust-lang.org/en-US.
1/--страниц
Пожаловаться на содержимое документа