Использование getaddrinfo () с AI_PASSIVE - PullRequest
6 голосов
/ 13 ноября 2011

Функция 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() вольтер для всех этих ответов, или я должен
  • просто выберите первый, который действительно работает?

Пример из manpage предлагает мне взять только первый и быть довольным им, если он безошибочен, но тогда я получаю соединение только через 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 ]

3 голосов
/ 13 ноября 2011

Ваш getaddrinfo по какой-то причине возвращает неверный результат.Он должен вернуть сокет IPv6 первым.Единственное, о чем я могу подумать, это если ваша ОС обнаружит, что ваша система имеет низкий IPO 6 (6to4 или Teredo), и избегает их, то есть в этом случае ошибочно.Изменить: Только что заметил, что мой собственный компьютер делает то же самое, я использую 6to4.

Однако, вы можете либо прослушать их обоих, либо использовать AF_INET6 вместо AF_UNSPEC.Затем вы можете выполнить команду setockopt, чтобы отключить IPV6_V6ONLY.

. Здесь getaddrinfo делает все возможное и возвращает все применимые результаты (хотя, как я уже говорил, в неправильном порядке).И один, и два сокета прослушивания являются допустимыми подходами, в зависимости от вашего приложения.

0 голосов
/ 14 ноября 2011

JFTR: Похоже, что программа, указанная в man-странице, неверна.

Существует два возможных подхода для прослушивания обоих типов IP:

  1. Создайте только сокет 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) - есть два разных стека протоколов
  2. работать с двумя розетками и включить флаг 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() или неблокирующий ввод / вывод)

    Плюсы:

    • использует (почти) независимую от протокола обработку с getaddrinfo()
    • работает и под XP

    Минусы:

    • сложный в обращении
...