Назад | Перейти на главную страницу

Настройка сетевого интерфейса для TCP через TCP в Linux

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

Поскольку исходное приложение работает в системе с «голым железом», не оставалось ничего другого, кроме как реализовать все дополнительные службы как часть приложения.

В рамках своих усилий по переносу я решил использовать драйвер TUN / TAP, предоставляемый ядром Linux, и направить все инкапсулированные кадры TCP и UDP на интерфейс TUN. Однако это приводит к запуску TCP через TCP. принося все связанные с этим проблемы. Исходная реализация не имеет этой проблемы, потому что туннель не инкапсулирует полный стек TCIP / IP.

Итак, мой вопрос: можно ли настроить интерфейс TUN так, чтобы стек TCP / IP, через который он проходит, не выполнял никаких очередей и повторных передач? Или я придерживаюсь той индивидуальной реализации, которую унаследовал? Мне известно только о проблеме повторной передачи TCP через TCP. Есть ли другие проблемы, о которых мне нужно знать при использовании такого решения?

Это непростая ситуация. Я надеюсь, что со временем вы сможете заменить все приложение чем-то более продуманным.

На уровне TUN / TAP мало что можно сделать, потому что это слишком низкий уровень стека, чтобы понять, что такое повторные передачи.

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

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

На принимающей стороне

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

Чтобы пакеты доставлялись в интерфейс TUN / TAP, вы используете необработанный сокет, который будет получать сегменты TCP, как показано на проводе. Этот процесс может использовать фильтры в ядре, чтобы видеть только те пакеты, которые ему нужны, и игнорировать любые лишние пакеты, если ядро ​​не может выполнять фильтрацию достаточно точно. Ваш процесс должен сделать достаточно для повторной сборки TCP, чтобы извлечь внутренние пакеты, которые он затем может доставить в интерфейс TUN / TAP.

Здесь важно то, что при потере внешнего пакета будут потеряны или задержаны только внутренние пакеты, затронутые им. Ваш процесс может продолжать сборку пакетов после потерянного, чтобы извлечь и доставить внутренние пакеты в интерфейс TUN / TAP. Внутренний стек TCP может повторно передать несколько пакетов, но не так много, как при остановке внешнего TCP-соединения.

Следует отметить несколько предостережений, которые могут быть очевидными, а могут и не быть:

  • Если окно приема или окно перегрузки заполняются, TCP на отправляющей стороне остановится. Вы не можете предотвратить это, но вы можете снизить риск, убедившись, что внешнее TCP-соединение поддерживает выборочные подтверждения (SACK).
  • В зависимости от специфики туннельного протокола может быть сложно или даже невозможно точно определить границы пакета после потери пакета. Если это так для протокола, который вам нужно реализовать, возможно, вам не повезло. Я бы предложил изменить протокол, но я понимаю, что это не вариант для вас.

На отправляющей стороне

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

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

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

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

Игнорирование проблемы

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

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