正在连接IPv4客户端到IPv6服务器:连接拒绝

Connecting IPv4 client to IPv6 server: connection refused

本文关键字:连接 服务器 IPv6 拒绝 客户端 IPv4      更新时间:2023-10-16

我正在尝试IPv6套接字,特别是"双栈";在Windows Vista和更高版本中提供的功能,显然在Unix默认情况下也是如此。我发现,当我将服务器绑定到特定的IP地址或本地机器的主机名解析时,我无法接受来自IPv4客户端的连接。然而,当我绑定INADDR_ANY时,我可以。

请考虑下面的代码为我的服务器。您可以看到,我遵循Microsoft的建议创建IPv6套接字,然后将IPV6_V6ONLY标志设置为零:

addrinfo* result, *pCurrent, hints;
memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // We intend to use the addrinfo in a call to connect().  (I know it is ignored if we specify a server to connect to...)
int nRet = getaddrinfo("powerhouse", "82", &hints, &result);
SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
int no = 0;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) != 0)
    return -1;
if (bind(sock, result->ai_addr, result->ai_addrlen) ==  SOCKET_ERROR)
    return -1;
if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
    return -1;
SOCKET sockClient = accept(sock, NULL, NULL);
                                 

这是我的客户端的代码。您可以看到我创建了一个IPv4套接字并尝试连接到我的服务器:

addrinfo* result, *pCurrent, hints;
memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo("powerhouse", "82", &hints, &result) != 0)
    return -1;
SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
int nRet = connect(sock, result->ai_addr, result->ai_addrlen);
                                  

我的connect调用的结果总是10061:connection refused.

如果我将服务器代码更改为绑定到::(或将NULL主机传递给getaddrinfo()(同样的事情)),并更改客户端代码以在getaddrinfo()调用中指定NULL主机,那么V4客户端可以正常连接。

谁能解释一下为什么?我没有读到任何东西,我们必须指定一个NULL主机(因此使用INADDR_ANY),如果我们想要双套接字行为。这不能是一个要求,因为我有一个多主主机,我想接受IPv4只在一些可用的ip ?

编辑15/05/2013:

这是相关的文档,它让我对为什么我的代码失败感到困惑:

来自IPv6 Winsock应用程序的双栈套接字

Windows Vista及以后版本提供了创建单个IPv6的能力套接字,可以处理IPv6和IPv4流量。例如TCP创建IPv6监听套接字,将其置于双栈模式,并且绑定端口5001。这个双栈套接字可以接受来自IPv6 TCP客户端连接到端口5001和IPv4 TCP客户端连接到端口5001."

"默认情况下,仅在Windows Vista及更高版本上创建的IPv6套接字在IPv6协议上运行。为了使IPv6套接字变成对于双栈套接字,setsockopt函数必须使用IPV6_V6ONLY套接字选项将此值设置为零已绑定IP地址。设置IPV6_V6ONLY套接字选项时到0时,可以使用为AF_INET6地址族创建的套接字发送和接收来自IPv6地址或IPv4地址的数据包映射的地址。(强调我的)"

IPv4和IPv6是两个独立的协议。一种协议的报文不能被另一种协议处理。这就是双栈概念存在的原因:你的系统同时运行IPv4和IPv6协议栈,同时拥有IPv4和IPv6地址,等等。

操作系统有一个技巧,你可以有一个IPv6套接字监听所有IPv4和IPv6地址。您仍然需要在主机上拥有两个地址族,并且只有在绑定到通配符地址时才有效。一旦你将套接字绑定到一个固定的地址,这个地址就不再工作了,它只会为你绑定到的地址工作。

所以如果你想监听所有可用的地址,那么将IPV6_V6ONLY设置为0并监听通配符地址即可。IPv4客户端将显示为使用以::ffff:开头的IPv6地址,最后32位包含IPv4地址。

当你想要绑定到特定的地址时,你需要将套接字绑定到你想要监听的每个地址。然后,您需要使用select(...)来监视这些套接字,并响应那些由于有人连接到它们而变得活跃的套接字。

此链接http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch12lev1sec2.html提供有关IPv4和IPv6连接的更多信息,

大多数双栈主机应该使用以下规则来处理监听套接字:

  • 一个正在监听的IPv4套接字只能接受来自IPv4客户端的连接。
  • 如果服务器有一个监听的IPv6套接字,它绑定了通配符地址,并且IPV6_V6ONLY套接字选项(章节7.8)没有设置,套接字可以接受来自IPv4客户端的连接或IPv6客户端。对于来自IPv4客户端的连接,服务器的连接的本地地址将是相应的ipv4映射IPv6地址。
  • 如果服务器有一个监听的IPv6套接字,该套接字绑定了一个IPv6地址,而不是ipv4映射的IPv6地址通配符地址,但设置了IPv6_V6ONLY套接字选项