2010-10-27 9 views
6

Ho nella mia applicazione C++ un errore che è sorto con il porting a 32 bit di FreeBSD 8.1 da Linux a 32 bit. Ho una connessione socket TCP che non riesce a connettersi. Nella chiamata a connect(), ho ottenuto un risultato di errore con errno == EINVAL che la pagina man per connect() non copre.Perché connect() fornisce EINVAL intermittente sulla porta su FreeBSD?

Cosa significa questo errore, quale argomento non è valido? Il messaggio dice semplicemente: "Argomento non valido".

Ecco alcuni dettagli della connessione:

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

Non ha sempre esito negativo però. La versione di FreeBSD fallisce solo dopo aver lasciato la macchina inattiva per diverse ore. Ma dopo aver fallito una volta, funziona in modo affidabile finché non lo lasci riposare di nuovo inattivo per un periodo prolungato.

Ecco parte del codice:

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

Ecco esempio uscita quando EINVAL (non sempre sicuro qui, a volte succede e non sul primo pacchetto inviato sulla presa ottenendo criptato):

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

Potremmo vedere qualche codice per favore? – blaze

+0

@blaze Ho aggiunto un esempio di codice. – WilliamKF

+0

Qual è il tuo codice init sockaddr (buildAddr)? Ho visto almeno un sistema operativo (anche se non riesco a ricordare quale dei due) dove se non si inizializza il padding si lamenterebbe. Inoltre dovresti davvero passare sizeof (sockaddr_in) anche se penso che per IPv4 praticamente tutte le implementazioni siano uguali. – tyranid

risposta

6

Ho capito qual era il problema, stavo per ottenere un ECONNREFUSED, che su Linux posso semplicemente riprovare connect() dopo una breve pausa e tutto va bene, ma su FreeBSD, il seguente tentativo di connect() fallisce con EINVAL.

La soluzione è quando ECONNREFUSED esegue il backup ulteriore e invece riprende a riprendere la definizione di inizio test() sopra. Con questa modifica, il codice ora funziona correttamente.

3

E 'interessante il fatto che il FreeBSD connect() manpage non elenca EINVAL. A different BSD manpage stati:

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

Sulla base della documentazione disparate dai diversi sapori BSD che galleggiano intorno, vorrei azzardare che ci possono essere senza documenti possibilità codice di ritorno in FreeBSD, vedere here per esempio.

Il mio consiglio è di stampare la lunghezza dell'indirizzo e il sizeof e il contenuto della struttura dell'indirizzo del socket prima di chiamare connect - questo si spera che vi aiuti a scoprire cosa c'è che non va.

Oltre a questo, probabilmente è meglio se ci mostra il codice che usi per configurare la connessione. Questo include il tipo utilizzato per l'indirizzo socket (struct sockaddr, struct sockaddr_in, ecc.), Il codice che lo inizializza e la chiamata effettiva a connect. Ciò renderà molto più facile l'assistenza.

+0

Ho aggiunto un codice dettagliato per la tua richiesta. – WilliamKF

1

Qual è l'indirizzo locale? Stai ignorando silenziosamente gli errori da bind(2), che sembra non solo una cattiva forma, ma potrebbe causare l'inizio di questo problema!

+0

No, il ritorno da bind() non viene ignorato: cerr << "Errore: impossibile eseguire il binding della porta."; lancio; – WilliamKF

+0

Ah, hai ragione. Ero confuso dai tuoi ritorni anticipati e (cosa che considero essere) condizionali invertiti. Capisco la logica per confrontare una costante con il ritorno a una funzione o syscall, ma ogni compilatore moderno ti avviserà se crei accidentalmente un compito nudo all'interno di un'espressione condizionale, quindi vorrei che le persone usassero semplicemente '(syscall()! = -1) '. –

Problemi correlati