- Вопрос или проблема
- Первый случай (разные адреса интерфейса)
- Второй случай (одинаковые адреса)
- Вопросы
- Первый случай (разные адреса)
- Второй случай (одинаковые адреса)
- Ответ или решение
- Первый случай: Разные адреса (10.1.0.10 и 10.1.0.20)
- Второй случай: Одинаковые адреса (10.1.0.20 и 10.1.0.20)
- Причины отсутствия бесконечной передачи пакетов
- Рекомендации по чтению
Вопрос или проблема
Первый случай (разные адреса интерфейса)
У меня есть эта простая программа на Go для инициализации интерфейса и его чтения/записи. Простая труба:
package main
import (
"fmt"
"log"
"log/slog"
"slices"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/songgao/water"
)
func main() {
// Создание сетевого интерфейса utun{номер}, имя назначается автоматически
ifce, err := water.New(water.Config{DeviceType: water.TUN})
if err != nil {
panic(fmt.Errorf("новый tun: %w", err))
}
slog.Info("создан интерфейс", "имя", ifce.Name())
for {
fmt.Printf("\n")
// Ожидание и чтение пакета из интерфейса DestAddr(?) в buf
buf := make([]byte, 1024)
n, err := ifce.Read(buf)
if err != nil {
panic(fmt.Errorf("чтение ifce: %w", err))
}
p := gopacket.NewPacket(buf[:n], layers.LayerTypeIPv4, gopacket.Default)
icmpPacket, isICMP := p.Layer(layers.LayerTypeICMPv4).(*layers.ICMPv4)
if !isICMP {
slog.Warn("не ICMP пакет")
continue
}
slog.Info("Получено сообщение", "байты", n, "тип", icmpPacket.TypeCode, "последовательность", icmpPacket.Seq)
// Запись пакета на Addr(?)
n, err = ifce.Write(buf[:n])
if err != nil {
panic(fmt.Errorf("запись ifce: %w", err))
}
slog.Info("Записан пакет", "байты", n, "тип", icmpPacket.TypeCode, "последовательность", icmpPacket.Seq)
}
}
После запуска процесса и получения имени интерфейса, я настроил адреса:
ifconfig utun6 10.1.0.10 10.1.0.20 up
ifconfig -v utun6
# utun6: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500 index 25
# eflags=1002080<TXSTART,NOAUTOIPV6LL,ECN_ENABLE>
# xflags=4<NOAUTONX>
# inet 10.1.0.10 --> 10.1.0.20 netmask 0xff000000
# generation id: 958
# state availability: 0 (true)
# scheduler: FQ_CODEL
# qosmarking enabled: no mode: none
# low power mode: disabled
# multi layer packet logging (mpklog): disabled
# routermode4: disabled
# routermode6: disabled
Что я вижу
Если я пингую адрес назначения, я получаю таймауты без ответов, и моя программа получает ICMP сообщение:
ping -c 1 10.1.0.20
# PING 10.1.0.20 (10.1.0.20): 56 data bytes
#
# --- 10.1.0.20 ping statistics ---
# 1 packets transmitted, 0 packets received, 100.0% packet loss
вывод программы go:
# INFO Получено сообщение байты=84 тип=EchoRequest seq=0
# INFO Записан пакет байты=84 тип=EchoRequest seq=0
Итак, мы получили пакет и отправили его куда-то (10.1.0.10) без ответа, он был потерян.
Второй случай (одинаковые адреса)
Давайте изменим шаг настройки utun6 на это:
sudo ifconfig utun6 10.1.0.20 10.1.0.20 up
ifconfig utun6
# utun6: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
# inet 10.1.0.20 --> 10.1.0.20 netmask 0xff000000
ping -c 2 10.1.0.20
# PING 10.1.0.20 (10.1.0.20): 56 data bytes
# 64 bytes from 10.1.0.20: icmp_seq=0 ttl=64 time=0.422 ms
# 64 bytes from 10.1.0.20: icmp_seq=1 ttl=64 time=0.476 ms
вывод программы go:
# INFO Получено сообщение байты=84 тип=EchoRequest seq=0
# INFO Записан пакет байты=84 тип=EchoRequest seq=0
# INFO Получено сообщение байты=84 тип=EchoReply seq=0
# INFO Записан пакет байты=84 тип=EchoReply seq=0
# INFO Получено сообщение байты=84 тип=EchoRequest seq=1
# INFO Записан пакет байты=84 тип=EchoRequest seq=1
# INFO Получено сообщение байты=84 тип=EchoReply seq=1
# INFO Записан пакет байты=84 тип=EchoReply seq=1
Вопросы
Первый случай (разные адреса)
Мое предположение:
ping
отправляет ICMP пакет на 10.1.0.20- Интерфейс/сокет слушает на 10.1.0.20 и получает пакет
- Мы отправляем его куда-то (10.1.0.10)??? (Я предполагаю, что из-за отсутствия маршрутного правила ядро не знает, что делать, и эти пакеты просто отбрасываются никуда)
Что на самом деле происходит на шаге 3? Почему я не могу прослушивать сообщения с 10.1.0.10 (или могу, на той же машине, не создавая обратный интерфейс)? Я предполагаю, что я мог бы настроить маршрут обратно к интерфейсу, чтобы смоделировать поведение второго случая с помощью route add
).
Второй случай (одинаковые адреса)
Итак, как я понимаю:
ping
отправляет ICMP пакет на 10.1.0.20- Интерфейс/сокет слушает на 10.1.0.20 и получает пакет (8 EchoRequest)
- Пакет отправляется на тот же интерфейс, и мы получаем это отправленное сообщение в нашей программе на go на втором цикле (я предполагаю, что оба адреса указывают на наш интерфейс, поэтому мы получаем сообщение обратно, как при петлевой проверке)
- Мы получаем сообщение с шага 3, но с другим типом (0 EchoReply)
- Как-то мы возвращаем сообщение обратно команде
ping
Почему мы получаем разные данные (тип), отправляя их через/в тот же интерфейс? Я предполагаю, это связано с тем, как ядро внутренне их преобразует, но почему? И почему ping
получает ответ, все что мы делаем – это записываем его обратно на наш интерфейс, почему у нас не происходит бесконечная пересылка пакетов? Почему EchoRequest возвращается в нашу программу, но EchoReply распознается ping
?
Я пытался разобраться в страницах man и route
/ifconfig
/netstats
/tcpdump
/гуглил в течение нескольких дней, но это все еще слишком запутано, а документация слишком неопределённая, чтобы правильно понять, что здесь происходит. Можете ли вы предложить какой-либо материал для чтения, чтобы понять весь этот хаос?
Ответ или решение
На данный момент в данной проблеме мы имеем два случая работы с TUN-интерфейсами в macOS, где первый случай используют разные IP-адреса, а второй — одинаковые. Давайте рассмотрим каждый случай подробнее и ответим на ваши вопросы.
Первый случай: Разные адреса (10.1.0.10 и 10.1.0.20)
В этом сценарии при настройке интерфейса с разными адресами (10.1.0.10
для локального адреса и 10.1.0.20
для удаленного) вы сталкиваетесь с проблемой, когда ICMP-пакеты, отправленные на 10.1.0.20
, не возвращаются. Рассмотрим ваши утверждения по шагам:
-
Отправка пакета: Когда вы пингуете
10.1.0.20
, пакет ICMP действительно отправляется на этот адрес, и ваш интерфейс, слушающий на10.1.0.20
, получает его. -
Прослушивание интерфейса: Так как интерфейс настроен на
10.1.0.20
, он корректно принимает пакеты, направленные на этот адрес. Вы это наблюдаете в выводе вашего Go-приложения, где вы получаетеEchoRequest
. -
Проблема с маршрутизацией: На этом этапе происходит недоразумение. Когда ваше приложение пытается отправить пакет обратно по адресу
10.1.0.10
, проблема заключается в том, что системы маршрутизации может не знать, как достать до10.1.0.10
, так как на этой же машине нет маршрута, ведущего обратно на себя черезutun6
. Это происходит из-за отсутствия маршрутных записей, и как следствие, пакеты просто теряются.
Чтобы «разрешить» ситуацию, вы можете использовать команду route add
для создания маршрута, который укажет, что пакеты к этому адресу следует возвращать через интерфейс utun6
. Это позволит вашему приложению получать пакеты обратно.
Второй случай: Одинаковые адреса (10.1.0.20 и 10.1.0.20)
Во втором случае, когда вы задали одинаковые IP-адреса для вашего интерфейса, ситуация изменяется:
-
Отправка пакета: Аналогично первому случаю, вы отправляете пинг на
10.1.0.20
. -
Прослушивание адреса: Интерфейс получает
EchoRequest
и обрабатывает его. -
Отправка пакета обратно: Так как вы написали этот же пакет обратно на тот же IP-адрес (
10.1.0.20
), это ведет к вопросу о том, как работает механизм обработки. Пакет перехватывается и обрабатывается вашей программой опять, возвращая ICMP-пакет, который теперь получает типEchoReply
. -
Тип пакета: Обратите внимание, что при возврате пакета тип ICMP изменяется на
EchoReply
. Это поведение реализовано в стекe сетевых протоколов, где конечные точки (клиент и сервер) различают типы ICMP-сообщений в зависимости от их состояния («запрос» или «ответ»). Таким образом, передавая ответ клиенту (в данном случаеping
), система отличает, что это ответ на предыдущий запрос.
Причины отсутствия бесконечной передачи пакетов
Процесс передачи организован так, чтобы избежать зацикливания. Операционная система не пересылает один и тот же пакет обратно, если он уже был обработан, что предотвращает создание зацикливания и застревания в одном состоянии.
Рекомендации по чтению
Чтобы глубже понять, как работают TUN/GRE интерфейсы и управление маршрутизацией в macOS, рекомендуется изучить следующие материалы:
- «TCP/IP Illustrated, Volume 1: The Protocols» — книга, предоставляющая надежное основание в понимании протоколов TCP/IP.
- man pages на macOS по командным утилитам, связанным с сетью:
ifconfig
,route
,ping
, иtcpdump
. - Документация и статьи по gopacket и Go — изучите, как библиотека работает на низком уровне.
Эти ресурсы помогут вам лучше ориентироваться в сетевых протоколах и архитектуре, что, в свою очередь, станет основой для дальнейшего исследования вопросов маршрутизации и управления трафиком в сети.