Когда неподкованные пользователи (пенсионеры, школьники, офисный планктон) первый раз сталкиваются с чёрным окошком, именуемым "Командной строкой" они зачастую хотят с профессиональным видом проверить интернет соединение.
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мб файлик у себя и потерять его спустя пару часов непрерывной работы. Зато был получен позитивный опыт работы с низкоуровневыми сокетами.
Будет интересно, дочитал ли вообще кто-то это до конца?
Оцените, пожалуйста первую статью на Хабре :-) Здравая критика, а что в особенности важно - ещё бесполезные, но весёлые идеи приветствуются!