2

これは、 IPv4 クライアントを IPv6 サーバーに接続する: 接続が拒否されました の続きです。私はデュアル スタック ソケットを試しており、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);  
4

1 に答える 1

9

私の 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. ワイルドカード アドレスをリッスンします。これにより、OS は任意のアドレス (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両方のアドレス ファミリでリッスンしていることを示す に注意してください。tcp6残念ながら、Linux では、両方のファミリでリッスンしている場合でも、しか表示されません。

次に、最も複雑な例として、複数のソケットをリッスンします。

#!/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。デュアル スタック クライアントは、使用するプロトコルを選択できます。

于 2013-05-26T20:54:15.313 に答える