2010-10-27 6 views
6

Tengo en mi aplicación C++ una falla que surgió tras la migración a FreeBSD 8.1 de 32 bits desde Linux de 32 bits. Tengo una conexión de socket TCP que no se puede conectar. En la llamada a connect(), obtuve un resultado de error con errno == EINVAL que la página man para connect() no cubre.¿Por qué connect() da EINVAL intermitente en el puerto a FreeBSD?

¿Qué significa este error, qué argumento es inválido? El mensaje solo dice: "argumento inválido".

Éstos son algunos detalles de la conexión:

family: AF_INET 
len: 16 
port: 2357 
addr: 10.34.49.13 

No siempre fracasan sin embargo. La versión de FreeBSD solo falla después de dejar que la máquina permanezca inactiva durante varias horas. Pero después de fallar una vez, funciona de manera confiable hasta que lo deje reposar de nuevo durante un período prolongado.

Aquí es una parte del código:

void setSocketOptions(const int skt); 
void buildAddr(sockaddr_in &addr, const std::string &ip, 
       const ushort port); 
void deepBind(const int skt, const sockaddr_in &addr); 


void 
test(const std::string &localHost, const std::string &remoteHost, 
    const ushort localPort, const ushort remotePort, 
    sockaddr_in &localTCPAddr, sockaddr_in &remoteTCPAddr) 
{ 
    const int skt = socket(AF_INET, SOCK_STREAM, 0); 

    if (0 > skt) { 
    clog << "Failed to create socket: (errno " << errno 
     << ") " << strerror(errno) << endl; 
    throw; 
    } 

    setSocketOptions(skt); 

    // Build the localIp address and bind it to the feedback socket. Although 
    // it's not traditional for a client to bind the sending socket to a the 
    // local address, we do it to prevent connect() from using an ephemeral port 
    // which (our site's firewall may block). Also build the remoteIp address. 
    buildAddr(localTCPAddr, localHost, localPort); 
    deepBind(skt, localTCPAddr); 
    buildAddr(remoteTCPAddr, remoteHost, remotePort); 

    clog << "Info: Command connect family: " 
     << (remoteTCPAddr.sin_family == AF_INET ? "AF_INET" : "<unknown>") 
     << " len: " << int(remoteTCPAddr.sin_len) 
     << " port: " << ntohs(remoteTCPAddr.sin_port) 
     << " addr: " << inet_ntoa(remoteTCPAddr.sin_addr) << endl; 

    if (0 > ::connect(skt, (sockaddr*)& remoteTCPAddr, sizeof(sockaddr_in)))) { 
    switch (errno) { 
     case EINVAL: { 
     int value = -1; 
     socklen_t len = sizeof(value); 
     getsockopt(skt, SOL_SOCKET, SO_ERROR, &value, &len); 

     cerr << "Error: Command connect failed on local port " 
      << getLocFbPort() 
      << " and remote port " << remotePort 
      << " to remote host '" << remoteHost 
      << "' family: " 
      << (remoteTCPAddr.sin_family == AF_INET ? "AF_INET" : "<unknown>") 
      << " len: " << int(remoteTCPAddr.sin_len) 
      << " port: " << ntohs(remoteTCPAddr.sin_port) 
      << " addr: " << inet_ntoa(remoteTCPAddr.sin_addr) 
      << ": Invalid argument." << endl; 
     cerr << "\tgetsockopt => " 
      << ((value != 0) ? strerror(value): "success") << endl; 

     throw; 
     } 
     default: { 

     cerr << "Error: Command connect failed on local port " 
      << localPort << " and remote port " << remotePort 
      << ": (errno " << errno << ") " << strerror(errno) << endl; 
     throw; 
     } 
    } 
    } 
} 


void 
setSocketOptions(int skt) 
{ 
    // See page 192 of UNIX Network Programming: The Sockets Networking API 
    // Volume 1, Third Edition by W. Richard Stevens et. al. for info on using 
    // ::setsockopt(). 

    // According to "Linux Socket Programming by Example" p. 319, we must call 
    // setsockopt w/ SO_REUSEADDR option BEFORE calling bind. 
    int so_reuseaddr = 1; // Enabled. 
    int reuseAddrResult 
    = ::setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, 
        sizeof(so_reuseaddr)); 

    if (reuseAddrResult != 0) { 
    cerr << "Failed to set reuse addr on socket."; 
    throw; 
    } 

    // For every two hours of inactivity, a keepalive occurs. 
    int so_keepalive = 1; // Enabled. See page 200 for info on SO_KEEPALIVE. 
    int keepAliveResult = 
    ::setsockopt(skt, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive, 
       sizeof(so_keepalive)); 

    if (keepAliveResult != 0) { 
    cerr << "Failed to set keep alive on socket."; 
    throw; 
    } 

    struct linger so_linger; 

    so_linger.l_onoff = 1; // Turn linger option on. 
    so_linger.l_linger = 5; // Linger time in seconds. (See page 202) 

    int lingerResult 
    = ::setsockopt(skt, SOL_SOCKET, SO_LINGER, &so_linger, 
        sizeof(so_linger)); 

    if (lingerResult != 0) { 
    cerr << "Failed to set linger on socket."; 
    throw; 
    } 

    // Disable the Nagel algorithm on the command channel. SOL_TCP is not 
    // defined on FreeBSD 
#ifndef SOL_TCP 
#define SOL_TCP (::getprotobyname("TCP")->p_proto) 
#endif 

    unsigned int tcpNoDelay = 1; 
    int noDelayResult 
    = ::setsockopt(skt, SOL_TCP, TCP_NODELAY, &tcpNoDelay, 
        sizeof(tcpNoDelay)); 

    if (noDelayResult != 0) { 
    cerr << "Failed to set tcp no delay on socket."; 
    throw; 
    } 
} 

void 
buildAddr(sockaddr_in &addr, const std::string &ip, const ushort port) 
{ 
    memset(&addr, 0, sizeof(sockaddr_in)); // Clear all fields. 
    addr.sin_len = sizeof(sockaddr_in); 
    addr.sin_family = AF_INET;    // Set the address family 
    addr.sin_port = htons(port);   // Set the port. 

    if (0 == inet_aton(ip.c_str(), &addr.sin_addr)) { 
    cerr << "BuildAddr IP."; 
    throw; 
    } 
}; 

void 
deepBind(const int skt, const sockaddr_in &addr) 
{ 
    // Bind the requested port. 
    if (0 <= ::bind(skt, (sockaddr *)&addr, sizeof(addr))) { 
    return; 
    } 

    // If the port is already in use, wait up to 100 seconds. 
    int count = 0; 
    ushort port = ntohs(addr.sin_port); 

    while ((errno == EADDRINUSE) && (count < 10)) { 
    clog << "Waiting for port " << port << " to become available..." 
     << endl; 
    ::sleep(10); 
    ++count; 
    if (0 <= ::bind(skt, (sockaddr*)&addr, sizeof(addr))) { 
     return; 
    } 
    } 

    cerr << "Error: failed to bind port."; 
    throw; 
} 

Aquí es ejemplo de salida cuando EINVAL (no siempre fallan aquí, a veces tiene éxito y falla en el primer paquete enviado a través de la toma de conseguir revueltos):

Info: Command connect family: AF_INET len: 16 port: 2357 addr: 10.34.49.13 
Error: Command connect failed on local port 2355 and remote port 2357 to remote host '10.34.49.13' family: AF_INET len: 16 port: 2357 addr: 10.34.49.13: Invalid argument. 
    getsockopt => success 
+0

¿Podríamos ver un código por favor? – blaze

+0

@blaze He agregado el ejemplo de código. – WilliamKF

+0

¿Cuál es su código de inicio sockaddr (buildAddr)? He visto al menos un sistema operativo (aunque no puedo recordar cuál) y si no inicializa el relleno, se quejaría. También debería pasar sizeof (sockaddr_in), aunque creo que para IPv4 en casi todas las implementaciones son las mismas de todos modos. – tyranid

Respuesta

6

Descubrí cuál era el problema, primero conseguí un ECONNREFUSED, que en Linux puedo volver a intentar la conexión() después de una breve pausa y todo está bien, pero en FreeBSD, el siguiente intento de connect() falla con EINVAL.

La solución es cuando ECONNREFUSED realiza una copia de seguridad adicional y en su lugar comienza a volver a intentar volver al principio de la definición de prueba() anterior. Con este cambio, el código ahora funciona correctamente.

3

es interesante que el FreeBSD connect() manpage no enumera EINVAL. A different BSD manpage estados:

[EINVAL] An invalid argument was detected (e.g., address_len is 
      not valid for the address family, the specified 
      address family is invalid). 

Sobre la base de la documentación dispares de los diferentes sabores de BSD que flotan alrededor, me atrevería a decir que puede ser indocumentado posibilidades de código de retorno en FreeBSD, consulte here por ejemplo.

Mi consejo es imprimir la longitud de su dirección y el sizeof y el contenido de la estructura de la dirección de su zócalo antes de llamar al connect - esto le ayudará a descubrir qué está mal.

Más allá de eso, probablemente sea mejor si nos muestra el código que usa para configurar la conexión. Esto incluye el tipo utilizado para la dirección del socket (struct sockaddr, struct sockaddr_in, etc.), el código que lo inicializa y la llamada real al connect. Eso hará que sea mucho más fácil ayudar.

+0

Agregué el código detallado según su solicitud. – WilliamKF

1

¿Cuál es la dirección local? Estás ignorando silenciosamente los errores del bind(2), lo que parece no solo como una mala forma, ¡pero podría estar causando que este problema comience!

+0

No, el retorno de bind() no se ignora: cerr << "Error: no se pudo vincular el puerto."; tiro; – WilliamKF

+0

Ah, tienes razón. Estaba confundido por sus declaraciones tempranas y (lo que considero que era) condicionales invertidos. Entiendo las razones para comparar una constante con el retorno a una función o syscall, pero cada compilador moderno te advertirá si accidentalmente creas una tarea vacía dentro de una expresión condicional, por lo que desearía que la gente simplemente usara '(syscall()! = -1) '. –

Cuestiones relacionadas