Отчёт об инциденте AdGuard для Windows: что случилось в октябре
После выхода версии 7.22 некоторые пользователи AdGuard для Windows сталкивались с проблемой при загрузке страниц в браузерах. Неполадку быстро устранили в Chrome, но в Firefox она сохранялась дольше, поскольку проблема оказалась редкой и трудновоспроизводимой и была вызвана необычным сочетанием факторов, которые не были обнаружены во время тестирования.
На момент публикации мы выпустили AdGuard для Windows v7.22.2, в котором полностью исправили проблему. Пожалуйста, обновите приложение до последней версии, чтобы всё работало как надо. Если вы пользуетесь AdGuard на других платформах, делать ничего не нужно, всё должно работать как обычно.
Мы приносим искренние извинения за неудобства и надеемся, что этот инцидент не повлияет на ваше общее впечатление от AdGuard. Это был единичный случай и эта ошибка больше не повторится. Помимо устранения технической проблемы, мы также пересмотрели наши внутренние процессы и уже внедрили ряд улучшений, чтобы предотвратить подобные ситуации в будущем.
Ниже приведена хронология событий, которые привели к инциденту, подробная техническая информация и меры, которые мы предпримем, чтобы избежать повторения таких ошибок.
Хроноология событий
2 октября
Мы выпустили AdGuard для Windows v7.22.
4 октября
Пользователь открыл задачу на Github, в которой сообщил о появившейся после обновления до версии 7.22 проблеме с медленной загрузкой страниц, которая иногда приводила к ошибке Timed Out.
6 октября
QA-команда (QA — quality assurance, то же, что и команда тестировщиков) начала работу над задачей, но не смогла воспроизвести проблему. В тот же день тестировщики сообщили о проблеме разработчикам, начались внутренние обсуждения.
16 октября
К обсуждению на Github присоединились новые пользователи, появилось больше комментариев с отчётами об ошибках. К сожалению, на этой стадии QA-команда не последовала инструкции по оценке и эскалации проблем подобного уровня. Мы столкнулись с серьёзными трудностями при попытке воспроизвести ошибку, отчасти из-за наличия некоторых несоответсвий в полученных нами отчётах пользователей. Это привело команду к выводу, что проблема появилась в одной из предыдущих версий и, следовательно, не требовала немедленного исправления. В результате мы отложили исправление до следующей запланированной версии, и хотя задаче был присвоен максимальный приоритет P1: Critical, задача не была должным образом эскалирована, и было потеряно драгоценное время.
30 октября
Через 24 дня после её появления на задачу вновь обратили внимание, осознав масштаб её воздействия на пользователей. Когда мы наконец успешно воспроизвели проблему, мы смогли оценить общий ущерб более точно. Тут же началась работа над первым хотфиксом (v7.22.1). Но львиная доля времени и сил ушла на то, чтобы научиться стабильно воспроизводить проблему, и на обнаружение её причин. В том числе было проработано несколько гипотез, в итоге оказавшихся ложным следом.
В ходе расследования мы обнаружили баг браузера Firefox при работе с QUIC-соединением, который в свою очередь снижал скорость загрузки страниц и дополнительно усложнял ситуацию как для пользователей, так и для команды разработчиков, пытавшейся воспроизвести проблему. Мы также обнаркжили другую проблему, связанную с отсутствием фильтрации QUIC-соединений при работе AdGuard в режиме совместимости с AdGuard VPN при отключённом Wintun.
1 ноября
Мы установили (как оказалось, частично) причину проблемы и научились стабильно её воспроизводить. Мы исправили проблему в DnsLibs, нашем движке DNS-фильтрации, и выпустили первый nightly-сборку для тестирования.
5 ноября
Мы обнаружили главную причину — ошибку в компоненте CoreLibs, определяющем зацикливание маршрутизации (routing loop detection) (CoreLibs — движок фильтрации AdGuard). Мы быстро исправили его и выпустили второй nightly-билд.
6 ноября
Тестирование nightly-сборок показало, что первая часть доработок исправляла большую часть проблем с TCP-соединениями, но всё ещё оставалась часть проблем с UDP-соединенями. Чтобы минимизировать воздействие на пользователей, мы решили выложить исправления в два этапа. Сначала мы выпустили третью nightly-сборку с исправлением оставшихся проблем, включая финальные исправления движков и обновления инструкций драйверов. Затем подготовили, тщательно протестировали и в тот же день выпустили патч v7.22.1.
Технические детали
Ошибка в AdGuard для Windows v7.22 вызывала случайные, непредсказуемые зависания при загрузке определённых страниц в Firefox. Пользователи Chrome не испытывали таких проблем. Проблема возникла после того, как в CoreLibs v1.19 появилась защита от зацикливания маршрутизации — механизм для предотвращения обратной маршрутизации трафика.
У самой ошибки было две основных причины.
Во-первых, программная ошибка исключала порт из проверки маршрутных петель — а значит, некоторые нормальные фильруемые соединения, такие как запросы браузера, могли оказаться заблокированными. Это вызывалось некоторыми сервисными запросами AdGuard, такими как запросы OCSP. После таких запросов следующее соединение браузера прерывалось.
Во-вторых, проверка применялась в неправильном месте к уже установленным соединениям, что приводило к ненужным блокировкам соединений.
Зачем нам вообще защита от петель в маршрутизации?
Зацикливание маршрутизации (routing loop) происходит, когда трафик возвращается к исходному приложению. Это приводит к медленным соединениям с повышенным потреблением CPU. При обычной работе AdGuard такие ситуации не происходят, но из-за взаимодействия с другим ПО это всё-таки может случиться. Чтобы предотвратить их, AdGuard отслеживает исходящие соединения по исходному адресу и обрывает те, что возвращаются в него же.
Почему Chrome был практически не затронут?
При сбросе соединения Chrome автоматически пробует выполнить запрос той же сети ещё раз. Так как рвалось только одно следующее за служебным соединение, пользователи Chrome гораздо реже замечали проблему.
Особенностью Firefox является то, что он не делает дополнительных попыток установить соединение при возникновении какой-либо сетевой ошибки. Это сделало проблему более очевидной, и пользователи Firefox напрямую страдали об неё.
А как насчёт AdGuard для Linux и Android?
Проблема не была обнаружена в CLI v1.19, хотя новейшие функции всегда сначала внедряются в AdGuard для Linux, чтобы серьёзные проблемы можно было найти и исправить до интеграции в приложения с пользовательским интерфейсом. Проблема возникала только в автоматическом режиме Linux и в основном в Firefox. Таких пользователей очень мало, поэтому никаких сообщений от них не поступило.
Проблема также не возникала в AdGuard для Android v4.12, хоть он и использует ту же CoreLibs 1.19. Адреса входящих и исходящих соединений различались, и это не позволяло создать те же условия, которые вызывали ошибку в других местах.
Диагностика
Диагностировать проблему было особенно сложно. AdGuard открывает относительно мало сервисных соединений, и при повторных попытках воспроизвести проблему часто использовались кешированные OCSP-запросы, что не позволяло обнаружить ошибку. Кроме того, проблема затрагивала только одно входящее соединение за раз. Пользователи Chrome в основном не испытывали никаких проблем, поскольку браузер автоматически повторяет неудачные подключения, а пользователи Firefox пострадали напрямую, поскольку браузер не пытается повторно установить соединение при возникновении сетевой ошибки.
Попутно обнаруженный баг Firefox
Пока мы искали этот трудноуловимый баг, мы столкнулись с тем, что в половине отчётов были и другие симптомы, и описанные проблемы начали повторяться и на более старых версиях AdGuard. Так мы обнаружили отдельный баг обработки HTTP/3-соединений в Firefox для Windows.
Если коротко, Firefox сразу пытается установить HTTP/3-соединение, когда получает информацию, что у сайта есть его поддержка, даже если соединение ещё не доступно. AdGuard на данный момент по умолчанию не фильтрует HTTP/3, поэтому HTTP/3 блокируется для приложений с включённой HTTPS-фильтрацией.
Обычно браузеры включают в себя алгоритм вида «Happy Eyeballs», который позволяет вам выбрать протокол, обеспечивающий лучшую работу, но Firefox для Windows сразу пытается установить HTTP/3-соединение, когда получает информацию, что у сайта есть его поддержка (например из DNS-записи типа HTTPS). После этого он назначает запросы на HTTP/3-соединение, которое ещё не установлено, несмотря на наличие живого HTTP/2-соединения.
Если HTTP/3 недоступен, то это приводит паузам в загрузке сайта по 20–30 секунд, после истечения которых запросы «переназначаются» на имеющееся живое HTTP/2-соединение.
По поводу данного поведения был заведён баг, а в качестве превентивной меры в AdGuard добавлена модификация DNS-записи типа HTTPS для исключения параметра h3 ALPN, если выключена HTTP/3-фильтрация. Это позволяет скрыть факт наличия HTTP/3 от браузера в случаях, когда его всё равно заблокирует AdGuard.
Исправление
На первом этапе мы исправили логику сопоставления соединений, чтобы правильно учитывать порт. Это решило большую часть проблемы, хотя некоторые ложные срабатывания сохранялись из-за повторного использования системой Windows портов из недавно закрытых соединений. Nightly-сборка, выпущенная вечером 5 ноября, помогла большинству пользователей.
Однако следы проблемы всё ещё были видны в логах AdGuard. Оказалось, что она была решена лишь отчасти — исправления алгоритма сопоставления соединений было недостаточно, поскольку Windows может повторно использовать порт исходящего соединения в течение секунды после освобождения сокета. Это привело к другому типу ложных срабатываний, когда несвязанные соединения с одинаковым адресом и портом ошибочно идентифицировались как зацикливания.
Явных новых инцидентов не было (все сообщали, что всё работает нормально), но потенциально проблема всё ещё могла затрагивать многих пользователей. Так мы перешли ко второму этапу: он был направлена на устранение этих случаев, в результате чего был выпущен хотфикс v7.22.1, который полностью решил проблему.
Как не допустить такого в будущем
Эта проблема не была обнаружена ранее из-за сложности её воспроизведения в нормальных условиях тестирования и недостаточного внимания к отзывам пользователей.
Сейчас мы обновляем наши процессы тестирования и разработки, делая особый акцент на их более строгом контроле, улучшении автоматизации и более тщательном тестировании и коммуникации внутри команд. Таким образом, мы стремимся предотвратить подобные инциденты в будущем и гарантировать надёжность AdGuard для всех пользователей.
Что изменится в работе QA-команды?
QA-команда будет более пристально следить за количеством комментариев и реакций к ним в разделе задач на GitHub. Чтобы исключить человеческий фактор, мониторинг будет полагаться не только на ручную проверку — мы добавим автоматизацию для отслеживания активности и количества «плюсов» в публичных задачах. При успехе можно будет масштабировать это решение на все QA-команды AdGuard.
Мы проведём дополнительный брифинг по triage-инструкции (triage — расстановка приоритетов, приоритизация при устранении багов и решении задач), введём обязательную практику внутренней оценки проблемы в Jira. Структурированные внутренние инструкции обеспечат последовательность и прозрачность решений по установлению приоритетов.
Также команда составит список диагностических вопросов для пользователей, по ответам на которые проще будет выявить и проанализировать проблемы с фильтрацией.
Наконец, мы внедрим несколько автотестов, чтобы не допустить такого в будущем. Мы работаем над скриптом бенчмарк-теста для оценки скорости фильтрации страниц в разных браузерах. Определим эталон и будем «измерять» все последующие релизы с его помощью.
На данный момент автотесты используются только в Chrome, но мы планируем добавить их и в Firefox. Мы также напишем тесты на замер скорости загрузки определённого перечня «проблемных» страниц в этих браузерах. В качестве начального перечня будет взят список уже известных нам проблемных страниц, но он будет пополняться: начнём с сайтов, доступ к которым признан проблемным в рамках решения данной задачи (например, discord.com).
Что изменится в командах разработчиков
Команды разработчиков будут уделять больше внимания двум вещам:
- Добавлению тестов — в том числе интеграционных — для новых функций, чтобы снизить вероятность возникновения ошибок в будущих релизах.
- Подробной технической документации по новым функциям и информированию всех вовлечённых команд о необходимости их тестирования на предмет потенциальных ошибок и тупиковых ситуаций.
Перед интеграцией новых версий CoreLibs в продукты все команды будут дожидаться явного одобрения от команды CoreLibs. На текущий момент процесс этой интеграции проходит в некоторой степени «изолированно», из-за чего риск упустить проблему возрастает.
Выводы
Мы хотели бы ещё раз извиниться перед всеми пользователями, затронутыми этим инцидентом, и искренне поблагодарить всех, кто оставил обратную связь и помог нам справиться с этой сложной ситуацией. В будущем мы будем быстрее и прозрачнее информировать наших пользователей о любых критических проблемах, которые могут иметь значительные последствия.