Самолёт поднялся на высоту трёх километров. Я вытащил свой ноутбук, надеясь воспользоваться Интернетом, а может, немного поработать, если станет совсем скучно.
Подключившись к Wi-Fi самолёта, я открыл браузер. Страница сетевого логина потребовала ввести данные кредитной карты. Я поискал карту, которая обнаружилась внутри паспорта. В процессе поисков я заметил, что страница логина предлагает бесплатно войти в мой аккаунт программы авиамиль, хотя я пока ни за что ещё не заплатил. Я решил, что это дыра в файрволле. Мне предстоял долгий путь из Лондона в Сан-Франциско, поэтому я решил её исследовать.
Я вошёл в свой аккаунт JetStreamers Diamond Altitude, перешёл на страницу своего профиля и увидел кнопку редактирования. Она выглядела обычно: отбрасываемая тень, скруглённые углы, ничего особенного. С её помощью можно было поменять имя, адрес и так далее.
Но внезапно я понял, что это необычная кнопка. Она мошенническим образом позволит мне получить полный доступ к Интернету через мой аккаунт программы авиамиль. Это будет медленно и невероятно тупо, но сработает.
Многие коллеги просили меня выполнить ревью их пул-реквестов, потому что я оставлял комментарии типа «опоздало на две недели» или «мешает развёртыванию критического обновления». Но мои идеи тоже важны, поэтому я надел наушники и включил музыку для концентрации. Я забыл зарядить наушники, поэтому Limp Bizkit начал проигрываться через динамики ноутбука. К счастью, никто из пассажиров не был против, так что мы кайфовали вместе.
Прежде чем получить доступ ко всему Интернету через аккаунт программы авиамиль, мне нужно было написать несколько прототипов. Сначала я думал, что напишу их на Go, но потом понял, что если напишу их на Python, то смогу назвать получившийся инструмент PySkyWiFi
. Разумеется, я выбрал второй вариант.
Прототип 1: мессенджер
Основная идея заключалась в следующем: допустим, я вошёл в аккаунт программы авиамиль и изменил своё имя. Если вы тоже войдёте в мой аккаунт, то сможете прочитать с земли моё новое имя. Можно снова его обновить, и я смогу прочитать новое значение. Если продолжить так делать, то поле имени аккаунта можно использовать как туннель через файрволл самолётного Wi-Fi в реальный мир.
Этот туннель может поддерживать простой протокол обмена мгновенными сообщениями. Я могу изменить имя на «Привет, как дела
». Собеседник сможет прочитать моё сообщение и отправить ответ, снова изменив имя на «Хорошо, а у тебя
». Я прочитаю это, и мы можем продолжить такое общение. Выглядит не очень функционально, но это может стать первым шагом на пути к полному Интернет-доступу.
Я заплатил за Интернет на своём старом ноутбуке. Миграция данных с этого компьютера ещё не была завершена, так что мне пришлось взять его с собой. Я написал жене и попросил помочь с моими экспериментами, на что она тепло ответила «ты о чём вообще, я занята
».
Так что мне пришлось взять новый ноутбук, в котором всё ещё не было доступа к Интернету. Я создал тестовый аккаунт программы авиамиль и вошёл в него на обоих компьютерах. Выяснилось, что действительно можно чатиться с самим собой, изменяя поле имени в UI.
Но это неудобный UX, поэтому я написал инструмент командной строки, чтобы автоматизировать его. Инструмент запрашивает у пользователя сообщение, а затем выполняет логин в аккаунт программы авиамиль через веб-сайт, используя мои учётные данные. Далее он вставляет в поле имени тестового аккаунта сообщение пользователя. Раз в несколько секунд инструмент опрашивает поле имени, чтобы проверить, поменялось ли снова имя аккаунта, и это говорит нам о том, что собеседник отправил сообщение. Как только инструмент обнаруживает новое значение, он выводит его, запрашивает у пользователя новый ответ и так далее.
При помощи этого инструмента я могу чатиться через мой терминал с кем-нибудь на земле. Мне не пришлось бы платить за Wi-Fi, и ни одной из сторон не было бы важно, что сообщения отправляются через мой аккаунт SkyVenture Premium Gold Rewards.
Всё ещё нужно найти кого-нибудь, кто захочет со мной поболтать, но начало хорошее!
Примечание: в дальнейшем я не хочу отправлять автоматизированные данные через аккаунт программы авиамиль, ведь это может привести к проблемам. Никакие мои действия не нанесут урона, но некоторым компаниям очень не нравится подобное, поэтому я убедился, что PySkyWiFi работает и с моим аккаунтом программы авиамиль, быстро изменив своё имя примерно с десяток раз. Все операции завершились успешно, и это означало, что мой аккаунт, вероятно, не ограничивает по частоте скорость отправляемых запросов.
Затем я написал остальную часть когда, отправляя данные через дружественные сервисы наподобие GitHub Gist и локальные файлы на компьютере, используя те же принципы, которые бы применял при их отправке через аккаунт программы авиамиль. Если PySkyWiFi работал через GitHub, то заработал бы и через аккаунт Star Power UltimateBlastOff. У такого способа было дополнительное преимущество: итерации стали гораздо быстрее и проще.
Я буду продолжать говорить об отправке данных через аккаунт программы авиамиль, потому что это и есть наша цель.
Прототип 2: новости, котировки акций и счёт футбольных матчей
Туннель, созданный через аккаунт, будет полезен не только для мгновенного обмена сообщениями. Для следующего прототипа я написал программу, которая может работать на компьютере у меня дома или в облаке. Она должна автоматически отправлять информацию из реального мира ко мне в самолёте через аккаунт. Я могу развернуть её перед следующим полётом и настроить её так, чтобы она отправляла мне свежие биржевые котировки или счёт футбольного матча, пока я лечу.
Для этого я написал демон, запускаемый на находящемся на земле и подключенном к Интернету компьютере. Демон постоянно опрашивает поле имени в аккаунте в поисках структурированных сообщений, которые я отправляю с самолёта (например, STOCKPRICE: APPL
или SCORE: MANUNITED
). Когда демон видит новый запрос, он парсит его, извлекает при помощи соответствующего API запрошенную информацию и отправляет её обратно мне через аккаунт. Всё сработало идеально.
Теперь можно использовать первый прототип для отправки мгновенных сообщений через аккаунт, а при помощи второго прототипа следить за данными рынков и футбольными матчами.
Настало время протиснуть весь Интернет через аккаунт программы авиамиль.
Самое главное: PySkyWiFi
В оставшееся время полёта я писал PySkyWiFi. PySkyWiFi — это сильно упрощённая версия протокола TCP/IP, «протискивающая» целые HTTP-запросы через аккаунт наружу к компьютеру, подключенному к Интернету на земле. Демон, запущенный на этом наземном компьютере, выполняет HTTP-запросы, а затем протискивает готовые HTTP-ответы через аккаунт ко мне на самолёт.
Это значило, что в следующем полёте у меня будет полный доступ к Интернету через аккаунт программы авиамиль. Если состояние сети на самолёте будет хорошим, я смогу добиться скоростей до сотен байтов в секунду.
Предупреждение: очевидно, ничего этого не стоит делать в реальной жизни
Ниже я объясню, как это работает (исходный код).
Как работает PySkyWiFi
PySkyWiFi состоит из двух компонентов:
-
Небесный прокси — прокси, запущенный на ноутбуке в самолёте
-
Наземный демон — демон, запущенный на наземном компьютере с подключением к Интернету или в облаке.
Вот упрощённая диаграмма:
Подготовка начинается ещё до того, как вы выйдете из дома. Сначала нужно запустить наземный демон. Затем вы вызываете такси до аэропорта, садитесь на самолёт и подключаетесь к сети Wi-Fi. Запускаете на ноутбуке небесный прокси. Ретранслятор PySkyWiFi готов к использованию.
Далее нужно использовать инструмент наподобие curl
, чтобы выполнить HTTP-запрос к небесному прокси, запущенному на ноутбуке. Вы направляете свой запрос к прокси (например, localhost:1234/
) и помещаете в специальный HTTP-заголовок HTTPX-PySkyWiFi
URL, который нужно запросить. Например:
curl localhost:1234 -H "X-PySkyWiFi: example.com"`
Заголовок X-PySkyWiFi
будет урезан наземным демоном и использован для маршрутизации запроса к целевому веб-сайту. Всё остальное в запросе (включая тело и другие заголовки) будет перенаправлено без изменений.
После выполнения запроса необходимо будет подождать несколько минут. Если случится чудо и ничего не сломается, то рано или поздно вы получите HTTP-ответ, будто отправили запрос через обычный Интернет как обычный человек. Единственное отличие в том, что это не будет стоить вам ни копейки. А потом вы наверняка заплатите за Wi-Fi, потому что уже удовлетворили своё любопытство, а срок, отмеренный вам на этой земле, очень короток.
Пошаговый разбор
Вот, как всё устроено внутри:
-
Небесный прокси получает HTTP-запрос из вызова
curl
. Он разбивает запрос на фрагменты, потому что весь запрос слишком велик для передачи через аккаунт программы авиамиль за раз -
Небесный прокси записывает каждый фрагмент по очереди в поле имени аккаунта.
-
Наземный демон опрашивает аккаунт. Когда он обнаруживает, что поле имени изменилось и в нём новый фрагмент, то он считывает этот фрагмент и передаёт отправителю подтверждение, чтобы тот знал, что можно передавать следующий фрагмент. Получатель объединяет фрагменты и воссоздаёт исходный HTTP-запрос
-
После получения и воссоздания наземным демоном полного HTTP-запроса он отправляет запрос по Интернету.
-
Наземный демон получает HTTP-ответ.
-
Наземный демон отправляет HTTP-ответ небесному прокси, выполняя описанный выше процесс в обратном порядке. На этот раз наземный демон разделяет на фрагменты HTTP-ответ и по очереди отправляет эти фрагменты в поле имени аккаунта (на самом деле, чтобы упростить протокол, он записывает эти фрагменты ответа в поле имени второго аккаунта)
-
Небесный прокси опрашивает второй аккаунт. Он считывает каждый фрагмент и склеивает их обратно, чтобы воссоздать HTTP-ответ
-
Небесный прокси возвращает HTTP-ответ исходному вызову
curl
. При этомcurl
работает с абсолютно обычным HTTP-ответом, только немного медленным. Он и понятия не имеет о всей той ерунде, что сейчас произошла
Небесный прокси и наземный демон достаточно просты: они отправляют HTTP-запросы и парсят HTTP-ответы. Магия заключается в том, как они протискивают эти запросы и ответы через аккаунт программы авиамиль. Давайте рассмотрим это подробнее.
Протискивание HTTP-запросов через аккаунт
Логика коммуникаций PySkyWiFi разделена на два слоя: транспортный и сетевой. Задача транспортного слоя — решить, какие данные должны друг другу передавать клиенты. Он определяет, как отправители должны разделять длинные сообщения на удобные фрагменты и как отправители и получатели должны сообщать информацию типа «я готов получить ещё один фрагмент». Если сильно не приглядываться, транспортный слой PySkyWiFi чем-то похож на протокол TCP, лежащий в основе большей части Интернета.
Задача сетевого слоя — отправлять данные между клиентами после того, как транспортный протокол решит, какими должны быть эти данные. Он смутно напоминает протокол IP, если ещё меньше приглядываться и ещё меньше понимать в нём.
Такое разделение обязанностей между слоями полезно, потому что транспортному слою не нужно беспокоиться о том, как отправляет данные сетевой слой, а сетевому слою не нужно заботиться о том, что значат данные и откуда они берутся. Транспортный слой просто передаёт сетевому слою какие-то данные, а сетевой слой передаёт их так, как ему хочется.
Такое разделение упрощает добавление поддержки других платформ работы с авиамилями, потому что нам достаточно реализовать новый сетевой слой, считывающий новый тип аккаунта программы авиамиль. Кроме того, такое разделение позволяет писать тестовые версии сетевого протокола, выполняющие запись и чтение из локальных файлов, а не из аккаунтов. В каждом случае сетевой слой меняется, но транспортный остаётся одинаковым.
Транспортный слой
Транспортное соединение PySkyWiFi между двумя клиентами состоит из двух «каналов» («аккаунтов программы авиамиль»). У каждого клиента есть канал отправки SEND, в который он может выполнять запись, и канала RECV, из которого он выполняет чтение. Клиенты выполняют запись в свой канал SEND, записывая в него данные, а чтение из канала RECV они выполняют, постоянно опрашивая его и наблюдая за изменениями.
С точки зрения транспортного слоя, канал — это просто нечто, куда и откуда можно выполнять запись и чтение данных. Во всём остальном транспортный слой не волнует, как работают каналы.
В каждый момент времени клиент PSWF (PySkYWiFi) может или отправлять, или получать данные, но не одновременно. Клиент в режиме отправки не будет видеть данные, отправляемые другим клиентом, а клиент в режиме получения никогда не должен отправлять данные, потому что другой клиент их не увидит. В этом отличие от TCP, в котором клиенты могут отправлять и получать данные когда угодно.
При протискивании HTTP-запросов и HTTP-ответов через аккаунт небесный прокси отправляет первое сообщение, а наземный демон получает его. Как только небесный прокси закончит отправку своего HTTP-запроса, он переключается в режим получения, а наземный демон переключается на отправку. Наземный демон выполняет HTTP-запрос и отправляет обратно ответ, после чего они снова меняются ролями, чтобы небесный прокси мог отправить ещё один HTTP-запрос.
Как долго передаются сообщения через такой узкий канал?
PSWF использует узкие каналы (например, поле имени аккаунта), в которые за раз можно уместить не так много данных. Поэтому протискивание через них длинных сообщений (наподобие HTTP-запросов) требует труда и времени.
Для отправки длинного сообщения отправитель сначала разделяет сообщение на фрагменты, умещающиеся в его канал SEND. Затем он по одному может отправить по этому каналу каждый из фрагментов.
Чтобы начать сообщение, отправитель передаёт первый фрагмент данных сообщения внутри сегмента DATA
:
Сегмент
DATA
имеет следующую структуру:
Буква
D
Порядковый номер фрагмента (шестизначное число, уникальным образом идентифицирующее фрагмент)
Сам фрагмент данных.
Например, сегмент данных в середине сообщения может выглядеть так:
D000451adline": "Mudslide in Wigan causes m
После того, как отправитель передаст сегмент DATA
, он делает паузу. Он хочет передать следующий сегмент DATA
, но не может переписать поле имени аккаунта, пока не будет знать, что получатель принял и обработал предыдущий.
Получатель сообщает отправителю, что тому можно безопасно передавать новый сегмент DATA
, подтверждая считывание каждого сегмента. Получатель делает это, записывая сегмент ACK
в собственный канала SEND:
Сегмент
ACK
имеет следующую структуру:
Буква
A
Шестизначный порядковый номер сегмента, получение которого подтверждается
Например:
A000451
Отправитель постоянно опрашивает собственный канал RCV в поисках изменений, поэтому сразу же считывает этот новый сегмент ACK
. После того, как отправитель прочитал ACK
, он знает, что получатель принял сегмент, соответствующий порядковому номеру ACK
. Например, если отправитель получает сегмент ACK
с порядковым номером 000451
, то он знает, что может безопасно отправлять следующий сегмент DATA
с порядковым номером 000452
. Поэтому отправитель извлекает следующий фрагмент из сообщения и собирает новый сегмент DATA
из этого фрагмента и порядкового номера. Отправитель записывает новый сегмент в свой канал SEND, а затем встаёт на паузу, ожидая следующего ACK
.
Этот цикл продолжается, пока отправитель не передаст все данные сообщения. Чтобы сообщить получателю о завершении, отправитель передаёт сегмент END
.
Сегмент
END
состоит из одной буквыE
.
Когда получатель видит сегмент END
, он понимает, что сообщение отправителя завершено. Отправитель и получатель меняются ролями. Прежний отправитель начинает опрашивать свой канал RECV в поисках сегментов DATA
, а прежний получатель начинает разбивать на фрагменты сообщение-ответ и отправлять их через свой канал.
Транспортной логике вообще не важны подробности сетевого слоя, через который передаются сегменты. Транспортному слою нужно лишь, чтобы сетевой слой предоставил два канала, один для чтения, другой для записи. Сетевой слой может передавать эти данные через локальные файлы, профиль Discord или аккаунт программы авиамиль. Благодаря этой универсальности PySkyWiFi может работать с аккаунтами программ авиамиль любых авиакомпаний, если компания позволяет логиниться из самолёта бесплатно.
Вот, как PSWF использует сегменты транспортного протокола для обмена длинными сообщениями:
Транспортный слой решает, какие данные клиенты должны передавать друг другу, но не говорит ничего о том, как они должны их передавать. Этим занимается сетевой слой.
Сетевой слой
Задача сетевого слоя — отправка данных между клиентами. Ему не важно, откуда взялись данные и что они значат; он просто получает какие-то данные из транспортного слоя и отправляет их другому клиенту (обычно через аккаунт).
Это значит, что сетевой слой достаточно прост. А ещё это значит, что в добавлении нового сетевого слоя для новой платформы программы авиамиль тоже нет ничего сложного. Мы используем новую платформу для реализации нескольких операций и нескольких свойств (см. ниже), а затем транспортный слой может автоматически использовать новую платформу без каких-либо дополнительных усилий.
Сетевой слой состоит из двух операций:
-
send(msg: str)
— записьmsg
в хранилище. В случае реализации на основе программы авиамиль он записывает значениеmsg
в поле имени аккаунта -
recv() -> str
— чтение сообщения из хранилища. В случае реализации на основе программы авиамиль он считывает значение из поля имени аккаунта.
Кроме того, реализация сетевого слоя должна определить два свойства:
-
sleep_for
— количество секунд, в течение которого транспортный слой должен ожидать между опросом новых сегментов из канала RECV. В случае тестовых реализацийsleep_for
может быть очень низким, но в случае реализации, например, на основе аккаунта программы авиамиль он должен составлять много секунд, чтобы не перегружать удалённый сервер слишком большим количеством запросов. -
segment_data_size
— количество символов, которое транспортный слой должен передавать в одном сегменте. Должно быть равно максимальному размеру поля аккаунта, используемого для передачи сегментов (часто примерно 20 символов).
Опционально реализация сетевого слоя также может предоставлять ещё две операции:
-
connect_send()
— хук, вызываемый отправителем при инициализации канала SEND. В реализации на основе аккаунта программы авиамиль это позволяет клиенту залогиниться на платформе с помощью имени пользователя и пароля. Это даёт клиенту куки, который он может использовать для аутентификации будущих вызововsend
иrecv
. -
connect_recv()
— хук, вызываемый получателем при инициализации канала RECV
Если вы реализуете все эти методы, то сможете использовать PySkyWiFi на самолётах другой авиакомпании. Но, повторюсь, не стоит этого делать.
Хитрости и тонкости
При написании сетевого слоя, использующего новую систему авиамиль, можно использовать пару трюков, повышающих скорость и надёжность реализации.
1. Кодировать сообщения так, чтобы аккаунт точно мог их принять их формат
HTML-формы аккаунтов программ авиамиль не позволяют вводить в имени неалфавитные символы. Stephen
ввести можно, а вот GET /data?id=5
, вероятно, отклонят.
Чтобы обойти эту проблему, сетевой слой должен перед записью сегментов в аккаунт преобразовать их в кодировку base26. Кодировка base26 — это способ представления строки буквами от A
до Z
. Чтобы преобразовать байтовую строку в base26, нужно преобразовать байты в одно большое число, а затем представить это число в системе счисления по основанию 26 (отсюда и название), где цифрами становятся буквы от A
до Z
.
def b26_encode(input_string: str) -> int:
# Преобразуем входную в строку в целое число base-256
base256_int = 0
for char in input_string:
base256_int = base256_int * 256 + ord(char)
# Преобразуем целое число base-256 в строку base26
if base256_int == 0:
return 'A' # Особый случай пустых входных данных или входных данных, равных нулю
base26_str = ""
while base256_int > 0:
base26_str = chr(base256_int % 26 + 65) + base26_str
base256_int //= 26
return base26_str
b26_encode("Hello world")
# => 'CZEZINADXFFTZEIDPKM'
Транспортному слою не нужно знать об этой кодировке. Сетевой слой получает какое-то количество байтов, кодирует их в base26 и записывает эту закодированную строку из символов от A
до Z
в аккаунт. Когда сетевой слой считывает значение base26 из аккаунта, он декодирует закодированную строку обратно в число, а затем и в байты, и далее возвращает эти байты транспортному слою.
При кодировании строки по основанию 26 она становится существенно длиннее, аналогично тому, как двоичный вид числа намного длиннее десятичного. Это уменьшает пропускную способность нашего протокола. Мы можем увеличить ширину, воспользовавшись base52 (буквы в верхнем и нижнем регистре), что немного сократит объём данных. Оставим это как улучшение для версии 2.
2. Увеличить пропускную способность, задействовав больше полей аккаунта
Ещё один способ увеличения пропускной способности PSWF — расширение размера сегмента, который может обрабатывать сетевой слой. Если мы удвоим размер сегментов, то и удвоим пропускную способность протокола.
Поля в аккаунтах программ авиамиль обычно имеют ограничения на длину. Например, может допускаться имя не длиннее 20 символов. Однако можно максимизировать пропускную способность:
-
Использовав полную длину поля
-
Разбив сегмент на несколько полей
Допустим, мы можем управлять пятью полями, в каждом из которых может храниться до 20 символов. Вместо использования одного поля для передачи сегментов из 20 символов мы можем разбить сегмент из 100 символов на 5 блоков по 20 и обновлять их все вместе в одном запросе. Тогда получатель сможет тоже за один запрос считать все 5 полей и сшить их вместе, чтобы воссоздать полный сегмент.
Дальнейшие улучшения
HTTP CONNECT
Было бы лучше, если бы PySkyWiFi использовал для настройки туннеля между небесным прокси и целевым сайтом HTTP-запросы CONNECT
, а не перекидывал вручную HTTP-запросы. Большинство HTTP-прокси реализовано на основе CONNECT
-запросов, и их использование позволит PySkyWiFi действовать в качестве прокси системного уровня и так обрабатывать запросы веб-браузера. Кроме того, тогда PySkyWiFi сможет напрямую устанавливать с целевым веб-сайтом TLS-соединения, чтобы при передаче через аккаунт программы авиамиль трафик шифровался.
С другой стороны, для использования CONNECT
придётся приложить гораздо больше труда, а моя шутка и так затянулась.
В заключение
Закончив со всем этим, я воспользовался PySkyWiFi для загрузки домашней страницы моего блога при помощи curl
, создав туннель данных через GitHub Gist. Много минут спустя я получил ответ. Я проскроллил HTML и подумал, что это одновременно был наиболее и наименее продуктивный полёт в моей жизни.
Автор: PatientZero