
В подразделении, где я работаю, есть традиция - новичку при онбординге вручается ссылка на Wiki с легендарными багами, приведшими к заметным последствиям. Недавно мне пришла в голову идея сделать такую же страницу, но уже со ссылками на Хабр, потому что на русском о багах пишут с бОльшим огоньком. Но, увы, оказалось, что каскадному падению серверов AT&T 15 января 1990 года внимание как-то не уделено. А ведь история получилась, прямо-таки эпическая.
Итак, 15 января 1990 года из-за одной строчки кода телефонная сеть AT&T получила 9 часов даунтайма, 70 миллионов несовершенных звонков, а общий убыток насчитали в $60 миллионов еще не инфляционных долларов. И нет, там не было неудачного релиза, развернутого сразу и везде. Все было гораздо интереснее.
Диспозиция
В конце 1980-х AT&T была доминирующим игроком дальней связи в США. Практически каждый междугородний звонок в стране проходил через ее сеть из 114 коммутаторов 4ESS (Electronic Switching System 4-го поколения), маршрутизирующих вызовы между городами, штатами и посылающих их во весь остальной мир.
AT&T была фанатично помешана на надёжности: каждый коммутатор 4ESS имел резервные источники питания, резервные тракты передачи и вообще резервировал все что можно. Сеть и вовсе строилась по принципу «параноидальной демократии» - поскольку централизованная оркестрация еще не использовалась, коммутаторы сами постоянно мониторили друг друга, отслеживая, кто здоров, а кто выбыл. "Общение" велось по протоколу CCS7 (Common Channel Signaling System 7, он же SS7), где служебные сообщения передавались отдельно от голосового трафика. Именно по CCS7 коммутаторы сообщали друг другу: «я принимаю вызовы», «я перегружен», «маршрутизируй через меня».

Сеть работала отлично - если один узел падал, остальные перенаправляли трафик мимо него и так до тех пор, пока узел снова не возвращался в строй. И ни один отдельный коммутатор не мог уронить всю сеть. Ну, по крайней мере так думалось до 15 января 1990 года.
А давайте чего-нибудь оптимизируем
Как стандартно работал коммутатор 4ESS: ОС маршрутизатора постоянно проверяла собственную работоспособность и целостность и, в случае нахождения каких-то сбоев, отправляла себя в перезагрузку. Перед перезагрузкой система слала через CCS7 всем окружающим маршрутизаторам служебное сообщение - мол, я подремлю. Окружение помечало выбывшего в таблицах как временно недоступного, плюс записывало некоторое количество логов.

Затем сбойный маршрутизатор перезагружался за 4-6 секунд, после чего слал сообщение "я вернулся". Окружение снова обновляло свои таблицы, плюс, мы же помним по "параноидальную демократию" - опять же записывало некоторую служебную информацию по времени перезагрузки, отмечало время сброса аптайма и т.п. Я специально это описываю, чтобы было понятно, что в таблицах маршрутизации окружения менялась не одна строка.
После того, как сбойный маршрутизатор известил всех о своем "камбеке", он начинал слать так называемый IAM (Initial Address Message), т.е. сообщения о маршрутизации и вызовах. Т.е. начинал работать в штатном режиме. Между служебным "я вернулся" и IAM была буквально секунда-две, но инженеры AT&T решили ее оптимизировать.
Логика была простая - зачем дожидаться служебного сообщения "я вернулся", если первый IAM и есть сообщение, что маршрутизатор уже в сети? Для обработки этого вызова написали кусок кода, который запускал переинициацию таблиц маршрутизации при первом IAM после перезагрузки, и условие, для обработки, если следом прилетал второй IAM.
Я не смог найти оригинальный листинг, но вот тут приведен псевдокод, описывающий логику работы:
1 while (ring receive buffer not empty
and side buffer not empty):
2 Initialize pointer to first message in side buffer
or ring receive buffer
3 get copy of buffer
4 switch (message):
5 case (incoming_message):
6 if (sending switch is out of service):
7 if (ring write buffer is empty):
8 send "in service" to status map
9 else:
10 break
END IF
11 process incoming message, set up pointers to
optional parameters
12 break
END SWITCH
13 do optional parameter work
Проблема заключалась в том, что в С, на котором был написан код, break на 10-й строке прерывал не IF, а более высокоуровневого Switch. В результате, второй входящий IAM приводил не к обработке сообщения согласно строке 11, а улетал в строку в 13, начиная перезаписывать доп. параметры. Тут подсистема для коррекции ошибок обнаруживала несколько перезаписей и отсутствие целостности таблиц, после чего перезагружала коммутатор, пока он ещё мог перезагрузиться.
Предположу, что программист, писавший код, привык работать с Ada, где поведение break именно такое, как предполагает листинг. Но в данном случае язык был именно такой, и ошибка осталась.
Позвони мне, позвони
Считается, что в AT&T очень серьезно относятся к тестированию. Однако баг проглядели и раскатали его в продакшн на все 114 маршрутизаторов. Причем, сделали это еще в 1989 году и он благополучно пережил рождественскую нагрузку. Но там звонки относительно равномерно грузили сеть и баг не проявляется.
Но 15 января из Нью-Йорка стали как-то особенно настойчиво звонить. Источники, описывающие баг, не поднимают вопрос, откуда взялась эта активность. Однако, мне стало любопытно, я погуглил мировые события и нашел, что именно 15 января 1990 г. бывшие жители ГДР взяли штурмом штаб-квартиру Штази. Чтобы понять, почему это важно, нужна небольшая историческая справка.

После падения берлинской стены одним из главных требований активистов стала ликвидация Штази (местного аналога КГБ). Однако штаб-квартира в Восточном Берлине продолжила работать под новой вывеской "Управление национальной безопасности", попутно отдав приказ об уничтожении документов. Под нож шли списки осведомителей, папки, фотографии и документы. И именно 15-го числа множество активистов взяло штурмом штаб-квартиру вместе с архивом. Как пишут в интернетах "общая длина стеллажей, на которых хранятся неуничтоженные в 1989-90 гг. документы Штази, достигают 112 км". А зная любовь США ко всяким архивам, не удивлюсь, что кто-то проявил настойчивость. Но, повторюсь, это мои домыслы.
В любом случае, из Нью-Йорка, несмотря на праздничный день имени Лютера Кинга, кто-то очень настойчиво звонил. Маршрутизатор AT&T в 14:25 накопил критическое количество в части транковского оборудования и принял решение о штатной 4-х секундной перезагрузке. Поскольку активность Нью-Йорка не уменьшалась, звонки копились в очереди, а сервисные сообщения IAM копились в буфере (и, судя по всему, активность была такая, какой не случалось даже в рождество). После перезагрузки, в соответствии с новой логикой, маршрутизатор Нью-Йорка не стал слать сообщение "я вернулся", а сразу пульнул первый IAM, после чего, в долю секунды послал второй и последующие IAM. Важно: т.е. до этого момента на центральных высоконагруженных узлах не возникала ситуация, когда два сервисных сообщения шли друг за другом в течении нескольких миллисекунд. Даже в праздники. А тут пошли.
Окружающие маршрутизаторы начали обновлять таблицы, одновременно получили второй IAM, вывалились из switch, начали писать данные непонятно куда, после чего словили ошибку "audit subsystems" и ушли в перезагрузку. А из Штатов продолжали звонить.

Поскольку сразу несколько маршрутизаторов ушли в ребут, их нагрузка распределилась на соседние. Перезагрузившиеся маршрутизаторы начинали разбор буфера и клали следующие маршрутизаторы по цепочке, включая те, что перезагрузились чуть раньше. И чем сильнее сеть пыталась перераспределить нагрузку, тем больше сообщений IAM копилось и тем быстрее шел каскад перезагрузок. Как только количество перезагружающихся коммутаторов стало преобладать над работающими, сеть перешла в режим коллапса.
Где все?
Суммарно даунтайм продлился около 9 часов. Попытки инженеров решить проблему через перезагрузки ни к чему не приводили - им оставалось наблюдать, как маршрутизаторы волнами красятся в красный по всей стране, уходя в перезагрузку.

Как потом посчитали, всего в этот день не случилось 50-70 миллионов звонков, а 60.000 абонентов и вовсе полностью остались без связи. Пострадали системы бронирования авиакомпаний, гостиниц, прокатных компаний (тогда многое было завязано именно на телефонном подтверждении). Более того, почему-то задержали 500 авиарейсов. Всего убытков насчитали на 60 миллионов долларов (с учетом покупательной способности, сейчас это ближе к 120-150 миллионам).
Проблему удалось частично решить замедлением сети CCS7, в результате чего маршрутизаторы стали успевать обновить свои таблицы и не падать. Ну а к ночи активность звонков спала сама собой и системщикам за ночь удалось откатить маршрутизаторы на старую версию ПО.
Выводы и рекомендации
Если бы эта статья была написана нейросетью, то тут был бы обязательный раздел с выученными уроками и рекомендациями. Но мы не будем. За 36 лет, прошедших с момента инцидента, индустрия выпустила достаточно методик и рекомендаций по предотвращению такого рода инцидентов.

Ценность конкретно этого кейса заключается в простом понимании - даже одна строчка кода может уронить даже кажущегося надежным монстра. Тестирование также жизненно необходимо, но даже самое жесткое тестирование способно пропустить ошибки, и тут важно иметь план по быстрому восстановлению.
Размещайте облачную инфраструктуру и масштабируйте сервисы с надежным облачным провайдером Beget.
Эксклюзивно для читателей Хабра мы даем бонус 10% при первом пополнении.

