2010-06-17 29 views
22

Tengo un socket UDP que está vinculado a INADDR_ANY para escuchar paquetes en todas las direcciones IP que tiene mi servidor. Estoy enviando respuestas a través del mismo socket.Configuración de la IP de origen para un socket UDP

En este momento el servidor elige automáticamente qué IP se utiliza como IP de origen cuando se envían los paquetes, pero me gustaría poder configurar la IP de origen saliente.

¿Hay alguna manera de hacerlo sin tener que crear un socket separado para cada IP?

Respuesta

22

Nikolai, utilizando un socket separado y bind (2) para cada dirección o jugando con tablas de enrutamiento a menudo no es una opción viable, por ejemplo. con direcciones dinámicas Un solo servidor UDP IP_ADDRANY-bound debería poder aparecer para responder en la misma dirección IP asignada dinámicamente en la que se recibe un paquete.

Afortunadamente, hay otra manera. Según el soporte de su sistema, puede utilizar las opciones de socket IP_PKTINFO para establecer o recibir datos auxiliares sobre un mensaje. Los datos auxiliares (a través de cmsg(3)) están cubiertos en muchos lugares en línea, aunque comp.os.linux.development.system tenía un código completo de muestra específico para IP_PKTINFO.

El código en el enlace utiliza IP_PKTINFO (o IP_RECVDSTADDR dependiendo de la plataforma) para obtener la dirección de destino de un mensaje UDP a partir de los datos auxiliares cmsg(3). Parafraseado aquí:

struct msghdr msg; 
struct cmsghdr *cmsg; 
struct in_addr addr; 
// after recvmsg(sd, &msg, flags); 
for(cmsg = CMSG_FIRSTHDR(&msg); 
    cmsg != NULL; 
    cmsg = CMSG_NXTHDR(&msg, cmsg)) { 
    if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { 
    addr = ((struct in_pktinfo*)CMSG_DATA(cmsg))->ipi_addr; 
    printf("message received on address %s\n", inet_ntoa(addr)); 
    } 
} 

Gene, su pregunta preguntó cómo configurar la dirección de origen de los paquetes salientes. Con IP_PKTINFO, es posible configurar el campo ipi_spec_dst del struct in_pktinfo en los datos auxiliares pasados ​​al sendmsg(2). Consulte la publicación mencionada anteriormente, cmsg(3) y sendmsg(2) para obtener instrucciones sobre cómo crear y manipular los datos auxiliares en un struct msghdr. Un ejemplo (sin garantía aquí) podría ser:

struct msghdr msg; 
struct cmsghdr *cmsg; 
struct in_pktinfo *pktinfo; 
// after initializing msghdr & control data to CMSG_SPACE(sizeof(struct in_pktinfo)) 
cmsg = CMSG_FIRSTHDR(&msg); 
cmsg->cmsg_level = IPPROTO_IP; 
cmsg->cmsg_type = IP_PKTINFO; 
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); 
pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg); 
pktinfo->ipi_ifindex = src_interface_index; 
pktinfo->ipi_spec_dst = src_addr; 
// bytes_sent = sendmsg(sd, &msg, flags); 

Nota esto es diferente en IPv6: struct in6_pktinfo::ipi6_addr utilizar tanto en el recvmsg y sendmsg casos.

Tenga en cuenta también que Windows no admite un equivalente a ipi_spec_dst en la estructura in_pktinfo, por lo que no puede usar este método para establecer la dirección de origen en un paquete de winsock2 saliente.

(paginas de referencia - Cómo límite de alrededor de 1 hipervínculo)

http:// linux.die.net/man/2/sendmsg 
http:// linux.die.net/man/3/cmsg 
3

bind(2)bind(2) a cada dirección de interfaz y administra varios sockets, o deja que el kernel realice la asignación de IP de origen implícita con INADDR_ANY. No hay otra manera.

Mi pregunta sería: ¿por qué necesita esto? ¿El enrutamiento IP normal no funciona para usted?

+0

Gracias, el enrutamiento de IP funciona bien y los paquetes llegan a su destino, pero desafortunadamente todos los clientes se conectan a su servidor IP específico y el protocolo requiere que obtengan la respuesta de esta IP específica. En este momento, todos los clientes obtienen su respuesta de la misma IP. –

+0

Mi sospecha sería la tabla de enrutamiento: ¿tiene una ruta/puerta de enlace predeterminada única? Agregar rutas específicas a las direcciones del cliente puede ser útil. –

+0

Sí, agregar una ruta de host ayuda, pero hubiera preferido hacerlo en mi programa. –

17

que pensé en expandir Jeremy sobre la manera de hacer esto para IPv6. Jeremy omite muchos detalles, y cierta documentación (como la página man de Linux para ipv6) es simplemente incorrecta.En primer lugar en algunas distribuciones, es necesario definir _GNU_SOURCE, de lo contrario algunas de las cosas IPv6 no está definido:

#define _GNU_SOURCE 
#include <netinet/in.h> 
#include <sys/types.h> 
#include <sys/socket.h> 

Siguiente estableció la toma de una manera bastante estándar que detecta todos los paquetes IP (es decir, IPv4 e IPv6) en un puerto UDP en particular:

const int on=1, off=0; 
int result; 
struct sockaddr_in6 sin6; 
int soc; 

soc = socket(AF_INET6, SOCK_DGRAM, 0); 
setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 
setsockopt(soc, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); 
setsockopt(soc, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); 
setsockopt(soc, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); 
memset(&sin6, '\0', sizeof(sin6)); 
sin6.sin6_family = htons(AF_INET6); 
sin6.sin6_port = htons(MY_UDP_PORT); 
result = bind(soc, (struct sockaddr*)&sin6, sizeof(sin6)); 

Aviso el código anterior establece tanto las opciones de IPv6 IP y para una toma de IPv6. Resulta que si el paquete llega a una dirección IPv4, obtendrá IP_PKTINFO (es decir, IPv4) cmsg a pesar de que es un socket IPv6, y si no los habilita, no se enviarán. Observe también la opción IPV6_RECPKTINFO se establece (que no se menciona en hombre 7 ipv6), no IPV6_PKTINFO (que se describe erróneamente en hombre 7 ipv6). Ahora recibirá un paquete UDP:

int bytes_received; 
struct sockaddr_in6 from; 
struct iovec iovec[1]; 
struct msghdr msg; 
char msg_control[1024]; 
char udp_packet[1500]; 

iovec[0].iov_base = udp_packet; 
iovec[0].iov_len = sizeof(udp_packet); 
msg.msg_name = &from; 
msg.msg_namelen = sizeof(from); 
msg.msg_iov = iovec; 
msg.msg_iovlen = sizeof(iovec)/sizeof(*iovec); 
msg.msg_control = msg_control; 
msg.msg_controllen = sizeof(msg_control); 
msg.msg_flags = 0; 
bytes_received = recvmsg(soc, &msg, 0); 

El siguiente paso es extraer la interfaz y la dirección del paquete UDP se recibió el cabo de la cmsg:

struct in_pktinfo in_pktinfo; 
struct in6_pktinfo in6_pktinfo; 
int have_in_pktinfo = 0; 
int have_in6_pktinfo = 0; 
struct cmsghdr* cmsg; 

for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != 0; cmsg = CMSG_NXTHDR(&msg, cmsg)) 
{ 
    if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) 
    { 
    in_pktinfo = *(struct in_pktinfo*)CMSG_DATA(cmsg); 
    have_in_pktinfo = 1; 
    } 
    if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) 
    { 
    in6_pktinfo = *(struct in6_pktinfo*)CMSG_DATA(cmsg); 
    have_in6_pktinfo = 1; 
    } 
} 

Finalmente llegamos a enviar la respuesta de vuelta, usando el mismo destino

int cmsg_space; 

iovec[0].iov_base = udp_response; 
iovec[0].iov_len = udp_response_length; 
msg.msg_name = &from; 
msg.msg_namelen = sizeof(from); 
msg.msg_iov = iovec; 
msg.msg_iovlen = sizeof(iovec)/sizeof(*iovec); 
msg.msg_control = msg_control; 
msg.msg_controllen = sizeof(msg_control); 
msg.msg_flags = 0; 
cmsg_space = 0; 
cmsg = CMSG_FIRSTHDR(&msg); 
if (have_in6_pktinfo) 
{ 
    cmsg->cmsg_level = IPPROTO_IPV6; 
    cmsg->cmsg_type = IPV6_PKTINFO; 
    cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); 
    *(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo; 
    cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo)); 
} 
if (have_in_pktinfo) 
{ 
    cmsg->cmsg_level = IPPROTO_IP; 
    cmsg->cmsg_type = IP_PKTINFO; 
    cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); 
    *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo; 
    cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); 
} 
msg.msg_controllen = cmsg_space; 
ret = sendmsg(soc, &msg, 0); 

Una vez más cuenta de cómo, si el paquete llegó a través de IPv4 tenemos que poner una opción IPv4 en el cmsg a pesar de que es un conector AF_INET6. Al menos, eso es lo que tienes que hacer para Linux.

Es una cantidad sorprendente de trabajo, pero AFAICT es lo mínimo que tiene que hacer para crear un servidor UDP robusto que funcione en todos los entornos de Linux concebibles. La mayor parte no es necesaria para TCP porque maneja multihoming de forma transparente.

0

Me encontré con el mismo problema recientemente.

Lo que hago para solucionar este problema es

  1. obtener el nombre de la interfaz del paquete recibido
  2. zócalo se unen a la interfaz específica
  3. toma unbind

Ejemplo:

struct ifreq ifr; 
    ... 
    recvmsg(fd, &msg...) 
    ...  
    if (msg.msg_controllen >= sizeof(struct cmsghdr)) 
    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) 
     if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) 
     { 
     iface_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex; 
     } 
    if_indextoname(iface_index , ifr.ifr_name); 
    mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); 

    sendmsg(...); 

    memset(&ifr, 0, sizeof(ifr)); 
    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), ""); 
    mret=setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); 
+0

Encontré un enlace sobre la selección de IP de origen del centro de conocimiento de IBM: [https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm] (https://www.ibm.com/support/knowledgecenter/SSLTBW_2.1.0/com.ibm.zos.v2r1.halz002/tcpip_source_ip_addr_selection.htm) –

Cuestiones relacionadas