【问题标题】:Use of IN6ADDR_SETV4MAPPED and dual stack sockets使用 IN6ADDR_SETV4MAPPED 和双堆栈套接字
【发布时间】:2013-05-21 16:56:37
【问题描述】:

这是Connecting IPv4 client to IPv6 server: connection refused 的延续。我正在尝试使用双栈套接字,并试图了解带有 IPV6_V6ONLY 的 setsockopt 的用途。在链接的问题上,我被告知“如果您还将服务器绑定到 IPv6 映射的 IPv4 地址,将 IPV6_V6ONLY 设置为 0 会很有用”。我在下面完成了这项工作,并希望我的服务器能够接受来自 IPv6 和 IPv4 客户端的连接。但令人震惊的是,当我使用 V4 和 V6 套接字运行客户端时,两者都无法连接!

有人可以告诉我我做错了什么,还是我完全误解了 IPv6 双栈功能?

服务器:

void ConvertToV4MappedAddressIfNeeded(PSOCKADDR pAddr)
{
// if v4 address, convert to v4 mapped v6 address
if (AF_INET == pAddr->sa_family)
{
    IN_ADDR In4addr;
    SCOPE_ID scope = INETADDR_SCOPE_ID(pAddr);
    USHORT port = INETADDR_PORT(pAddr);
    In4addr = *(IN_ADDR*)INETADDR_ADDRESS(pAddr);
    ZeroMemory(pAddr, sizeof(SOCKADDR_STORAGE));
    IN6ADDR_SETV4MAPPED(
        (PSOCKADDR_IN6)pAddr,
        &In4addr,
        scope,
        port
        );
    }
} 

addrinfo* result, hints;

memset(&hints, 0, sizeof hints); 
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

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;

ConvertToV4MappedAddressIfNeeded(result->ai_addr);

if (bind(sock, result->ai_addr, 28/*result->ai_addrlen*/) ==  SOCKET_ERROR)
    return -1;

if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
    return -1;

SOCKET sockClient = accept(sock, NULL, NULL);
printf("Got one!\n");

客户:

addrinfo* result, *pCurrent, hints;
char szIPAddress[INET6_ADDRSTRLEN];

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

const char* pszPort = "82";

if (getaddrinfo("powerhouse", "82", &hints, &result) != 0)
    return -1;

SOCKET sock = socket(AF_INET, result->ai_socktype, result->ai_protocol);
int nRet = connect(sock, result->ai_addr, result->ai_addrlen);  

【问题讨论】:

  • 您是否尝试过将hints.ai_family = AF_INET; 设置为hints.ai_family = AF_INET6; 时会发生什么?正如我现在所看到的,您正在查找的是IPv4 通配符地址,而不是IPv6
  • 不确定你的意思;当我传递我的机器名称(“powerhouse”)时,我没有查找通配符地址......除非我误解了你??
  • 啊,我不记得节点名称覆盖了 AI_PASSIVE 标志。 AI_PASSIVE 被忽略,因此您应该删除它以使代码更清晰。 powerhouse 解析到什么(如果您键入 host powerhouse)?
  • 我在 Windows 上,所以 'host' 不起作用 :) 不过,好消息是,我关闭了防火墙,现在上面的 IPv4 客户端代码连接正常;当它是 IPv6 时,虽然它仍然无法连接。我现在只需要从下面的 Sander 的 cmets 中了解整个 IPv6 内容...

标签: sockets tcp ipv6 ipv4


【解决方案1】:

我的 C 技能有点生疏,所以这里有一个用 Python 编写的反例。我的本地 IPv4 地址是 37.77.56.75,这就是我要绑定的地址。我尽可能简单地专注于概念。

这是服务器端:

#!/usr/bin/env python
import socket

# We bind to an IPv6 address, which contains an IPv6-mapped-IPv4-address,
# port 5000 and we leave the flowinfo (an ID that identifies a flow, not used
# a lot) and the scope-id (basically the interface, necessary if using
# link-local addresses)
host = '::ffff:37.77.56.75'
port = 5000
flowinfo = 0
scopeid = 0
sockaddr = (host, port, flowinfo, scopeid)

# Create an IPv6 socket, set IPV6_V6ONLY=0 and bind to the mapped address
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
sock.bind(sockaddr)

# Listen and accept a connection
sock.listen(0)
conn = sock.accept()

# Print the remote address
print conn[1]

这里我们在代码中绑定了一个 IPv6 地址,但该地址实际上是一个 IPv6 映射的 IPv4 地址,所以实际上我们绑定的是一个 IPv4 地址。这可以在查看即 netstat 时看到:

$ netstat -an | fgrep 5000
tcp4       0      0  37.77.56.75.5000       *.*                    LISTEN     

然后我们可以使用 IPv4 客户端连接到该服务器:

#!/usr/bin/env python
import socket

# Connect to an IPv4 address on port 5000
host = '37.77.56.75'
port = 5000
sockaddr = (host, port)                   

# Create an IPv4 socket and connect
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 
conn = sock.connect(sockaddr)

服务器会向我们显示谁连接了,使用 IPv6 地址表示:

('::ffff:37.77.56.76', 50887, 0, 0)

在本例中,我从 IPv4 主机 37.77.56.76 连接,它选择端口 50887 进行连接。

在此示例中,我们仅侦听 IPv4 地址(使用 IPv6 套接字,但它仍然是 IPv4 地址),因此纯 IPv6 客户端将无法连接。具有 IPv4 和 IPv6 的客户端当然可以使用带有 IPv6 映射 IPv4 地址的 IPv6 套接字,但它不会真正使用 IPv6,只是 IPv4 连接的 IPv6 表示。

双栈服务器必须:

  1. 监听通配符地址,这将使操作系统接受任何地址(IPv4 和 IPv6)上的连接
  2. 同时监听 IPv6 地址和 IPv4 地址(通过创建 IPv4 套接字,或通过创建 IPv6 套接字并监听 IPv6 映射的 IPv4 地址,如上所示)

使用通配符地址是最简单的。只需使用上面的服务器示例并替换主机名:

# We bind to the wildcard IPv6 address, which will make the OS listen on both
# IPv4 and IPv6
host = '::'
port = 5000
flowinfo = 0
scopeid = 0
sockaddr = (host, port, flowinfo, scopeid)

我的 Mac OS X 盒子显示为:

$ netstat -an | fgrep 5000
tcp46      0      0  *.5000                 *.*                    LISTEN     

注意tcp46,它表示它侦听两个地址系列。不幸的是,在 Linux 上它只显示 tcp6,即使同时收听两个家庭。

现在来看最复杂的例子:监听多个套接字。

#!/usr/bin/env python
import select
import socket

# We bind to an IPv6 address, which contains an IPv6-mapped-IPv4-address
sockaddr1 = ('::ffff:37.77.56.75', 5001, 0, 0)

sock1 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
sock1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
sock1.bind(sockaddr1)
sock1.listen(0)

# And we bind to a real IPv6 address
sockaddr2 = ('2a00:8640:1::224:36ff:feef:1d89', 5001, 0, 0)

sock2 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)
sock2.bind(sockaddr2)
sock2.listen(0)

# Select sockets that become active
sockets = [sock1, sock2]
readable, writable, exceptional = select.select(sockets, [], sockets)
for sock in readable:
    # Accept the connection
    conn = sock.accept()

    # Print the remote address
    print conn[1]

运行此示例时,两个套接字都是可见的:

$ netstat -an | fgrep 5000
tcp6       0      0  2a00:8640:1::224.5000  *.*                    LISTEN     
tcp4       0      0  37.77.56.75.5000       *.*                    LISTEN     

现在纯 IPv6 客户端可以连接到 2a00:8640:1::224:36ff:feef:1d89,纯 IPv4 客户端可以连接到 37.77.56.75。双栈客户端可以选择他们想要使用的协议。

【讨论】:

  • 首先,非常感谢 Sander 抽出宝贵时间做出如此规模和细节的回复!我仍在阅读和重新阅读您的 cmets,但与此同时,您能否准确说明 IPV6_V6ONLY 标志的目的是什么?你在你的例子中使用它,但我不明白为什么......(对不起)
  • 一个 IPv6 套接字可以有两种模式:一种只适用于真正的 IPv6 连接 (IPV6_V6ONLY=1) 的模式,一种是它可以为实际的 IPv4 连接提供 IPv6 套接字接口的模式(IPV6_V6ONLY=0)。使用第二种模式时,IPv4 地址将以 IPv6-Mapped-IPv4-address 格式表示为 IPv6 地址(看起来像 ::ffff:192.0.2.100::ffff:c000:0264。两者是相同的地址,第二种使用十六进制表示法)
  • 对,我已经阅读并重新阅读了您的帖子:我仍然不明白 将 IPv4 地址映射到 IPv6 地址的目的是什么...如果它下面是一个 v4 套接字,只能接受 v4 连接,为什么不直接使用普通的 v4 地址而无需映射呢?以 v6 方式表示 v4 地址还给了我们什么? (抱歉——此刻我的脑子里浮现出这一切,但我真的很想理解它——感谢你对我的包容:))
  • Wad:这个想法是您可以编写所有代码,就好像 IPv6 是唯一的协议一样,因此您不必为 IPv4 编写单独的代码:一切看起来都是 IPv6。它可以让你的代码更小更简单。
  • Sander,我要关闭这个,因为我觉得有足够的信息让我暂时忙个不停。如果我还有任何问题,我会在此处发布指向它们的链接:如果您有时间,请在可以提供帮助的情况下发表评论。总的来说,非常感谢你花时间帮助我理解这一切:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-17
  • 1970-01-01
  • 2017-03-01
  • 1970-01-01
  • 2021-03-09
  • 2023-03-12
相关资源
最近更新 更多