2013-07-11 17 views
6

È possibile ottenere in modo affidabile un socket Winsock su connect() se si connette a localhost con una porta nell'intervallo di porte temporanee assegnate automaticamente (5000-65534). In particolare, Windows sembra avere un numero di porta a rotazione a livello di sistema che è la porta successiva che tenterà di assegnare come numero di porta locale per un socket client. Se creo i socket fino a quando il numero assegnato è appena sotto il mio numero di porta di destinazione, quindi creo ripetutamente un socket e tento di connettermi a quel numero di porta, di solito riesco a connettere il socket a se stesso.Perché un socket può connettersi() alla propria porta effimera?

Prima ho verificato che si verifichi in un'applicazione che tenta ripetutamente di connettersi a una determinata porta su localhost e, quando il servizio non è in ascolto, stabilisce molto raramente una connessione e riceve il messaggio inviato inizialmente (cosa che accade essere un comando Redis PING).

Un esempio, in Python (gestito con niente ascolto della porta di destinazione):

import socket 

TARGET_PORT = 49400 

def mksocket(): 
    return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) 

while True: 
    sock = mksocket() 
    sock.bind(('127.0.0.1', 0)) 
    host, port = sock.getsockname() 
    if port > TARGET_PORT - 10 and port < TARGET_PORT: 
     break 
    print port 

while port < TARGET_PORT: 
    sock = mksocket() 
    err = None 
    try: 
     sock.connect(('127.0.0.1', TARGET_PORT)) 
    except socket.error, e: 
     err = e 
    host, port = sock.getsockname() 
    if err: 
     print 'Unable to connect to port %d, used local port %d: %s' % (TARGET_PORT, port, err) 
    else: 
     print 'Connected to port %d, used local port %d' (TARGET_PORT, port) 

Sulla mia macchina Mac, questo alla fine termina con Unable to connect to port 49400, used local port 49400. Sulla mia macchina Windows 7, una connessione viene stabilita con successo e stampa Connected to port 49400, used local port 49400. Il socket risultante riceve tutti i dati che gli vengono inviati.

Si tratta di un bug in Winsock? È un bug nel mio codice?

Edit: Ecco uno screenshot del TcpView con il collegamento incriminato mostrato:

python.exe 8108 TCP (my HOSTNAME) 49400 localhost 49400 ESTABLISHED

+0

Ecco alcune domande correlate: http://stackoverflow.com/questions/4949858/how-can-you-have-a-tcp-connection-back-to-the-same-port http: // stackoverflow. it/questions/5139808/tcp-simult-open-and-self-connect-prevention –

+0

Ho questo problema che si verifica abbastanza regolarmente nei miei sistemi, che coinvolgono almeno cinque porte locali su cui i server possono o non possono essere in esecuzione ea cui i clienti cercano continuamente di connettersi. Non riesco a pensare ad alcun modo per risolvere questo in modo indipendente dalla piattaforma a livello di connessione. –

risposta

2

Questo sembra essere un 'iniziazione simultaneo' come descritto nel # 3.4 di RFC 793. Vedere la Figura 8. Notare che nessuno dei due lati è in stato LISTEN in qualsiasi momento. Nel tuo caso, entrambe le estremità sono le stesse: ciò farebbe in modo che funzioni esattamente come descritto nella RFC.

+0

Questo spiega sicuramente come TCP non cada, ma è difficilmente 'simultaneo' se si ha un solo socket. Winsock sta ancora facendo qualcosa di straordinario qui. –

+0

È simultaneo perché il socket ha due porte, locale e remota, e si collegano entrambe contemporaneamente come descritto. Capita di essere lo stesso, ma ci sono comunque due cose che accadono contemporaneamente. Altrimenti la connessione non potrebbe essere completata. Winsock si comporta esattamente come descritto nella sezione 3.4 e nella figura 8. – EJP

+0

Ok, ma non riesco a farlo accadere anche se provo esplicitamente sul mio Mac: posso 'bind()' il socket su una porta locale ma quando prova a 'connect()' quello stesso socket a quella porta ottengo 'EINVAL'. Sicuramente questo è uno scenario da manuale "avvio simultaneo con una presa". –

0

Si tratta di un bug di logica nel codice.

Prima di tutto, solo le versioni più recenti di Windows utilizzano 5000-65534 come porte effimere. Le versioni precedenti utilizzavano invece 1025-5000.

Si stanno creando prese multiple che sono esplicitamente associate a porte temporanee casuali finché non si è associato un socket che si trova a meno di 10 porte rispetto alla porta di destinazione. Tuttavia, se qualcuno di questi socket si collega effettivamente alla porta di destinazione effettiva, lo ignori e continua il ciclo. Quindi si può o si può finire con un socket collegato alla porta di destinazione, e si può o non si può finire con un valore finale port che in realtà è inferiore alla porta di destinazione.

Dopo di che, se port sembra essere inferiore alla tua porta di destinazione (che non è garantita), si sta quindi creando più prese che sono implicitamente destinata a diverse porte effimere disponibili casuali quando si chiama connect() (lo fa un implicito bind() internamente se non è stato ancora chiamato bind()), nessuna delle quali sarà la stessa porta temporanea a cui ci si limita esplicitamente poiché tali porte sono già in uso e non possono essere riutilizzate.

In nessun punto si ha un dato socket che si connette da una porta effimera alla stessa porta effimera. E a meno che l'un'altra app si leghi alla porta di destinazione e sia attivamente in ascolto su quella porta, quindi non è possibile che connect() possa connettersi correttamente alla porta di destinazione su uno qualsiasi dei socket creati, poiché nessuno di loro sono nello stato di ascolto. E getsockname() non è valido su un socket non associato e non è garantito che un socket di connessione sia associato se connect() non riesce.Quindi i sintomi che pensi stiano accadendo sono fisicamente impossibili dato il codice che hai mostrato. Il tuo logging sta semplicemente facendo delle ipotesi sbagliate e quindi sta registrando le cose sbagliate, dandoti un falso stato dell'essere.

provare qualcosa di più simile a questo, invece, e vedrete che cosa le porte reali sono:

import socket 

TARGET_PORT = 49400 

def mksocket(): 
    return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) 

while True: 
    sock = mksocket() 
    sock.bind(('127.0.0.1', 0)) 
    host, port = sock.getsockname() 
    print 'Bound to local port %d' % (port) 
    if port > TARGET_PORT - 10 and port < TARGET_PORT: 
     break 

if port >= TARGET_PORT: 
    print 'Bound port %d exceeded target port %d' % (port, TARGET_PORT) 
else: 
    while port < TARGET_PORT: 
     sock = mksocket() 
     # connect() would do this internal anyway, so this is just to ensure a port is available for logging even if connect() fails 
     sock.bind(('127.0.0.1', 0)) 
     err = None 
     try: 
      sock.connect(('127.0.0.1', TARGET_PORT)) 
     except socket.error, e: 
      err = e 
     host, port = sock.getsockname() 
     if err: 
      print 'Unable to connect to port %d using local port %d' % (TARGET_PORT, port) 
     else: 
      print 'Connected to port %d using local port %d' % (TARGET_PORT, port) 
+0

Python sta chiudendo i socket quando vengono sovrascritti, quindi in teoria è possibile riutilizzare le porte (sebbene finiscano nello stato TIME_WAIT e probabilmente non saranno riutilizzate comunque). E sono consapevole che non è definito se questo script fa ciò che voglio che faccia ogni volta, ma normalmente si comporta come sto descrivendo. Almeno su Windows non posso chiamare 'getsockname()' prima della connessione, ma posso chiamarlo dopo la connessione (anche se fallisce). Anche se non è definito, Windows sembra consentire il binding implicito anche se non viene stabilita alcuna connessione. –

+0

Devo anche chiarire che ho fatto apparire questo scenario in un altro programma usando solo il primo ciclo, "inizializzando" l'attuale porta temporanea, e ho controllato TcpView (da Sysinternals) e verificato che ci sia in effetti una porta con entrambi i telecomandi e le porte locali identiche. Non credo che il tuo reclamo nel quinto paragrafo sia accurato. (Proverò a eseguire lo script modificato domani.) –

+0

@JohnCalsbeek: sì, le porte chiuse si troveranno in uno stato TIME_WAIT e non saranno riutilizzabili per diversi minuti, a meno che non venga disattivato prima che vengano chiusi o che vengano aperti con Opzione SO_REUSEADDR abilitata. Sì, non è possibile chiamare 'getsockname()' su un socket non associato e chiamare 'connect()' su un socket non associato lo collegherà solo se la connessione ha esito positivo. Non vi è alcuna garanzia che il socket verrà associato se 'connect()' fallisce. –

Problemi correlati