Юникастовая маршрутизація мультикаст-трафіку

Передмова
Нещодавно мною було помічено, що при перегляді мультікастового IPTV через Wi-Fi частина трафіку втрачається. Після детального вивчення проблеми було з'ясовано, що така поведінка пояснюється природою мультикаст-трафіку, а саме – MAC-адресу одержувача пакета. Він не залежить від одержувача і формується з адреси мультикаст-групи. Відповідно, на такі пакети претендують всі клієнти, підключені до бездротової точки доступу. Внаслідок цього нам дістається лише частина пакетів і ми бачимо обривистий картинку.

Штатними засобами проблема вирішується створенням окремої точки доступу для клієнта, або створенням статичного маршруту для певних мультикаст-груп, або ж виведенням клієнта в окремий VLAN. Вся сила таких рішень проявиться, коли в мережі декілька IPTV-приставок, охочих подивитися один і той же канал, плюс необхідність їх в інтернеті додасть складність налаштування роутера. Своє рішення даної проблеми пропоную нижче.

Програми типу udpxy тут не підходять, так як вони змінюють повну структуру пакету. А нам необхідно лише встановити необхідний MAC-адресу, при цьому зберігаючи мережеву і транспортну частини, щоб клієнтське ПЗ не помітив ніяких змін.

Дане рішення, назвемо його MUT (Multicast to Unicast Translation), полягає в наступному:
  1. Дізнатися IP-адресу клієнта, який бажає підключитися до групи
  2. Повідомити про це ядру ОС
  3. Дізнатися IP-адресу MAC-адресу клієнта
  4. Створити та надіслати копію пакета на відповідний інтерфейс
Виконання кроків 1 і 2 лежить на програмі мультикастовой маршрутизації, 3 і 4 – на ядрі. І те й інше вимагає невеликих змін у своїй роботі. Вся робота буде проходити в ОС GNU/Linux.

Трохи теорії
Мережна маршрутизація IP версії 4 в Linux базується на наступний структурах:
  • sk_buff – найбільш часто використовувана структура і представляє з себе весь мережевий пакет. Вона передається з функції у функцію по шляху змінюючи свій вміст.
  • rtable + dst_entry – дві структури, що зберігають результат кешування маршруту, отриманий з таблиці маршрутизації. У залежності від адреси одержувача, адреси джерела і поля TOS пакета визначається подальша політика по відношенню до нього. Ці дві структури зберігають важливу інформацію для нас: інтерфейс, через який буде проходити відправлення, і поле шлюз — майбутній L2-сусід, якому можна відправити пакет, не змінюючи L3-заголовок. Пошук кешу для кожного кадру проводиться два рази: один раз на вході (вхідний трафік) і другий раз на виході (вихідний). Нас цікавить другий.
  • neighbour – кожен примірник цієї структури являє собою L2-сусіда для певної IP-адреси одержувача. Він містить MAC-адреса одержувача, отриманий після ARP-відповіді; чергу sk_buff, які необхідно відправити після визначення MAC-адреси; таймери і багато іншого. Для мультикаст-груп сусіди теж створюються, MAC-адресу генерується функцією. Нам же слід уникнути цього.
В Linux маршрутизація мультикаст-трафіку повністю контролюється з області користувача, а саме програмою-маршрутизатором. Ключовим елементом у мультикаст-маршрутизації є структура mfc_cache. Це зв'язаний список, який зберігає всю інформацію про кожному маршруті: адреса джерела потоку, статистику, подальший маршрут і т. д. Додавання і видалення mfc_cache-структур здійснюється користувача програмою.

Схематичне представлення mfc_cache-списку:

image
Зображення взято з книги «Linux Networking Architecture»

Розробка
За основу було взято ядро Linux 3.18. Для зберігання IP-адрес клієнтів для кожної мультикаст-групи розширюємо mfc_cache зв'язаним списком:

struct mut_dst {
struct list_head list;
__be32 ip;
struct rcu_head rcu;
};

Вводимо нову функцію ipmr_unicast_xmit. У ній буде генеруватися юникастовый rtable, але передавати при цьому будемо мультикастовый sk_buff. Таким чином ми вибираємо необхідний інтерфейс для майбутньої відправлення.

Тепер для того, щоб надалі був створений neighbour для нашого одержувача, а не для мультикаст-групи, у rtable вказуємо шлюз. За це відповідає поле rt_gateway:

struct rtable *rt;

rt = ip_route_output_ports(net, &fl4, NULL, m_dst->ip, 0, 0, 0, IPPROTO_IPIP, RT_TOS(iph->tos), 0);

if (IS_ERR(rt))
goto out_free;

rt->rt_gateway = m_dst->ip;
dev = rt->dst.dev;

Вводимо sysctl-змінну /proc/sys/net/ipv4/mut. Вона дасть можливість зміни режиму роботи ядра «на льоту».

Довідкаsysctl net.ipv4.mut=1 – Включає новий режим
sysctl net.ipv4.mut=0 – Повертає режим стандартної маршрутизації

Як і раніше можна подивитися список маршрутів, тепер ще і unicast:

root@multicast:~# cat /proc/net/ip_mr_cache
 
Group     Origin Iif Pkts  Bytes    Wrong Dsts
 
0520C3EF           2 18842 25323648     0 01000A0A
 

Детальніше з усіма змінами можна ознайомитися в репозиторії. Посилання в кінці статті.

Наочне представлення роботи (зміни в колонці з MAC-адресою):



Маршрутизатор
За основу взята програма IGMPProxy. Можна було взяти будь-яку іншу, той же mrouted. Дуже важливо, що всі IGMP-повідомлення відправляються від IP-адреси запитувача інтерфейсу, і нам ні що не заважає його використовувати. Подробиці змін описувати сенсу немає, їх також можна знайти у відповідному сховищі. Головне те, що в управлінні ядра з'являються дві нові команди, які повинна підтримувати програма:

  • MRT_MUT_ADD_DST (212) — додавання одержувача
  • MRT_MUT_DEL_DST (213) — видалення одержувача
Разом з ними передається структура виду:

struct <name> {
struct in_addr group; // Адреса групи
struct in_addr origin; // Адреса джерела
struct in_addr destination; // Адреса клієнта
}

Попередження
Варто зауважити, що такий підхід не дає можливості відключати від клієнтів груп за відсутність від них Membership Report-запитів, так як, виходячи з протоколу IGMP, клієнт, який отримав від іншого клієнта такий запит з тією ж групою, сам не відправляє аналогічний. Тому відключення можливе тільки після отримання явного Leave Group-пакета.

Використання
Для включення нової можливості необхідно скомпілювати ядро з опцією CONFIG_IP_MUT=y
Для повноцінної роботи зміненої IGMPProxy також необхідно включити CONFIG_SYSCTL_SYSCALL=y

Посилання

Змінене ядро
Змінений IGMPProxy

Використана література

Rami Rosen «Linux Kernel Networking. Implementation and Theory»
Christian Benvenuti «Understanding Linux Network Internals»
Klaus Wehrle and Frank Pahlke «Linux Networking Architecture»

Якщо у кого-небудь є інший спосіб вирішення проблеми, прошу поділитися в коментарях.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.