Usage of getaddrinfo() with AI_PASSIVE

只谈情不闲聊 提交于 2019-12-19 07:24:49

问题


The getaddrinfo() function not only allows for client programs to efficiently find the correct data for creating a socket to a given host, it also allows for servers to bind to the correct socket - in theory.

I just learned about that and started to play around with it via Python:

from socket import *
for i in getaddrinfo(None, 22, AF_UNSPEC, SOCK_STREAM, IPPROTO_IP, AI_PASSIVE): i

yields

(2, 1, 6, '', ('0.0.0.0', 22))
(10, 1, 6, '', ('::', 22, 0, 0))

what makes me wonder about if there is something wrong.

What exactly am I supposed to do with these answers? Should I

  • make a listen()ing socket of all of these answers, or should I
  • just pick the first one which really works?

The example in the manpage suggests me to only take the first one and be happy with it if it is error-free, but then I only get a connection via IPv4 n my example.

But if I try all of them, I have to worry with 2 server sockets, which is unnecessary due to the fact that IPv6 server sockets also listen to IPv4 if certain conditions are met (OS, socket flags etc.).

Where am I thinking wrong?


EDIT: Obviously, I'm not thinking wrong, but my PC does the wrong thing. I use the default /etc/gai.conf shipped with OpenSUSE. It would be nice if anyone could point me towards the right direction.

EDIT 2: In the given case, strace gives the following calls made internally after reading /etc/gai.conf (now with port 54321, as I thought that using port 22 might have some bad influence, which was not the case):

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

Obviously, the decision is intended to take place according to the results of the getsockname() calls...

BTW: https://bugs.launchpad.net/ubuntu/+source/eglibc/+bug/673708 and the other bug reports mentionned there confirm my observations. Several people there claim that the new behaviour is correct, so I'm obviously stuck to using AF_INET6... :-(


回答1:


Your getaddrinfo is returning the wrong result for some reason. It's supposed to return the IPv6 socket first. The only thing I can think of is if your OS detects that your system has a low prio IPv6 (6to4 or Teredo) and avoids them, IMO wrongly so in that case. Edit: Just noticed my own computer does the same thing, I use 6to4.

However, you can either listen to both of them, or use AF_INET6 instead of AF_UNSPEC. Then you can do setsockopt to disable IPV6_V6ONLY.

getaddrinfo does the reasonable thing here and returns all applicable results (though in the wrong order, as I mentioned). Both one and two listen sockets are valid approaches, depending on your application.




回答2:


JFTR: It seems now that the program given in the manpage is wrong.

There are two possible approaches for listening to both IP types:

  1. Create only a IPv6 socket and switch off the v6 only flag:

    from socket import *
    s = socket(AF_INET6, SOCK_STREAM)
    s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
    s.bind(...)
    

    resp.

    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])
    

    Pros:

    • simpler to handle

    Cons:

    • doesn't work under XP (AFAIK) - there are two different protocol stacks
  2. work with two sockets and switch on the v6only flag:

    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)
    

    and handle all sockets in sl in accepting loop (use select() or nonblocking IO to do so)

    Pros:

    • uses a (nearly) protocol independent handling with getaddrinfo()
    • works under XP as well

    Cons:

    • complicated to handle


来源:https://stackoverflow.com/questions/8113805/usage-of-getaddrinfo-with-ai-passive

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!