Использование getaddrinfo() с AI_PASSIVE
getaddrinfo()
Функция не только позволяет клиентским программам эффективно находить правильные данные для создания сокета для данного хоста, но также позволяет серверам связываться с правильным сокетом - теоретически.
Я только что узнал об этом и начал играть с ним через Python:
from socket import *
for i in getaddrinfo(None, 22, AF_UNSPEC, SOCK_STREAM, IPPROTO_IP, AI_PASSIVE): i
доходность
(2, 1, 6, '', ('0.0.0.0', 22))
(10, 1, 6, '', ('::', 22, 0, 0))
что заставляет меня задуматься, если что-то не так.
Что именно я должен делать с этими ответами? Нужно ли мне
- делать
listen()
В ответ на все эти ответы, или я должен - просто выбрать первый, который действительно работает?
Пример на странице руководства предлагает мне взять только первый и быть довольным им, если он безошибочен, но тогда я получаю соединение только через IPv4 в моем примере.
Но если я попробую все из них, мне придется позаботиться о 2-х серверных сокетах, что не нужно из-за того, что серверные сокеты IPv6 также прослушивают IPv4, если выполняются определенные условия (ОС, флаги сокетов и т. Д.).
Где я не так думаю?
РЕДАКТИРОВАТЬ: Очевидно, я не думаю, что неправильно, но мой компьютер делает неправильные вещи. Я использую по умолчанию /etc/gai.conf
поставляется с OpenSUSE. Было бы хорошо, если бы кто-нибудь мог указать мне правильное направление.
РЕДАКТИРОВАТЬ 2: В данном случае, strace
после чтения делает следующие внутренние вызовы /etc/gai.conf
(теперь с портом 54321, так как я думал, что использование порта 22 может иметь плохое влияние, чего не произошло):
socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET6, sin6_port=htons(54321), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(38289), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(54321), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(60866), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
close(3) = 0
Очевидно, что решение должно быть принято в соответствии с результатами getsockname()
звонки...
Кстати: https://bugs.launchpad.net/ubuntu/+source/eglibc/+bug/673708 и другие упомянутые там сообщения об ошибках подтверждают мои наблюдения. Некоторые люди там утверждают, что новое поведение является правильным, поэтому я, очевидно, застрял в использовании AF_INET6
...:-(
2 ответа
Ваш getaddrinfo
возвращает неправильный результат по какой-то причине. Он должен вернуть сокет IPv6 первым. Единственное, о чем я могу думать, - это если ваша ОС обнаружит, что ваша система имеет низкий IPO 6 (6to4 или Teredo), и избегает их, IMO ошибочно, в таком случае. Изменить: Просто заметил, что мой собственный компьютер делает то же самое, я использую 6to4.
Однако вы можете либо послушать их обоих, либо использовать AF_INET6
вместо AF_UNSPEC
, Тогда вы можете сделать setsockopt, чтобы отключить IPV6_V6ONLY
,
getaddrinfo делает здесь разумные вещи и возвращает все применимые результаты (хотя, как я уже говорил, в неправильном порядке). И один, и два прослушивающих сокета являются допустимыми подходами, в зависимости от вашего приложения.
JFTR: Теперь кажется, что программа, указанная в man-странице, неверна.
Существует два возможных подхода к прослушиванию обоих типов IP:
Создайте только сокет IPv6 и отключите флаг v6 only:
from socket import * s = socket(AF_INET6, SOCK_STREAM) s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0) s.bind(...)
соответственно
from socket import * ai = getaddrinfo(None, ..., AF_INET6, SOCK_STREAM, 0, AI_PASSIVE)[0] s = socket(ai[0], ai[1], ai[2]) s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0) s.bind(ai[4])
Плюсы:
- проще в обращении
Минусы:
- не работает под XP (AFAIK) - есть два разных стека протоколов
работать с двумя сокетами и включить флаг v6only:
from socket import * aii = getaddrinfo(None, ..., AF_UNSPEC, SOCK_STREAM, 0, AI_PASSIVE) sl = [] for ai in aii: s = socket(ai[0], ai[1], ai[2]) if ai[0] == AF_INET6: s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1) s.bind(ai[4]) sl.append(s)
и обрабатывать все розетки в
sl
в принимающей петле (используйтеselect()
или неблокирующий IO для этого)Плюсы:
- использует (почти) протокол независимую обработку с
getaddrinfo()
- работает и под XP
Минусы:
- сложный в обращении
- использует (почти) протокол независимую обработку с