问题描述
这是将IPv4客户端连接到的延续IPv6服务器:连接被拒绝.我正在尝试使用双堆栈套接字,并试图了解使用IPV6_V6ONLY的setsockopt有用的地方.在链接的问题上,我被告知如果还将服务器绑定到映射了IPv6的IPv4地址,则将IPV6_V6ONLY设置为0会很有用".我已经在下面完成了此操作,并期望我的服务器能够接受来自IPv6和IPv4客户端的连接.但是令人震惊的是,当我使用V4和V6套接字运行客户端时,都无法连接!
This is a continuation of Connecting IPv4 client to IPv6 server: connection refused. I am experimenting with dual stack sockets and trying to understand what setsockopt with IPV6_V6ONLY is useful for. On the linked question I was advised that "Setting IPV6_V6ONLY to 0 can be useful if you also bind the server to an IPv6-mapped IPv4 address". I have done this below, and was expecting my server to be able to accept connections from both an IPv6 and an IPv4 client. But shockingly when I run my client with a V4 and a V6 socket, neither can connect!
有人可以告诉我我做错了什么吗,还是我一起误解了IPv6双栈功能?
Can someone please tell me what I am doing wrong, or have I misunderstood IPv6 dual stack functionality all together?
服务器:
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);
推荐答案
我的C语言有点生锈,因此这里是一个用Python编写的反例.我的本地IPv4地址是37.77.56.75,这就是我要绑定的地址.我将重点放在概念上尽可能的简单.
My C skills are a bit rusty, so here is a counter-example written in Python. My local IPv4 address is 37.77.56.75, so that is what I will bind to. I kept it as simple as possible to focus on the concepts.
这是服务器端:
#!/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时可以看到这一点:
Here we bind to an IPv6 address in the code, but the address is actually an IPv6-mapped IPv4 address, so in reality we are binding to an IPv4 address. This can be seen when looking at i.e. netstat:
$ netstat -an | fgrep 5000
tcp4 0 0 37.77.56.75.5000 *.* LISTEN
然后我们可以使用IPv4客户端连接到该服务器:
We can then use an IPv4 client to connect to this server:
#!/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地址表示方式向我们显示谁连接了
And the server will show us who connected, using IPv6 address representation:
('::ffff:37.77.56.76', 50887, 0, 0)
在此示例中,我从IPv4主机37.77.56.76
连接,它选择了端口50887
作为连接来源.
In this example I connected from IPv4 host 37.77.56.76
, and it choose port 50887
to connect from.
在此示例中,我们仅侦听IPv4地址(使用IPv6套接字,但仍为IPv4地址),因此仅IPv6的客户端将无法连接.同时具有IPv4和IPv6的客户端当然可以使用具有IPv6映射的IPv4地址的IPv6套接字,但实际上并不会真正使用IPv6,而只是IPv4连接的IPv6表示形式.
In this example we are only listening on an IPv4 address (using IPv6 sockets, but it is still an IPv4 address) so IPv6-only clients will not be able to connect. A client with both IPv4 and IPv6 could of course use IPv6 sockets with IPv6-mapped-IPv4-addresses, but then it would not really be using IPv6, just an IPv6 representation of an IPv4 connection.
双栈服务器必须:
- 收听通配符地址,这将使操作系统接受任何地址(IPv4和IPv6)上的连接
- 同时监听IPv6地址和IPv4地址(通过创建IPv4套接字,或者创建IPv6套接字并侦听IPv6映射的IPv4地址,如上所示)
使用通配符地址是最简单的.只需使用上面的服务器示例并替换主机名:
Using the wildcard address is the most simple. Just use the server example from above and replace the hostname:
# 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框显示为:
My Mac OS X box shows this as:
$ netstat -an | fgrep 5000
tcp46 0 0 *.5000 *.* LISTEN
请注意tcp46
,这表明它在两个地址族上进行侦听.不幸的是,即使在两个家庭中收听时,在Linux上它也只显示tcp6
.
Notice the tcp46
which indicates that it listens on both address families. Unfortunately on Linux it only shows tcp6
, even when listening on both families.
现在是最复杂的示例:在多个套接字上侦听.
Now for the most complicated example: listening on multiple sockets.
#!/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]
运行此示例时,两个套接字均可见:
When running this example both sockets are visible:
$ 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
.双栈客户端可以选择要使用的协议.
And now IPv6-only clients can connect to 2a00:8640:1::224:36ff:feef:1d89
and IPv4-only clients can connect to 37.77.56.75
. Dual stack clients can choose which protocol they want to use.
这篇关于使用IN6ADDR_SETV4MAPPED和双堆栈插槽的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!