首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用IN6ADDR_SETV4MAPPED和双堆栈套接字

使用IN6ADDR_SETV4MAPPED和双堆栈套接字
EN

Stack Overflow用户
提问于 2013-05-27 03:31:08
回答 1查看 3K关注 0票数 2

这是Connecting IPv4 client to IPv6 server: connection refused的延续。我正在尝试使用双堆栈套接字,并试图了解使用IPV6_V6ONLY的setsockopt有什么用处。在链接的问题上,我被建议“如果您还将服务器绑定到IPV6映射的IPv4地址,则将IPV6_V6ONLY设置为0会很有用”。我已经在下面这样做了,并且期望我的服务器能够接受来自IPv6和IPv4客户机的连接。但令人震惊的是,当我使用V4和V6套接字运行客户机时,两者都无法连接!

有人能告诉我我做错了什么吗,或者我误解了IPv6双栈功能?

服务器:

代码语言:javascript
复制
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");

客户端:

代码语言:javascript
复制
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);  
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-05-27 04:54:15

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

这是服务器端:

代码语言:javascript
复制
#!/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时看到:

代码语言:javascript
复制
$ netstat -an | fgrep 5000
tcp4       0      0  37.77.56.75.5000       *.*                    LISTEN     

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

代码语言:javascript
复制
#!/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地址表示向我们显示是谁连接的:

代码语言:javascript
复制
('::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地址,如上所述)

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

代码语言:javascript
复制
# 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框显示为:

代码语言:javascript
复制
$ netstat -an | fgrep 5000
tcp46      0      0  *.5000                 *.*                    LISTEN     

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

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

代码语言:javascript
复制
#!/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]

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

代码语言:javascript
复制
$ 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。双堆栈客户端可以选择要使用的协议。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16762939

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档