О большом MTU в Yggdrasil

13 июля 2018 года, автор: Neil Alexander

Возможно, вы заметили, что в Yggdrasil значение параметра MTU отличается от обычных значений, которые вы могли видеть в других местах. В большинстве случаев, в сетях Ethernet это значение приблизительно равно 1500 байт. В сетях, где используется VPN, значение может быть еще меньше, это связано с накладными расходами на работу протоколов, обеспечивающих соединение VPN. Так что, возможно, вы были удивлены, увидев в Yggdrasil интерфейсный MTU со значением до 65535 байт.

На самом деле, это не ошибка, так задумано.

MTU - это параметр конфигурации Yggdrasil, который задает максимальный размер полезного блока данных одного пакета, передаваемого без фрагментации. Помимо этого, сама операционная система поддерживает настройку MTU для каждого сетевого адаптера. Фактически, этот параметр означает следующее: “пакеты, равные или меньшие этого числа байтов, безопасно отправлять по этому каналу одним пакетом”. Любые блоки большего размера операционная система должна будет разделить на более мелкие, прежде чем отправлять их дальше.

При меньшем значении MTU мы будем вынуждены гораздо чаще повторно отправлять IP-заголовки (и, возможно, другие заголовки) с каждым отдельным пакетом. Это не только лишние байты трафика, но и каждый пакет, вероятно, потребует нового набора системных вызовов для его обработки. В случае с Yggdrasil мы полагаемся на системные вызовы не только для операций c сокетом, но и для виртуальных сетевых драйверов TUN/TAP. Каждый системный вызов требует переключения контекста, что является медленной операцией. На встроенных платформах это может быть настоящим убийцей производительности. Например, на EdgeRouter X переключение контекста для адаптера TUN/TAP является самым узким местом и на это расходуется больше ресурсов, чем на криптографические алгоритмы!

Выбор TCP вместо UDP

Разумно предположить, что чем меньше переключений контекста и меньше передач заголовков, тем лучше. Использование MTU 65535 вместо 1500 позволяет передать более чем в 43 раза больше данных перед следующим набором заголовков. Это позволяет из 65535 байт сэкономить почти 1680 байт только на заголовках! Но отправка 65535-байтового пакета Yggdrasil по каналу связи с MTU 1500 потребует, чтобы пакет был фрагментирован 43 раза, верно?

Чтобы избежать этого Yggdrasil использует для пиринга TCP-соединения. Это не только позволяет нам использовать преимущества прокси SOCKS (и Tor) таким образом, который не возможен при использовании UDP, но и дает нам свойства потока на наших соединениях вместо того, чтобы вручную принудительно разделять пакеты UDP. На самом деле, мы делали это в прошлом, и это было некрасиво, и во многих случаях производительность была хуже, чем производительность TCP.

TCP настроит размер окна в соответствии с нижним звеном - в данном случае с вероятным значением MTU 1500 - и будет передавать большой пакет кусками, пока он не прибудет на удаленную сторону в виде одного блока. Поэтому Yggdrasil не заботит MTU реального линка между вами и вашими пирами. Он просто получает пакет размером до 65535 байт, который затем обрабатывает и передает адаптеру TUN/TAP.

Но... TCP поверх TCP?

Описанное означает, что мы туннелируем протокол TCP через TCP. Я слышу, как вы орете. Это же безумие, серьезно? Проблема с TCP-over-TCP заключается в том, что в случае перегрузки или потери пакетов TCP попытается повторно передать неудачные пакеты, но если управляющие пакеты TCP из внутреннего соединения будут повторно переданы, переупорядочены и т.д. инкапсулированным TCP-соединением, это приведет к существенному снижению производительности когда операционная система будет регулировать это внутреннее TCP-соединение, чтобы справиться с тем, что она считает перегрузкой.

Однако, используя большие MTU и, следовательно, большие размеры окон во внутреннем TCP-соединении, мы посылаем гораздо меньше управляющих сообщений TCP по проводу - возможно, до 43 раз меньше - поэтому существует гораздо меньше возможностей для управляющих сообщений на внутреннем соединении быть затронутыми при повторной передаче. Это помогает стабилизировать производительность внутренних TCP-соединений.

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

Согласование MTU

Большая проблема, с которой мы столкнулись, заключалась в том, что разные операционные системы имеют разные максимальные MTU. Linux, macOS и Windows все разрешают максимальный MTU 65535 байт, но FreeBSD позволяет только половину этого - 32767 байт, OpenBSD еще меньше - 16384 байт и NetBSD позволяет ничтожные 9000 байт! Не говоря уже о том, что могут быть также веские причины для корректировки MTU вручную, для этого добавлена опция IfMTU в yggdrasil.conf

Для любых двух узлов Yggdrasil нужен способ договориться о значении MTU, которое подходит для обеих сторон. С этой целью, каждый узел отправляет свой собственный MTU на удаленную сторону при создании зашифрованного сеанса. Зная как свой собственный MTU, так и MTU удаленной стороны, вы можете выбрать нижний из двух MTU, так как нижний из двух MTU гарантированно безопасен для обеих сторон. На самом деле, вы даже можете увидеть, что такое согласованный MTU сеанса, используя yggdrasilctl getSessions или отправив запрос getSessions в сокет администратора.

Использование ICMPv6 для передачи сигналов в userspace

Итак, что произойдет, если вы установите сеанс с кем-то, у кого настроено меньшее MTU, чем у вас? Согласование сеанса и обмен MTU позволят Yggdrasil выбрать более низкий MTU для сеанса, но ваш адаптер TUN/TAP на вашей собственной стороне все еще имеет больший из двух MTU. Как мы сообщим userspace о новом MTU?

IPv6 предусматривает пакет типа «Too Big», который предназначен для облегчения обнаружения пути MTU (Path MTU Discovery) в стандартных сетях IPv6. Если вы отправляете большой пакет, промежуточный узел (например, маршрутизатор) может отправить отправителю сообщение ICMPv6 «Too Big», означающее, что отправленный вами пакет превышает самый большой поддерживаемый размер пакета (фактически восходящий канал MTU). Отправляющий узел может затем фрагментировать пакет и повторно отправлять его в меньших кусках, чтобы он соответствовал этому нижнему значению MTU на пути. Yggdrasil использует это поведение, генерируя управляющие сообщения «Too Big» и отправляя их обратно адаптеру TUN/TAP, когда он обнаруживает, что вы пытались отправить пакет через сеанс Yggdrasil, который больше, чем согласованный MTU.

Это приводит к тому, что операционная система фрагментирует пакеты до размера MTU, поддерживаемого сеансом, и повторно отправляет их. Кроме того, операционная система кэширует этот недавно обнаруженный MTU на будущее, чтобы он правильно фрагментировался для этого получателя с этого момента. По сути, Yggdrasil «обманывает» операционную систему, заставляя ее думать, что в соединении действительно происходит Path MTU Discovery.

Вывод

Вы видите, что Yggdrasil выполняет ряд трюков, чтобы обеспечить гибкое MTU между узлами, и использует преимущества потоковой природы TCP, чтобы обеспечить наибольший уровень производительности и гибкости. Существует ряд подвижных частей для этого - согласование сеанса, генерация сообщений ICMPv6 и выбор TCP вместо UDP для пиринговых соединений. Я надеюсь, что это демистифицирует некоторые из наших решений и объяснит, почему мы поощряем использование более крупных MTU в соединениях.

Ссылки

Только авторизованные участники могут оставлять комментарии.
yggdrasil/mtu.txt · Последние изменения: 2020/05/29 20:48 — acetone
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki