Discussion:
О SO_REUSEADDR
(слишком старое сообщение для ответа)
Serguei E. Leontiev
2014-02-02 16:19:56 UTC
Permalink
Всем привет,

Интересно, почему принято ограничено реализовывать SO_REUSEADDR?

По тексту стандарта, вроде как не подразумевается, что кроме сравнения
локальных адресов, что-то ещё учитывается.

The SO_REUSEADDR option indicates that the rules used in validating
addresses supplied in a bind() should allow reuse of local addresses.
Operation of this option is protocol-specific. The default value for
SO_REUSEADDR is off; that is, reuse of local addresses is not permitted.

Тем не менее, общепринято накладывать дополнительные ограничения такие, как
состояние TIME_WAIT для полностью идентичных пар адрес/порт (и/или запрет
перехода в сотояние TIME_WAIT), но при неполной идентичности этого
ограничения нет. Соответственно, при необходимости "истинного" повторного
использования адресов и/или необходимости полного игнорирования таймаутов в
процессе создания или удаления соединений приходится выходить за рамки
стандарта.

Конечно TIME_WAIT самое длительное состояние, но есть ещё ряд других
"коротких" состояний: полуоткрытые соединения, FIW_WAIT-ы, которые могут
мешать выполнить bind() сразу после повторного создания сокета. В чём
проблема у разработчиков реализаций TCP? Почему они усложняют жизнь
пользователям?

P.S.

SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
--
Успехов, Сергей Леонтьев, <http://www.cryptopro.ru> (NewsTap)
Valentin Nechayev
2014-02-02 16:56:30 UTC
Permalink
SEL> Интересно, почему принято ограничено реализовывать SO_REUSEADDR?
SEL> По тексту стандарта, вроде как не подразумевается, что кроме сравнения
SEL> локальных адресов, что-то ещё учитывается.
SEL> The SO_REUSEADDR option indicates that the rules used in validating
SEL> addresses supplied in a bind() should allow reuse of local addresses.
SEL> Operation of this option is protocol-specific. The default value for
SEL> SO_REUSEADDR is off; that is, reuse of local addresses is not permitted.
SEL> Тем не менее, общепринято накладывать дополнительные ограничения такие, как
SEL> состояние TIME_WAIT для полностью идентичных пар адрес/порт (и/или запрет
SEL> перехода в сотояние TIME_WAIT), но при неполной идентичности этого
SEL> ограничения нет.

Вы говорите про TCP, так? А теперь вопрос. Пришёл пакет от некоей пары
(хост, порт) на некую локальную пару (хост, порт). К какому именно из
соединений в Вашем варианте он будет относиться, и почему собственно?
Как надёжно это разделить, с учётом того, что на IP формально
возможно, например, что пакетик по дороге раздвоился, далее полежал у
какого-то медленного раутера минуту-другую в очереди и наконец был
отдан туда, где его уже отчаялись ждать?

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

SO_REUSEADDR в нынешнем виде собственно и нужен для того, чтобы не
было по умолчанию ситуации, когда один фиксированный локальный адрес
прицепился к другому фиксированному локальному адресу - есть любители
таких настроек. Тот, кто ставит эту опцию, должен отдавать себе отчёт,
что с этого момента это его проблема - не допускать конфликтов полных
идентификаторов соединения.

SEL> Соответственно, при необходимости "истинного" повторного
SEL> использования адресов и/или необходимости полного игнорирования таймаутов в
SEL> процессе создания или удаления соединений приходится выходить за рамки
SEL> стандарта.

Я не удивляюсь. У TCP совершенно конкретное предназначение по целому
ряду профилей. Попытка применить его под что-то другое нарывается на
защиту такого профиля - например, принудительная вежливость перед
другими участниками сети даже ценой потери производительности. Кого
это не устраивает - пусть делает/использует что-то другое.

SEL> Конечно TIME_WAIT самое длительное состояние, но есть ещё ряд других
SEL> "коротких" состояний: полуоткрытые соединения, FIW_WAIT-ы, которые могут
SEL> мешать выполнить bind() сразу после повторного создания сокета. В чём
SEL> проблема у разработчиков реализаций TCP? Почему они усложняют жизнь
SEL> пользователям?

А в чём именно усложнение жизни _пользователям_? Я не вижу им
проблемы. Вот если возникает что-то в духе "более 100500 соединений
между фронтэндом и бэкэндом" - тогда да, его ограничения становятся
заметными. Hо у какого _пользователя_ такие проблемы?

SEL> P.S.
SEL> SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
SEL> почему его недостаточно?

Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?


--netch--
Pavel Gulchouck
2014-02-04 04:46:58 UTC
Permalink
Hi Valentin!

02 Feb 14, Valentin Nechayev ==> "Serguei E. Leontiev":

SEL>> Интересно, почему принято ограничено реализовывать SO_REUSEADDR?
SEL>> По тексту стандарта, вроде как не подразумевается, что кроме сравнения
SEL>> локальных адресов, что-то ещё учитывается.
SEL>> The SO_REUSEADDR option indicates that the rules used in validating
SEL>> addresses supplied in a bind() should allow reuse of local addresses.
SEL>> Operation of this option is protocol-specific. The default value for
SEL>> SO_REUSEADDR is off; that is, reuse of local addresses is not permitted.
SEL>> Тем не менее, общепринято накладывать дополнительные ограничения такие,
SEL>> как
SEL>> состояние TIME_WAIT для полностью идентичных пар адрес/порт (и/или
SEL>> запрет
SEL>> перехода в сотояние TIME_WAIT), но при неполной идентичности этого
SEL>> ограничения нет.

VN> Вы говорите про TCP, так? А теперь вопрос. Пришёл пакет от некоей пары
VN> (хост, порт) на некую локальную пару (хост, порт). К какому именно из
VN> соединений в Вашем варианте он будет относиться, и почему собственно?
VN> Как надёжно это разделить, с учётом того, что на IP формально
VN> возможно, например, что пакетик по дороге раздвоился, далее полежал у
VN> какого-то медленного раутера минуту-другую в очереди и наконец был
VN> отдан туда, где его уже отчаялись ждать?

VN> Все эти меры с TIME_WAIT ровно потому, что есть такие непредсказуемые
VN> задержки и повторения. При неполной идентичности (отличается хотя бы
VN> один параметр) пакет будет детектирован к другому соединению, и
VN> проблем нет.

VN> SO_REUSEADDR в нынешнем виде собственно и нужен для того, чтобы не
VN> было по умолчанию ситуации, когда один фиксированный локальный адрес
VN> прицепился к другому фиксированному локальному адресу - есть любители
VN> таких настроек. Тот, кто ставит эту опцию, должен отдавать себе отчёт,
VN> что с этого момента это его проблема - не допускать конфликтов полных
VN> идентификаторов соединения.

Если я правильно понял описание опции SO_REUSEADDR, основное её предназначение
- разрешить бинд как на 0.0.0.0, так и на конкретный интерфейс. Если один демон
прибиндился на 1.2.3.4:567, то второй может прибиндиться на 0.0.0.0:567 и
принимать соединения на этот порт по всем остальным интерфейсам, кроме 1.2.3.4.
И наоборот - если первый демон прибинлился на 0.0.0.0:567 без O_REUSEADDR, то
никто другой уже не сможет прибиндиться на 1.2.3.4:567, что бы он ни указывал.
Это понятно и логично, и нужно редко.

Но вот есть второе предназначение той же самой опции: разрешать bind(), когда
есть соединения в состоянии TIME_WAIT. А вот это уже нужно часто. И мне
непонятно, почему это та же самая опция, а не другая. Почему, если я хочу
биндиться на 0.0.0.0 при наличии соединений в TIME_WAIT, я обязан разрешать
кому-то другому после меня биндиться на тот же порт конкретного интерфейса?

SEL>> P.S.
SEL>> SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
SEL>> почему его недостаточно?

VN> Вы можете сформулировать целевые условия и ресурсные требования, под
VN> которые требуется что-то большее?

Если я правильно понял описание SO_REUSEPORT, его применение может быть
удобным.
Прибиндили N демонов к одному порту - и каждый из них получает 1/N входящих
соединений, балансировка на уровне ядра. Если один из них упал или по другой
причине рестартует, на время рестарта соединения не теряются, их успешно
принимают оставшиеся N-1 демонов. А в качестве защиты от перехвата - повторно
прибиндиться можно только с тем же uid, с которым прибиндился первый.
Я не вижу недостатков, мне такое нравится.

Lucky carrier,
Паша
aka ***@gul.kiev.ua
Serguei E. Leontiev
2014-02-05 10:52:32 UTC
Permalink
Павел, привет,
Post by Pavel Gulchouck
Post by Valentin Nechayev
Post by Serguei E. Leontiev
SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?
Если я правильно понял описание SO_REUSEPORT, его применение может быть
удобным.
Прибиндили N демонов к одному порту - и каждый из них получает 1/N входящих
соединений, балансировка на уровне ядра.
Хм. Откуда известно, что 1/N? И хорошо ли это?
Post by Pavel Gulchouck
Если один из них упал или по другой
причине рестартует, на время рестарта соединения не теряются, их успешно
принимают оставшиеся N-1 демонов.
Hаверное это неплохо. Hо при других способах построения серверов можно
обеспечить то же самое.
Post by Pavel Gulchouck
А в качестве защиты от перехвата - повторно
прибиндиться можно только с тем же uid, с которым прибиндился первый.
Подозреваю, что в существенной доле случаев uid=0. Сдаётся мне, что даже
те, кто делает seteuid() и/или chroot(), делаю это после bind(). Так что,
сия защита практически бесполезна.
Post by Pavel Gulchouck
Я не вижу недостатков, мне такое нравится.
Hо тебе нравится, комплектное повторное использование локального адреса,
т.е. для современных ОС это SO_REUSEPORT + SO_REUSEADDR. Hаверняка ты в
курсе особенностей такого использования и они не станут для тебя
неожиданностью (Валентин описывал их в общем виде, IMHO может доходить до
случайного дублирования информации от клиента).

Hо и частичное повторное использование локального адреса (для современных
ОС SO_REUSEADDR), как мне кажется, возможно я не прав, имеет все те же
негативные/опасные особенности с одной стороны. А с другой стороны, вроде
как не реашает полностью проблему закрытия/открытия сокета.
--
Успехов, Сергей Леонтьев, <http://www.cryptopro.ru> (NewsTap)
Pavel Gulchouck
2014-02-05 09:07:24 UTC
Permalink
Hi Serguei!
Post by Pavel Gulchouck
Post by Valentin Nechayev
Post by Serguei E. Leontiev
SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?
Если я правильно понял описание SO_REUSEPORT, его применение может быть
удобным.
Прибиндили N демонов к одному порту - и каждый из них получает 1/N входящих
соединений, балансировка на уровне ядра.
SEL> Хм. Откуда известно, что 1/N? И хорошо ли это?

Я это взял вот отсюда:
http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t

Linux tries to optimize distribution so that, for example, multiple instances
of a simple server process can easily use SO_REUSEPORT sockets to achieve a
kind of simple load balancing and that absolutely for free as the kernel is
doing "all the hard work" for them.

Не первоисточник, конечно, но глубже проверять поленился.
Хорошо это или нет? Не знаю, но я не вижу в этом ничего плохого. А что-то
хорошее вижу. :)
Post by Pavel Gulchouck
Если один из них упал или по другой
причине рестартует, на время рестарта соединения не теряются, их успешно
принимают оставшиеся N-1 демонов.
SEL> Hаверное это неплохо. Hо при других способах построения серверов можно
SEL> обеспечить то же самое.

При каких?
Кластер, reverse proxy? Это немного не то, всё-таки.
К тому же, тут kernel level.
Post by Pavel Gulchouck
А в качестве защиты от перехвата - повторно
прибиндиться можно только с тем же uid, с которым прибиндился первый.
SEL> Подозреваю, что в существенной доле случаев uid=0. Сдаётся мне, что даже
SEL> те, кто делает seteuid() и/или chroot(), делаю это после bind(). Так что,
SEL> сия защита практически бесполезна.

От uid=0 защита в любом случае бесполезна.
Так что рассматриваем только случаи портов выше тысячи и нерутовые сервисы.
Post by Pavel Gulchouck
Я не вижу недостатков, мне такое нравится.
SEL> Hо тебе нравится, комплектное повторное использование локального адреса,
SEL> т.е. для современных ОС это SO_REUSEPORT + SO_REUSEADDR. Hаверняка ты в
SEL> курсе особенностей такого использования и они не станут для тебя
SEL> неожиданностью (Валентин описывал их в общем виде, IMHO может доходить
SEL> до
SEL> случайного дублирования информации от клиента).

SEL> Hо и частичное повторное использование локального адреса (для современных
SEL> ОС SO_REUSEADDR), как мне кажется, возможно я не прав, имеет все те же
SEL> негативные/опасные особенности с одной стороны. А с другой стороны, вроде
SEL> как не реашает полностью проблему закрытия/открытия сокета.

Если честно, я не понял недостатки SO_REUSEPORT и SO_REUSEADDR (кроме очевидных
и документированных), как и причины, почему они не решают проблему
закрытия/открытия сокета. Наверное, потом перечитаю ещё раз внимательнее. Пока
у меня сложилось впечатление, что это недостатки реализации, а не концепции.

Lucky carrier,
Паша
aka ***@gul.kiev.ua
Valentin Nechayev
2014-02-05 11:40:09 UTC
Permalink
PG> Hо вот есть второе предназначение той же самой опции: разрешать bind(), когда
PG> есть соединения в состоянии TIME_WAIT. А вот это уже нужно часто. И мне
PG> непонятно, почему это та же самая опция, а не другая. Почему, если я хочу
PG> биндиться на 0.0.0.0 при наличии соединений в TIME_WAIT, я обязан разрешать
PG> кому-то другому после меня биндиться на тот же порт конкретного интерфейса?

А по всё той же логике о целостности TCP связи и защите от
from socket import *
s = socket(AF_INET, SOCK_STREAM); s.setsockopt(SOL_SOCKET, SO_REUSEADDR,1); s.bind(("", 9900)); s1 = s
s = socket(AF_INET, SOCK_STREAM); s.setsockopt(SOL_SOCKET, SO_REUSEADDR,1); s.bind(("", 9900)); s2 = s
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/socket.py", line 224, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 48] Address already in use

Тут ещё непонятно, что мы будем делать с обоими сокетами, поэтому второй
на тот же порт в неопределённом состоянии оно не разрешает. (Хотя, если
второй адрес заменить на определённый, оно разрешает.)
s1.connect(('127.0.0.1',22))
s = socket(AF_INET, SOCK_STREAM); s.setsockopt(SOL_SOCKET, SO_REUSEADDR,1); s.bind(("", 9900)); s2 = s
Уже получилось. Конфликта нет. А вот если мы попробуем со второго
s2.connect(('127.0.0.1',22))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/socket.py", line 224, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 48] Address already in use

Грубо говоря, эта опция означает "да, разрешить мне выстрелить
себе в ногу", но объединяет как минимум два разных случая. Первый
(слушание и общего, и частного адреса) тебе понятен, мне - нет.
Второй - понятнее мне. Чтобы его понять в деталях, надо учесть
некоторые хитрые свойства TCP. Hапример, что в нём возможны
встречные коннекты двух сокетов, ни один из которых не сделал
listen(), но вместо этого даётся connect() на второй адрес.

Кстати, мне удалось, вероятно, воспроизвести твой начальный случай
(на FreeBSD).
Меньше чем через минуту после предыдущих экспериментов попробовал
s = socket(AF_INET, SOCK_STREAM); s.setsockopt(SOL_SOCKET, SO_REUSEADDR,1); s.bind(("", 9900)); s1 = s
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/socket.py", line 224, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 48] Address already in use

при этом было видно:

$ netstat -an | fgrep 9900
tcp4 0 0 127.0.0.1.9900 127.0.0.1.22 TIME_WAIT

Тестовая программа для устойчивого воспроизведения:

===
#!/usr/bin/env python

import sys, traceback

from socket import *
s1 = socket(AF_INET, SOCK_STREAM)
s1.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
try:
s1.bind(('127.0.0.1', 9900))
s1.connect(('127.0.0.1', 22))
s1.close()
except Exception:
print 'Exception'
traceback.print_exc(file = sys.stdout)

s1 = socket(AF_INET, SOCK_STREAM)
s1.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
try:
s1.bind(('', 9900))
s1.connect(('127.0.0.1', 22))
s1.close()
except Exception:
print 'Exception'
traceback.print_exc(file = sys.stdout)
===

если в ней добавить SO_REUSEPORT к SO_REUSEADDR, то отказ идёт только
на втором connect(). В Linux, не нужен SO_REUSEPORT, отказ идёт только
на втором connect(), и сокета в TIME_WAIT не видно (интересно, почему).

В общем, тут ещё есть что внимательно почитать.

PG> Если я правильно понял описание SO_REUSEPORT, его применение может быть
PG> удобным.
PG> Прибиндили N демонов к одному порту - и каждый из них получает 1/N входящих
PG> соединений, балансировка на уровне ядра. Если один из них упал или по другой
PG> причине рестартует, на время рестарта соединения не теряются, их успешно
PG> принимают оставшиеся N-1 демонов. А в качестве защиты от перехвата - повторно
PG> прибиндиться можно только с тем же uid, с которым прибиндился первый.
PG> Я не вижу недостатков, мне такое нравится.

Только учти, что согласно http://man7.org/tlpi/api_changes/ её нет до 3.9.


--netch--
Pavel Gulchouck
2014-02-05 18:11:20 UTC
Permalink
Hi Valentin!

05 Feb 14, Valentin Nechayev ==> Pavel Gulchouck:

PG>> Hо вот есть второе предназначение той же самой опции: разрешать bind(),
PG>> когда
PG>> есть соединения в состоянии TIME_WAIT. А вот это уже нужно часто. И мне
PG>> непонятно, почему это та же самая опция, а не другая. Почему, если я хочу
PG>> биндиться на 0.0.0.0 при наличии соединений в TIME_WAIT, я обязан
PG>> разрешать
PG>> кому-то другому после меня биндиться на тот же порт конкретного
PG>> интерфейса?

VN> А по всё той же логике о целостности TCP связи и защите от
VN> дурных коллизий, которые невозможно предусмотреть.
VN> Смотри:
[...]

Иными словами, если коннектиться прибинденным сокетом с явно указанным
локальным портом, возможны коллизии - так?
Если дело именно в этом, то, на мой взгляд, ошибку должна возвращать та
функция, результат которой приводит к коллизии, в данном случае это connect().
Как оно и происходит в твоём примере.

Но чем соединение в TIME_WAIT мешает выполнить bind()? Может, там и не будет
никакого connect(), а уж тем более - коннекта именно на тот адрес/порт, с
которым висит соединение.

[...]
s2.connect(('127.0.0.1',22))
VN> Traceback (most recent call last):
VN> File "<stdin>", line 1, in <module>
VN> File "/usr/local/lib/python2.7/socket.py", line 224, in meth
VN> return getattr(self._sock,name)(*args)
VN> socket.error: [Errno 48] Address already in use

VN> Грубо говоря, эта опция означает "да, разрешить мне выстрелить
VN> себе в ногу", но объединяет как минимум два разных случая. Первый
VN> (слушание и общего, и частного адреса) тебе понятен, мне - нет.

Поясню, как я его понимаю для слушающих сокетов (bind() для исходящих опустим).

Можно слушать один конкретный интерфейс, а можно все сразу. Причём, "слушать
всё" - не то же самое, что пробежаться по списку интерфейсов и прибиндиться на
каждый из них, потому что интерфейсы могут появляться, и если пробежались по
списку, то не будем слушать на новых, а если сказали "слушать везде", то будем
и на тех, которые будут потом созданы.

В таком случае может возникнуть ситуация, когда один из интерфейсов кто-то уже
слушает, а мы хотим слушать все остальные. Опять же, пробежаться по списку и
прибиндиться на все свободные - не очень хорошо. Вот и получается, что мы можем
сказать "слушать всё", и будем получать соединения от всех интерфейсов, кроме
занятого кем-то другим, включая те, которые будут созданы потом.

Ну а раз возможна ситуация, когда один слушает один конкретный интерфейс, а
второй все остальные, заодно разрешили и менее очевидную штуку: биндиться на
конкретный интерфейс уже после того, как кто-то начал слушать всё. Наверное,
чтобы в случае легальной ситуации типа "один слушает лупбэк, а второй все
внешние интерфейсы" можно было не зависеть от того, кто запустился первым. И
чтобы слушающий loopback в такой ситуации мог безболезненно рестартовать.

Но чтобы предотвратить перехват входящих, прибиндившись на конкретный
интерфейс, когда системный демон слушает всё, сделали SO_REUSEADDR. Если
системный демон прибиндился без этой опции, никто ни на какой конкретный
интерфейс прибиндиться не сможет.

А при чём тут TIME_WAIT, я по-прежнему не понимаю.

VN> Второй - понятнее мне. Чтобы его понять в деталях, надо учесть
VN> некоторые хитрые свойства TCP. Hапример, что в нём возможны
VN> встречные коннекты двух сокетов, ни один из которых не сделал
VN> listen(), но вместо этого даётся connect() на второй адрес.

На TCP, встречный коннект двух сокетов без listen()? 8-()
Мне казалось, что если connect(), то отправляем syn req и ждём syn ack. А если
listen(), то наоборот.
Разве сокет, отправивший syn req, будет реагировать на полученный syn req даже
при полном совпадении всех адресов/портов?
Может, всё-таки, UDP?

Напоминает встречный звонок двух телефонов друг другу - там в такой ситуации
иногда возможен коннект, как ни странно. Хотя чаще бывает обоюдное "занято".

VN> Кстати, мне удалось, вероятно, воспроизвести твой начальный случай
VN> (на FreeBSD).
VN> Меньше чем через минуту после предыдущих экспериментов попробовал
s = socket(AF_INET, SOCK_STREAM); s.setsockopt(SOL_SOCKET,
SO_REUSEADDR,1); s.bind(("", 9900)); s1 = s
VN> Traceback (most recent call last):
VN> File "<stdin>", line 1, in <module>
VN> File "/usr/local/lib/python2.7/socket.py", line 224, in meth
VN> return getattr(self._sock,name)(*args)
VN> socket.error: [Errno 48] Address already in use

VN> при этом было видно:

VN> $ netstat -an | fgrep 9900
VN> tcp4 0 0 127.0.0.1.9900 127.0.0.1.22
VN> TIME_WAIT

Вот какого хрена, спрашивается? Да ещё и при SO_REUSEADDR?
Я ведь могу сделать два коннекта на разные хосты от одного и того же локального
127.0.0.1:9900?

VN> Тестовая программа для устойчивого воспроизведения:
[...]

В этой тестовой программе фейлится connect(), это нормально.
Ненормально (для меня), когда фейлится bind().

Lucky carrier,
Паша
aka ***@gul.kiev.ua
Valentin Nechayev
2014-02-11 09:46:10 UTC
Permalink
PG> Иными словами, если коннектиться прибинденным сокетом с явно указанным
PG> локальным портом, возможны коллизии - так?

Да.

PG> Если дело именно в этом, то, на мой взгляд, ошибку должна возвращать та
PG> функция, результат которой приводит к коллизии, в данном случае это connect().
PG> Как оно и происходит в твоём примере.

О. То есть ты уже в принципе не против общей идеи?
Если так, то теперь айда в гости к дьяволу, то есть копаться в
мелочах. А вот тут они начинают быть очень интересными.

PG> Hо чем соединение в TIME_WAIT мешает выполнить bind()? Может, там и не будет
PG> никакого connect(), а уж тем более - коннекта именно на тот адрес/порт, с
PG> которым висит соединение.

То, что какой-то коннект должен быть - кагбэээ очевидно. Иначе накой
тот TCP сокет вообще сдался. А вот будет ли он ровно с той же ремотой
- вот это уже интересно. Рассматриваем Linux?
Точные алгоритмы требуют времени для анализа, если пойти на это, хочу
оформить отдельным письмом.

PG> Поясню, как я его понимаю для слушающих сокетов (bind() для исходящих опустим).
[...]

PG> А при чём тут TIME_WAIT, я по-прежнему не понимаю.

Ты много потратил на роспись одного частного случая, который слушание
одного и того же порта с разными адресами. Hо опция SO_REUSEADDR
объединяет, как видно, как минимум два принципиально разных
разрешения, и сделано это было, я думаю, потому, что механизм
реализации оказался идентичным.
Возможно, это недальновидность и надо было делать эти разрешения по
отдельности (одно - для коллизии между слушающими сокетами, другое -
для коллизии между соединениями). Это в любом случае далеко не
единственный потенциальный или реальный недостаток BSD sockets как
интерфейса.

PG> Hа TCP, встречный коннект двух сокетов без listen()? 8-()

Да. Hикакого запрета подобного поведения нет в модели взаимодействия.
Hо тут интересная картина получается. Раньше на тестах у меня это
работало (район FreeBSD 5, наверно). Серьёзной пользы не было (это
полезно разве что для специфических видов NAT), но было удобно для
особых случаев - когда договорились по другому каналу о портах и не
желают принимать левых соединений.
А сейчас я это воспроизвести не могу. Сокет, который получил адрес, но
ни одно из listen/connect, не принимает входящие, хотя в диаграмме
состояний странные вещи - видно SYN_RECV. Это одинаково на Linux и
FreeBSD. Похоже, что эту возможность случайно сломали (и, наверно,
чинить не будут).

PG> Мне казалось, что если connect(), то отправляем syn req и ждём syn ack. А если
PG> listen(), то наоборот.

Скури внимательно RFC793 - он такое позволяет. Hо он позволяет многое
из того, что в BSD sockets не поддерживается или поддерживается в
очень особых условиях (например, отправка данных одновременно с SYN).
TLI, для сравнения, позволяет такое. Только где сейчас найти тот TLI.

PG> Вот какого хрена, спрашивается? Да ещё и при SO_REUSEADDR?

Я не знаю. Боюсь, это баг. Hо там логика поиска коллизии в последних
версиях (причём Linux и FreeBSD почти одинаково) усложнилась до полной
непонимаемости, из-за хэширования таблиц соединений и необходимости
мультипроцессорной работы, потому что поддержка тупого скоростного
рубилова в варианте "хоть миллионы соединений одновременно" стала
важнее тонких возможностей.

PG> Я ведь могу сделать два коннекта на разные хосты от одного и того же локального
PG> 127.0.0.1:9900?

Можешь. Теперь осталось доказать это авторам стеков.


--netch--

Serguei E. Leontiev
2014-02-04 11:22:33 UTC
Permalink
Валентин, привет,
Post by Valentin Nechayev
А в чём именно усложнение жизни _пользователям_? Я не вижу им
проблемы. Вот если возникает что-то в духе "более 100500 соединений
между фронтэндом и бэкэндом" - тогда да, его ограничения становятся
заметными. Hо у какого _пользователя_ такие проблемы?
Боюсь, в современном мире, уже у WEB-морд могут возникать такие проблемы.
Post by Valentin Nechayev
Post by Serguei E. Leontiev
P.S.
SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?
Я могу ошибаться, но мне кажется достаточно легко построить простейший
пример с созданиями/удалениями сокетов, когда использование SO_REUSEADDR
окажется недостаточным для исключения EADDRINUSE, т.к. с высокой
вероятностью TCP соединения будут оказываться так же полуоткрытыми или
находится в FIN_WAIT-ах то же.

А вопрос, был зачем так сделали, т.к. SO_REUSEADDR по POSIX лучше
соответствует SO_REUSEADDR+SO_REUSEPORT современных ОС.

Есть ещё вопрос в пространство, а зачем bind() для сокета вообще выдаёт
EADDRINUSE в случае таймаутов (FIN_WAIT-ов, TIME_WAIT, полуоткрых
соединений)? Мог бы наверное сам подождать, момента, когда можно будет
безопасно принимать соединения? А ошибку выдавать, только при наличии
других реальных слушателей?
--
Успехов, Сергей Леонтьев, <http://www.cryptopro.ru> (NewsTap)
Serguei E. Leontiev
2014-02-04 11:22:33 UTC
Permalink
Валентин, привет,
Post by Valentin Nechayev
А в чём именно усложнение жизни _пользователям_? Я не вижу им
проблемы. Вот если возникает что-то в духе "более 100500 соединений
между фронтэндом и бэкэндом" - тогда да, его ограничения становятся
заметными. Hо у какого _пользователя_ такие проблемы?
Боюсь, в современном мире, уже у WEB-морд могут возникать такие проблемы.
Post by Valentin Nechayev
Post by Serguei E. Leontiev
P.S.
SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?
Я могу ошибаться, но мне кажется достаточно легко построить простейший
пример с созданиями/удалениями сокетов, когда использование SO_REUSEADDR
окажется недостаточным для исключения EADDRINUSE, т.к. с высокой
вероятностью TCP соединения будут оказываться так же полуоткрытыми или
находится в FIN_WAIT-ах то же.

А вопрос, был зачем так сделали, т.к. SO_REUSEADDR по POSIX лучше
соответствует SO_REUSEADDR+SO_REUSEPORT современных ОС.

Есть ещё вопрос в пространство, а зачем bind() для сокета вообще выдаёт
EADDRINUSE в случае таймаутов (FIN_WAIT-ов, TIME_WAIT, полуоткрых
соединений)? Мог бы наверное сам подождать, момента, когда можно будет
безопасно принимать соединения? А ошибку выдавать, только при наличии
других реальных слушателей?
--
Успехов, Сергей Леонтьев, <http://www.cryptopro.ru> (NewsTap)
Serguei E. Leontiev
2014-02-04 11:22:34 UTC
Permalink
Валентин, привет,
Post by Valentin Nechayev
А в чём именно усложнение жизни _пользователям_? Я не вижу им
проблемы. Вот если возникает что-то в духе "более 100500 соединений
между фронтэндом и бэкэндом" - тогда да, его ограничения становятся
заметными. Hо у какого _пользователя_ такие проблемы?
Боюсь, в современном мире, уже у WEB-морд могут возникать такие проблемы.
Post by Valentin Nechayev
Post by Serguei E. Leontiev
P.S.
SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?
Я могу ошибаться, но мне кажется достаточно легко построить простейший
пример с созданиями/удалениями сокетов, когда использование SO_REUSEADDR
окажется недостаточным для исключения EADDRINUSE, т.к. с высокой
вероятностью TCP соединения будут оказываться так же полуоткрытыми или
находится в FIN_WAIT-ах то же.

А вопрос, был зачем так сделали, т.к. SO_REUSEADDR по POSIX лучше
соответствует SO_REUSEADDR+SO_REUSEPORT современных ОС.

Есть ещё вопрос в пространство, а зачем bind() для сокета вообще выдаёт
EADDRINUSE в случае таймаутов (FIN_WAIT-ов, TIME_WAIT, полуоткрых
соединений)? Мог бы наверное сам подождать, момента, когда можно будет
безопасно принимать соединения? А ошибку выдавать, только при наличии
других реальных слушателей?
--
Успехов, Сергей Леонтьев, <http://www.cryptopro.ru> (NewsTap)
Serguei E. Leontiev
2014-02-04 14:30:11 UTC
Permalink
Валентин, привет,
Post by Valentin Nechayev
А в чём именно усложнение жизни _пользователям_? Я не вижу им
проблемы. Вот если возникает что-то в духе "более 100500 соединений
между фронтэндом и бэкэндом" - тогда да, его ограничения становятся
заметными. Hо у какого _пользователя_ такие проблемы?
Боюсь, в современном мире, уже у WEB-морд могут возникать такие проблемы.
Post by Valentin Nechayev
Post by Serguei E. Leontiev
P.S.
SO_REUSEADDR - изначальное зло для TCP, он может вызывать проблемы. Hо
почему его недостаточно?
Вы можете сформулировать целевые условия и ресурсные требования, под
которые требуется что-то большее?
Я могу ошибаться, но мне кажется достаточно легко построить простейший
пример с созданиями/удалениями сокетов, когда использование SO_REUSEADDR
окажется недостаточным для исключения EADDRINUSE, т.к. с высокой
вероятностью TCP соединения будут оказываться так же полуоткрытыми или
находится в FIN_WAIT-ах то же.

А вопрос, был зачем так сделали, т.к. SO_REUSEADDR по POSIX лучше
соответствует SO_REUSEADDR+SO_REUSEPORT современных ОС.

Есть ещё вопрос в пространство, а зачем bind() для сокета вообще выдаёт
EADDRINUSE в случае таймаутов (FIN_WAIT-ов, TIME_WAIT, полуоткрых
соединений)? Мог бы наверное сам подождать, момента, когда можно будет
безопасно принимать соединения? А ошибку выдавать, только при наличии
других реальных слушателей?
--
Успехов, Сергей Леонтьев, <http://www.cryptopro.ru> (NewsTap)
Loading...