sockets tcp ipv6 ipv4

Uso de IN6ADDR_SETV4MAPPED y sockets de doble pila



tcp ipv6 (1)

Mis habilidades C están un poco oxidadas, así que aquí hay un contraejemplo escrito en Python. Mi dirección IPv4 local es 37.77.56.75, así que eso es a lo que me vincularé. Lo mantuve lo más simple posible para enfocarme en los conceptos.

Este es el lado del servidor:

#!/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]

Aquí vinculamos una dirección IPv6 en el código, pero la dirección es realmente una dirección IPv4 asignada IPv6, por lo que en realidad estamos vinculados a una dirección IPv4. Esto se puede ver al mirar, por ejemplo, netstat:

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

Entonces podemos usar un cliente IPv4 para conectarnos a este servidor:

#!/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)

Y el servidor nos mostrará quién se conectó, utilizando la representación de direcciones IPv6:

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

En este ejemplo, me conecté desde el host IPv4 37.77.56.76 , y elijo el puerto 50887 para conectarme.

En este ejemplo, solo estamos escuchando en una dirección IPv4 (usando sockets IPv6, pero todavía es una dirección IPv4), por lo que los clientes solo IPv6 no podrán conectarse. Un cliente con IPv4 e IPv6 podría usar, por supuesto, sockets IPv6 con IPv6-mapped-IPv4-addresses, pero entonces realmente no usaría IPv6, solo una representación IPv6 de una conexión IPv4.

Un servidor de doble pila tiene que:

  1. escuchar en la dirección de comodín, que hará que el sistema operativo acepte conexiones en cualquier dirección (tanto IPv4 como IPv6)
  2. escuchar tanto en una dirección IPv6 como en una dirección IPv4 (ya sea creando un socket IPv4, o creando un socket IPv6 y escuchando una dirección IPv6-mapped-IPv4 como se muestra arriba)

Usar la dirección del comodín es el más simple. Simplemente use el ejemplo de servidor de arriba y reemplace el nombre de host:

# 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)

El cuadro Mi Mac OS X muestra esto como:

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

Observe el tcp46 que indica que escucha en ambas familias de direcciones. Desafortunadamente en Linux solo muestra tcp6 , incluso cuando se escucha en ambas familias.

Ahora, para el ejemplo más complicado: escuchar en múltiples tomas.

#!/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]

Al ejecutar este ejemplo, ambos enchufes son visibles:

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

Y ahora los clientes solo IPv6 pueden conectarse a 2a00:8640:1::224:36ff:feef:1d89 y los clientes solo IPv4 pueden conectarse a 37.77.56.75 . Los clientes de doble pila pueden elegir qué protocolo desean usar.

Esta es una continuación de Connecting IPv4 client to IPv6 server: conexión rechazada . Estoy experimentando con sockets de doble pila e intentando entender para qué es útil setsockopt con IPV6_V6ONLY. En la pregunta vinculada me informaron que "Establecer IPV6_V6ONLY en 0 puede ser útil si también vincula el servidor a una dirección IPv4 asignada IPv6". He hecho esto a continuación, y esperaba que mi servidor pudiera aceptar conexiones de un IPv6 y un cliente IPv4. Pero sorprendentemente cuando ejecuto mi cliente con un V4 y un socket V6, ¡ninguno puede conectarse!

¿Puede alguien decirme qué estoy haciendo mal o he entendido mal la funcionalidad de doble pila IPv6?

Servidor:

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");

Cliente:

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);