networking - sistemas - ¿Cómo saber de qué interfaz recibió el mensaje el socket?
socket udp (5)
Si un socket está vinculado a IN6ADDR_ANY
o IN6ADDR_ANY
y utiliza una llamada como recvfrom()
para recibir mensajes en el socket. ¿Hay alguna manera de averiguar de qué interfaz proviene el mensaje?
En el caso de los mensajes del ámbito del enlace IPv6, esperaba que el argumento from recvfrom()
tuviera el campo scope_id
inicializado en el ID de la interfaz. Lamentablemente, está configurado en 0
en mi programa de prueba.
¿Alguien sabe de una manera de descubrir esta información?
Ha pasado un tiempo desde que he estado haciendo la codificación C / C ++ TCP / IP, pero por lo que recuerdo en cada mensaje (o socket derivado) puede acceder a la información de los encabezados IP. Estos encabezados deben incluir la dirección de recepción que será la dirección IP de la interfaz sobre la que está preguntando.
Además de enlazar a cada interfaz, no estoy enterado de una forma con IPv4, per se.
IPv6 ha agregado la opción de socket IPV6_PKTINFO para solucionar este inconveniente. Con esa opción en efecto, una struct in6_pktinfo
se devolverá como datos auxiliares.
Aparte de abrir un socket separado en cada interfaz como sugirió Glomek, la única forma que conozco de hacerlo definitivamente en Windows es usar un socket sin formato, por ejemplo,
SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
Cada recepción de este socket será un paquete IP , que contiene las direcciones de origen y de destino. El programa en el que trabajo requiere que coloque el socket en modo promiscuo usando la opción SIO_RCVALL. Hacer esto significa que obtengo todos los paquetes de IP que la interfaz "ve" en la red. Extraer paquetes expresamente para mi aplicación requiere que filtre los datos usando las direcciones y puertos en los encabezados IP y TCP / UDP. Obviamente, eso probablemente sea más sobrecargado de lo que a usted le interesa. Solo lo menciono para decir esto: nunca he usado un socket en bruto sin ponerlo en modo promiscuo. Por lo tanto, no estoy seguro de si puede vincularlo a INADDR_ANY y simplemente usarlo como un socket normal desde ese punto en adelante o no. Me parece que puedes; Nunca lo intenté.
EDITAR: lee este artículo para conocer las limitaciones con respecto a los sockets sin formato en Windows. Este obstáculo más grande que enfrenté en mi proyecto fue que uno tiene que ser miembro del grupo Administradores para abrir un socket sin formato en Windows 2000 y posterior.
dwc tiene razón, IPV6_PKTINFO funcionará para IPv6 en Linux.
Además, IP_PKTINFO funcionará para IPv4: puede ver detalles en la página de manual ip (7)
Construí un ejemplo que extrae las direcciones de origen, destino e interfaz. Por brevedad, no se proporciona ninguna verificación de errores. Vea este duplicado: obtenga la dirección de destino de un paquete UDP recibido .
// sock is bound AF_INET socket, usually SOCK_DGRAM
// include struct in_pktinfo in the message "ancilliary" control data
setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt));
// the control data is dumped here
char cmbuf[0x100];
// the remote/source sockaddr is put here
struct sockaddr_in peeraddr;
// if you want access to the data you need to init the msg_iovec fields
struct msghdr mh = {
.msg_name = &peeraddr,
.msg_namelen = sizeof(peeraddr),
.msg_control = cmbuf,
.msg_controllen = sizeof(cmbuf),
};
recvmsg(sock, &mh, 0);
for ( // iterate through all the control headers
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&mh, cmsg))
{
// ignore the control headers that don''t match what we want
if (cmsg->cmsg_level != IPPROTO_IP ||
cmsg->cmsg_type != IP_PKTINFO)
{
continue;
}
struct in_pktinfo *pi = CMSG_DATA(cmsg);
// at this point, peeraddr is the source sockaddr
// pi->ipi_spec_dst is the destination in_addr
// pi->ipi_addr is the receiving interface in_addr
}