LINUX.ORG.RU

TCP-протокол и странные FIN,ACK,RST... Вопросы.

 


0

2

Нормально закрывать TCP-коннект между А и Б по инициативе А так:

  1. А шлёт FIN («я всё сказал»)
  2. Б шлёт FIN + ACK («вас понял, я тоже всё»)
  3. А шлёт ACK («вас понял»).

Три вопроса вопроса:

  1. Непонятно, зачем посылать FIN на шаге (1), когда можно послать RST и забыть обо всём. Видимо это не экологично: потеря RST приведёт к сохранению коннекта на Б, в сценарии выше (1) можно повторить, если долго не будет (2)?

  2. А есть какая-то другая причина, почему после (1) коннекшн ещё остаётся? Может ли Б после получения FIN что-то PUSH в этот коннекшн? Но это бессмысленно, софтина на А уже вызвала close() на сокете.

  3. Проводил эксперимент:

  1. A - C++ сервер вида «socket();setsetsockopt();bind();listen(port 12345);accept();». Также, в этом сервере написано «По приходу чего-нибудь в коннект, вычитать всё через read() и сразу сделать close(socket)»;
  2. Б: «nc 127.0.0.1 12345»
  3. Б что-то PUSH в коннекшн (печатаю в nc рандомную строку, жму enter), A это вычитывает из сокета и делает сразу close(socket);
  4. В tcpdump видно, как от А к Б прилетает FIN + ACK. Зачем тут ACK? ACK на PUSH прилетело пакетом ранее….
  5. В ответ на FIN от Б прилетает один ACK, как будто nc не собирается закрывать. Всмысле? Какой смысл тут не хотеть закрывать?
  6. Ввожу в nc ещё одну строку, отправляю (от Б летит PUSH + ACK). Зачем тут опять ack?
  7. От A приходит RST. Это уже выглядит как «слыш ты кто»?


Последнее исправление: tcpfinhello (всего исправлений: 2)

когда можно послать RST и забыть обо всём.

Можно и дверью хлопнуть, но, так ведь о отлететь что-то может. А 4 или 3-way handshake для «gracefully» shutdown.

А есть какая-то другая причина, почему после (1) коннекшн ещё остаётся?

Чтобы дождаться последних данных от клиента и его ответного «я всё сказал».

Но это бессмысленно, софтина на А уже вызвала close() на сокете.

Есть shutdown() с SHUT_WR.

gag ★★★★★
()

Непонятно, зачем посылать FIN на шаге (1), когда можно послать RST и забыть обо всём.

Ну вот представь, что с той стороны тебе шлют RST, а ты как раз в сокет пишешь. Хренак, SIGPIPE, ты мертв.

Еще вопросы остались?

t184256 ★★★★★
()

Пакеты идут по сети в рандомном порядке и в нем же [не]приходят. Close/rst означает выкинуть все нахрен, что ОС и делает, включая буферы невычитанные процессом или неотправленные в железо, на обеих сторонах. В случае потери пакета ретрансляции также не будет. На loopback ты такого не увидишь.

Поэтому все не выпендриваются и делают грейсфул шатдаун.

anonymous
()

бессмысленно, софтина на А уже вызвала close() на сокете.

Ну это этой софтины проблемы. Нормальные люди ждут результата SHUT_WR (блоком или в фоне) и только потом клозят, хотя у линукса есть свои загоны по этому поводу.

anonymous
()
Ответ на: комментарий от anonymous

Нормальные люди ждут результата SHUT_WR (блоком или в фоне) и только потом клозят,

Непонятно. Я хочу закрыть соединение в приложении, я могу вызывать только close(). Я не могу вызвать i_wait_close();

hellonik
()

а если надо заново создать соединение то тебя шлют

doc0
()
Последнее исправление: doc0 (всего исправлений: 1)
Ответ на: комментарий от t184256

Хренак, SIGPIPE, ты мертв.

SIGPIPE может прилететь и от того что кто-то рубанул свет в датацентре. Те, кто не обрабаытают SIGPIPE (или читают без MSG_NOSIGNAL) - обмылки и для них есть отдельный котел.

PPP328 ★★★★★
()

тут мне кажется задействовано много устройств и чем они более информированы в ситуации тем лучше работает вся сеть

doc0
()
Ответ на: комментарий от PPP328

По соседству с теми, кто шлет RST сразу за данными в штатной ситуации в асинхронных протоколах.

t184256 ★★★★★
()
Ответ на: комментарий от hellonik

Потому что close() это аналог free() для сокета. С точки зрения юзерленда он не закрывает сокет, а просто выкидывает его, и для этого не надо ничего ждать (но см. нюанс ниже).

Чтобы нормально закрыть сокет надо помнить, что это дуплекс-канал, а не односторонний или файл, и закрытий не одно, а два. Чтобы не скипать полную логику, рассмотрим полностью асинхронное двустороннее общение, т.е. не запрос-ответ, а что-то вроде чата.

  • открылся
  • читаешь-пишешь as usual
  • если ты решил закрыться
    • посылаешь FIN (shutdown(SHUT_WR))
    • больше ничего не пишешь
    • но пир все еще может считать, что не договорил
      • как и его ядро при потерях
    • читаешь пока он не закроет (read() == 0)
    • теперь можно выкинуть (close())
  • если пир решил закрыться
    • он шлет FIN
    • получаешь read() == 0
    • больше не читаешь
    • дописываешь все, что хотел, as usual
    • посылаешь FIN (так же)
    • теперь можно выкинуть (close())

Вы оба проходите TCP стейты FIN-WAIT-1, CLOSE-WAIT, FIN-WAIT-2, LAST-ACK, TIME-WAIT, CLOSING, скипая некоторые, в зависимости от рейсов, кто быстрее закрыл, и какие пакеты где задержались. Если закрыть сокет не дожидаясь, ядро просто шлет RST, и там уже кто не успел, тот опоздал.

Поскольку хопы между вами могут терять любые пакеты, гарантированного общения/подтверждения нет, и в ядре все держится на таймаутах, после которых общение гарантированно не имеет смысла. Но для вас это выглядит как блокирующие вызовы read/write (или нонблок аналоги), -1/SIGPIPE, явный EOF (FIN/SHUT_WR/read 0) и «просто» close().

Последний тоже может быть блокирующим, но это афаир как раз прикол для тех, кто не осилил грейсфул, или на уровне соглашения работает запрос-ответом - при включенном SO_LINGER close() сначала дождется отправки и подтверждения прошлых write() и только потом пошлет FIN и дропнет сокет. Иначе неотправленное просто выкинется. То есть это такой graceful_close() для бедных.

Из-за асинхронки пиров/сети, разных стейтов соединения во времени, того что write() разблокируется не по ACK, а просто по факту отправки, возникает куча нюансов, которые я сам не до конца понимаю, поэтому если хочешь вкурить, то выдели месяц-другой, не меньше. Кроме того, разные стеки ведут себя по-разному, и тут два слоя понимания: что происходит в самом стеке, и как системные вызовы/возвраты мапятся на события в нем. Но с точки зрения юзерленда схема выше это текстбук мастхев, если ты в фуллдуплексе.

Если ты строго в запрос-ответе, то наверное можно опираться на лингер, но лично я такое ни разу не писал и не тестил, не могу комментировать. Кроме того, в лингере ты отчасти живешь в фулл-блок режиме на запись со стороны отдающего ответ, а это ну сам понимаешь уровень хелло ворлда на коленке, а не реального сервера.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.