Когда неподкованные пользователи (пенсионеры, школьники, офисный планктон) первый раз сталкиваются с чёрным окошком, именуемым "Командной строкой" они зачастую хотят с профессиональным видом проверить интернет соединение.
ping 8.8.8.8
Магический C:\Windows\System32\PING.EXE выполняет вполне интуитивно понятные действия: отправляет запрос на указанный адрес (в данном случае 8.8.8.8 - dns google), и если адрес отвечает, то выводит задержку ответа, по которой можно косвенно судить о скорости интернет соединения...
Если же вы имеете начальное представление о принципе работы интернетов этих ваших и примерно понимаете сетевую модель, то вам до следующего заголовка)
База базовая
Сидели умные люди такие в восьмидесятых и думали, как бы интернет сделать общий. Сидели они, будучи соединёнными сотнями Ethernet кабелей и разрабатывали иерархическую модель интернета, дабы каждый был соединён с каждым, и было всё стильно, да без централизации...
Зелёные (верхние) уровни нас не интересуют, их реализовывают конкретные приложения и сервисы, их зоопарк мы трогать не будем. Нужно идти с низов.
Провод передаёт приёмнику логическое состояние передатчика (0\1 - 1бит), является самым низким (физическим) уровнем.
Второй уровень добавляет понятие MAC адресов. Теперь тот, кто отправляет данные, идентифицирует того, кто на другом конце провода. Каждому устройству в сети даётся уникальный шестибайтный адрес, и появляется возможность маршрутизировать данные к конкретному абоненту (в зоне действия провода).
Теперь данные пакуются в пакеты и адресуются некому IP адресу получателя, а специальные устройства вроде вашего роутера или огромного шкафа-маршрутизатора в общаге заботятся об их доставке (но не гарантируют её!)
Поверх 3его уровня, в частности IP адресов были построены такие протоколы передачи данных как TCP, HTTPS, UDP, FTP, ICMP и т.д. Они заботятся о многих вещах: гарантии передачи данных, безопасности и передаче конкретных высокоуровневых структур данных.
Идея!
Тут лучше просто взглянуть на оригинал:
Его план такой (конечно, интернет подразумевается безлимитным, а сетевой адаптер достаточно мощным):
Находим точки А и В максимально удалённые друг от друга в сети.
Отправляем большие данные из А в В
Они фрагментируются и тормозят по пути, оседая в кэшах маршрутизаторов, которые их соединяют. (растёт задержка)
Пункт В просто отправляет данные обратно к А
А использовал модель интернета как "дикое" хранилище файлов на некоторое время (сравнимое с секундой)
Звучит бесполезно, но интересно. Надо подумать
Есть проблема: В в данном случае остается ни с чем. И совершать такие пересылки взаимно не выгодно.
Вот если бы можно было обойтись без В
Ты не можешь контролировать улетевший пакет
Или можешь?... Улетевший пакет...? Ping?
Ping — утилита для проверки целостности и качества соединений в сетях (или, проще говоря, штука которая отправляет ICMP Echo пакеты)
Созревает план. Кладём пароль от крипто-кошелька в нагрузку пинг пакета (удаляем его локально), отправляем на другой конец земного шара и на протяжении 0.5сек он хранится, будучи в кэшах роутеров и маршрутизаторов по дороге!
Б-безопасность П-практичность
Но windows не позволяет редактировать содержимое, отправляемое ping.exe(
Но, для начала, проверим: есть ли оно вообще?
Качаем Wireshark - (вики) - (офиц. сайт) - утилиту для перехвата пакетов. Запускаем перехват и ping 8.8.8.8 в консоли.
Полезная нагрузка (данные) в этом пакете действительно есть, однако до реальной пользы им далеко - это английский алфавит (нет, я не испытываю ненависть к латинице, просто мне хотелось бы уметь редактировать это содержимое).
Точка невозврата
С этого момента начинается практическое создание скрипта, который автоматизировал бы это. Идея изначально провальная, ведь icmp не гарантирует доставку, и очень ценные файлы, которые я буду фрагментировать и гонять таким образом рано или поздно будут по частям теряться).
Ну, а мне и не жалко, ведь дальше я хотел просто шутки ради реализовать этот механизм на Python\Sockets потестить, и забыть как страшный сон - поехали:
ICMP
Протокол межсетевых управляющих сообщений, или проще говоря - проверки связи.
Не гарантирует доставку данных, не использует шифрование - сплошной минимализм. Но, зато он просит получателя отправить себя обратно! Если получатель (сетевое устройство 3его уровня сетевой модели) не стало намеренно отклонять подключение, то оно скорее всего так и поступит.
icmp пакетами можно конечно отправлять полезные коды, которые помогут администрировать сеть, но мы его будем рассматривать как примитивный тестер связи, ок?
(я специально не упоминаю порты и всё что с ними связано, ведь 1й порт для ICMP это скорее условность, да и эта информация нигде далее не понадобится)
Структура ICMP пакета
Начнём с низов этой вложенной структуры.
Заголовок кадра это всё, что о пакете нужно знать для MAC адресации - мы с этим уровнем взаимодействовать не будем, так что идём далее.
Заголовок дейтограммы (Заголовок IP) - IP header 20 байт под адреса получателя\отправителя и прочей служебной информации для сетевой маршрутизации по IP.
-
ICMP заголовок и ICMP данные - в нашем случае данные это:
Идентификатор + Номер последовательности + Данные (payload, хехе...)
Ре-а-ли-за-ци-я
Строгое представление ICMP header:
type (8), code (8), checksum (16), id (16), sequence (16)
def create_packet(id):
ICMP_ECHO_REQUEST=8 #Код типа ICMP - в нашем случае ECHO
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, (тут должен быть хэш пакета), 0, id, 1)
data = "hello!"
return header + data
Подготовим icmp пакет используя STRUCT для превращения типов данных в байты.
Но есть один нюанс. Это дырка (тут должен быть хэш пакета), checksum - если вам так угодно. Без него наш пакет будет сочтён за битый. Парадоксально, ведь мы должны посчитать хэш пакета, ещё до его создания. Однако, допустимо использовать для подсчёта хэша хэш в заголовке = 0
def create_packet(id):
ICMP_ECHO_REQUEST=8 #Код типа ICMP - в нашем случае ECHO
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
data = b"hello!"
my_checksum = checksum(header + data)
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
return header + data
Создаем шаблон пакета, добавляем наши данные в виде "hello!", считаем checksum (пока не ясно как), и создаем окончательный пакет, зная хэш.
Функцию для подсчёта хэша пакета я подсмотрел где-то на просторах интернета, и не пожалел. Нужно было только немного адаптировать под python3.
def checksum(source_string):
sum = 0
count_to = (len(source_string) / 2) * 2
count = 0
while count < count_to:
this_val = source_string[count + 1]*256+source_string[count]
sum = sum + this_val
sum = sum & 0xffffffff
count = count + 2
if count_to < len(source_string):
sum = sum + source_string[len(source_string) - 1]
sum = sum & 0xffffffff
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer = ~sum
answer = answer & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
Построитель пакетов полностью готов.
Остаются: функция send и функция recv
Вторая будет выполняться через время после первой и получать обратно отправленное.
def send(dest_addr):
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
host = socket.gethostbyname(dest_addr)
packet_id = random.randint(0,65535)
packet = create_packet(packet_id)
while packet:
sent = my_socket.sendto(packet, (dest_addr, 1))
packet = packet[sent:]
return my_socket,packet_id
Send отправляет наш hello на данный ей адрес и возвращает сокет и ид пакета, в котором ждать возвращения оного.
def recv(my_socket, packet_id):
ready = select.select([my_socket], [], [], 2) #таймаут 2с
rec_packet, addr = my_socket.recvfrom(1024)
icmp_header = rec_packet[20:28] # Байты с 20 по 28 - заголовок ICMP
type, code, checksum, p_id, sequence = struct.unpack(
'bbHHh', icmp_header)
data = rec_packet[28:] # Наш hello будет лежать после заголовка ICMP
return data
Recv получает пакет с заданным ID из старого сокета
Последние штрихи для запуска
import struct
import socket
import random
import select
ICMP_CODE = socket.getprotobyname('icmp')
Тест
Пора бы и продемонстрировать работу сия кода:
Попробуем отправить hello гуглу
send("8.8.8.8")
Попробуем отправить и получить обратно через время...
sock, id=send("8.8.8.8")
...
print(recv(sock,id))
Добавил функции send аргумент data, для кастомизации этого Hello
Итоговый код?
import struct
import socket
import random
import select
ICMP_CODE = socket.getprotobyname('icmp')
def checksum(source_string):
sum = 0
count_to = (len(source_string) / 2) * 2
count = 0
while count < count_to:
this_val = source_string[count + 1]*256+source_string[count]
sum = sum + this_val
sum = sum & 0xffffffff
count = count + 2
if count_to < len(source_string):
sum = sum + source_string[len(source_string) - 1]
sum = sum & 0xffffffff
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer = ~sum
answer = answer & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
def create_packet(id,data):
ICMP_ECHO_REQUEST=8 #Код типа ICMP - в нашем случае ECHO
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, 0, id, 1)
data = data
my_checksum = checksum(header + data)
header = struct.pack('bbHHh', ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
return header + data
def send(dest_addr,data):
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_CODE)
host = socket.gethostbyname(dest_addr)
packet_id = random.randint(0,65535)
packet = create_packet(packet_id,data)
while packet:
sent = my_socket.sendto(packet, (dest_addr, 1))
packet = packet[sent:]
return my_socket,packet_id
def recv(my_socket, packet_id):
ready = select.select([my_socket], [], [], 2) #таймаут 2с
rec_packet, addr = my_socket.recvfrom(1024)
icmp_header = rec_packet[20:28] # Байты с 20 по 28 - заголовок ICMP
type, code, checksum, p_id, sequence = struct.unpack(
'bbHHh', icmp_header)
data = rec_packet[28:] # Наш hello будет лежать после заголовка ICMP
return data
sock, id=send("8.8.8.8",b"hohoho")
print(recv(sock,id))
Была отправлена строка, и получена через 50мс, однако, это не есть решенная задача.
Дописать же функцию для фрагментации и последовательной отправки файла я предлагаю вам самим.
Я дописал и решил проблему маленького пинга до 8.8.8.8 (не успевал отправить много данных, как они начинали возвращаться). Нашел адрес одного стабильного сайта с хостингом в Новой Зеландии(пинг ~400). Что?
Однако есть ограничения, которые не позволяют получить зрелищный результат.
Один icmp пакет не хочет нести больше 1кб, а если отправлять их с очень большой частотой, то они бьются еще на старте.
Максимальный размер файла, который мне удалость удержать таким образом был текст 0.1мб (что вполне прилично для текста). Конечно, он со временем исчезал по кускам, но это следует из отсутствия гарантий доставки у ICMP.
Conclusion
Под конец могу сказать, что в итоге получилась (как и ожидалось) не самая полезная и практичная программа, которая может сохранить 0.1мб файлик у себя и потерять его спустя пару часов непрерывной работы. Зато был получен позитивный опыт работы с низкоуровневыми сокетами.
Будет интересно, дочитал ли вообще кто-то это до конца?
Оцените, пожалуйста первую статью на Хабре :-) Здравая критика, а что в особенности важно - ещё бесполезные, но весёлые идеи приветствуются!
Комментарии (26)
maikuss
15.07.2023 12:41+1Ping tunnel ещё можно было бы изобрести. Но он тоже уже изобретён. ) http://www.cs.uit.no/~daniels/PingTunnel/
adron_s
15.07.2023 12:41+2Кстати часто операторы связи приоритезируют ICMP, чтобы скрыть перегрузки каналов связи. И "ping туннель" через таких операторов работает лучше чем UDP.
blind_oracle
15.07.2023 12:41+4В ранней сети Корбины между районами Москвы ходили только пинги и мы с друзьями туннелировались как раз через них ;)
CodeDroidX Автор
15.07.2023 12:41+1обидно бывает читать рассказы тех, кто застал рождение интернета из соединения своего компа с компом друга по проводу для quake по локалке) когда родился позже этого...
megaslowpoke
15.07.2023 12:41+1обиднее — периодически наблюдать что у кого-то в запасе больше лет чем у тебя, а старпёрские рассказы миллениалов — это всё фигня
Wesha
15.07.2023 12:41обиднее — периодически наблюдать что у кого-то в запасе больше лет чем у тебя
Зачем обижаться? Я тихо радуюсь, что я буду червей кормить — а они ещё помучаются!
shasoftX
15.07.2023 12:41+1отправляем на другой конец земного шара и на протяжении 0.5сек он хранится, будучи в кэшах роутеров и маршрутизаторов по дороге!
Б-безопасность П-практичность
У вас (или на промежуточном роутере) выключился свет на N секунд и все пакеты с паролем от крипто кошелька потерялись. Б - но это уже не слово безопасность :)
blind_oracle
15.07.2023 12:41Поэтому нужно отправлять данные на несколько хостов сразу, репликация, очевидно же
shasoftX
15.07.2023 12:41+3А какая разница на сколько хостов, если у вас света нет, к примеру, минуту. Все хосты пришлют ответы которые потеряются раз вас нет.
Т.е. как я понял информация жива только пока ходит запрос. Как только вас нет, то запрос потеряется и вся его информация тоже.
CodeDroidX Автор
15.07.2023 12:41Верно, однако зачастую информация теряется не в моем узле. Но факт остается фактом - хранить что либо таким способом не стоит.
pulsatrix
15.07.2023 12:41Сказочка про ping.Задумал школьный учитель детей ping-у обучить. И умер весь интернет ненадолго. Почему не надолго? Да потому что мертвый интернет не пингуется.
redfox0
15.07.2023 12:41Интересная идея, но бесполезное.
Вспомнилось, как я в своё время реализовал ICMP-туннель между двумя подсетями. Дано: две подсети A, B, между которыми отсутствуют acl - даже пинги не ходят. И есть общий ресурс в третьей подсети C, который доступен всем. Отправляем пинг на общий ресурс, подделывая IP-адрес отправителя. В итоге пакет ходит по маршруту A->C->B. Из встреченных сложностей: нужно знать все три ip-адреса, но позже ICMP-туннель оброс функционалом служебных сообщений, чтобы можно было протестировать туннель или сообщить реальный ip-адрес отправителя. Внутри туннеля бегал обычный tcp, так что потери пакетов восстанавливались им. Для браузинга интернета по выходным этого хватало.
CodeDroidX Автор
15.07.2023 12:41Много думал над этим, но пришел к выводу, что подделывать адрес отправителя бесполезно. Ведь этот адрес в заголовке пакета переписывается NATами и до получателя он дойдёт с другим адресом. Или нет? Если бы эта схема действительно работала, поверх неё можно было бы придумать ещё массу всяких плюшек...
redfox0
15.07.2023 12:41nat'a между подсетями не было в моём случае, просто связь между двумя машинами, на одной из которых был доступ в интернет, и на обеих админские права.
CodeDroidX Автор
15.07.2023 12:41nat'a между подсетями не было в моём случае
необычный кейс, вообще так обидно бывает читать рассказы тех, кто застал рождение интернета из соединения своего компа с компом друга по проводу для quake по локалке...
chuvechello
15.07.2023 12:41Я, конечно, могу ошибаться, но NAT никак не мешает подделыванию ip-адреса отправителя icmp-пакета, роутер просто пошлёт echo-ответ в поддельный ip-адрес (он то не в курсе ваших махинаций). На похожем принципе реализована ddos атака через icmp -
smurf attack
.
adron_s
Пан знает толк в извращеньях :-)
С учебной точки зрения опыт интересный ! ICMP действительно может нести данные и в контексте текущей задачи не отличается от UDP. Кроме уровня в модели OSI.
anonymous
НЛО прилетело и опубликовало эту надпись здесь
drygdryg
Это может иметь применение в стеганографии.