From b48ed824e60babf4b142fe6b874d53ca382bb2db Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Fri, 23 Oct 2015 13:37:41 -0700 Subject: [PATCH] Improved RPC connection closure logic --- netcon/Intercept.c | 2 +- netcon/NetconEthernetTap.cpp | 67 +++++++++++++++++++++++------------ netcon/NetconService.hpp | 1 + netcon/libintercept.so.1.0 | Bin 47488 -> 47448 bytes 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/netcon/Intercept.c b/netcon/Intercept.c index bfc75b58..3a44419a 100755 --- a/netcon/Intercept.c +++ b/netcon/Intercept.c @@ -663,7 +663,7 @@ int accept(ACCEPT_SIG) } //if(opt & O_NONBLOCK) - fcntl(sockfd, F_SETFL, O_NONBLOCK); + //fcntl(sockfd, F_SETFL, O_NONBLOCK); char rbuf[16], c[1]; int new_conn_socket; diff --git a/netcon/NetconEthernetTap.cpp b/netcon/NetconEthernetTap.cpp index acbccef3..ec3a9994 100644 --- a/netcon/NetconEthernetTap.cpp +++ b/netcon/NetconEthernetTap.cpp @@ -267,13 +267,13 @@ TcpConnection *NetconEthernetTap::getConnectionByTheirFD(PhySocket *sock, int fd */ void NetconEthernetTap::closeConnection(TcpConnection *conn) { - lwipstack->_tcp_arg(conn->pcb, NULL); - lwipstack->_tcp_sent(conn->pcb, NULL); - lwipstack->_tcp_recv(conn->pcb, NULL); - lwipstack->_tcp_err(conn->pcb, NULL); - lwipstack->_tcp_poll(conn->pcb, NULL, 0); - lwipstack->_tcp_close(conn->pcb); - close(conn->their_fd); + //lwipstack->_tcp_arg(conn->pcb, NULL); + //lwipstack->_tcp_sent(conn->pcb, NULL); + //lwipstack->_tcp_recv(conn->pcb, NULL); + //lwipstack->_tcp_err(conn->pcb, NULL); + //lwipstack->_tcp_poll(conn->pcb, NULL, 0); + //lwipstack->_tcp_close(conn->pcb); + //close(conn->their_fd); if(conn->dataSock) { close(_phy.getDescriptor(conn->dataSock)); _phy.close(conn->dataSock,false); @@ -359,20 +359,38 @@ void NetconEthernetTap::threadMain() do not currently have any data connection associated with them. If they are unused, then we will try to read from them, if they fail, we can safely assume that the client has closed their end and we can close ours */ - for(size_t i=0, associated = 0; irpcSock == rpc_sockets[i]) - associated++; - } - if(!associated){ - // No TCP connections are associated, this is a candidate for removal - char c; - if(read(_phy.getDescriptor(rpc_sockets[i]),&c,1) < 0) { - closeClient(rpc_sockets[i]); - } + for(size_t i = 0; ilistening) { + char c; + if (read(_phy.getDescriptor(tcp_connections[i]->dataSock), &c, 1) < 0) { + // Still in listening state + } + else { + // Here we should handle the case there there is incoming data (?) + fprintf(stderr, "Listening socketpair closed. Removing RPC connection (%d)\n", + _phy.getDescriptor(tcp_connections[i]->dataSock)); + closeConnection(tcp_connections[i]); + } + } + } + } + fprintf(stderr, "tcp_conns = %d, rpc_socks = %d\n", tcp_connections.size(), rpc_sockets.size()); + for(size_t i=0, associated = 0; irpcSock == rpc_sockets[i]) + associated++; + } + if(!associated){ + // No TCP connections are associated, this is a candidate for removal + char c; + if(read(_phy.getDescriptor(rpc_sockets[i]),&c,1) < 0) { + closeClient(rpc_sockets[i]); + } + else { + // Handle RPC call, this is rare + // phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) } } - fprintf(stderr, "tcp_conns = %d, rpc_socks = %d\n", tcp_connections.size(), rpc_sockets.size()); } } if (since_tcp >= ZT_LWIP_TCP_TIMER_INTERVAL) { @@ -582,6 +600,7 @@ err_t NetconEthernetTap::nc_accept(void *arg, struct tcp_pcb *newpcb, err_t err) int n = write(larg_fd, "z", 1); // accept() in library waits for this byte if(n > 0) { if(sock_fd_write(send_fd, fds[1]) > 0) { + close(fds[1]); // close other end of socketpair new_tcp_conn->pending = true; } else { @@ -958,7 +977,9 @@ void NetconEthernetTap::handle_listen(PhySocket *sock, void **uptr, struct liste lwipstack->tcp_arg(listening_pcb, new Larg(this, conn)); /* we need to wait for the client to send us the fd allocated on their end for this listening socket */ - conn->pending = true; + fcntl(_phy.getDescriptor(conn->dataSock), F_SETFL, O_NONBLOCK); + conn->listening = true; + conn->pending = true; send_return_value(conn, ERR_OK, ERR_OK); } else { @@ -1031,6 +1052,7 @@ void NetconEthernetTap::handle_socket(PhySocket *sock, void **uptr, struct socke new_conn->their_fd = fds[1]; tcp_connections.push_back(new_conn); sock_fd_write(_phy.getDescriptor(sock), fds[1]); + close(fds[1]); // close other end of socketpair // Once the client tells us what its fd is on the other end, we can then complete the mapping new_conn->pending = true; } @@ -1171,9 +1193,10 @@ void NetconEthernetTap::handle_write(TcpConnection *conn) now space on the buffer */ if(sndbuf == 0) { _phy.setNotifyReadable(conn->dataSock, false); - lwipstack->_tcp_output(conn->pcb); return; } + if(!conn->listening) + lwipstack->_tcp_output(conn->pcb); if(conn->dataSock) { int read_fd = _phy.getDescriptor(conn->dataSock); @@ -1186,7 +1209,7 @@ void NetconEthernetTap::handle_write(TcpConnection *conn) // NOTE: this assumes that lwipstack->_lock is locked, either // because we are in a callback or have locked it manually. int err = lwipstack->_tcp_write(conn->pcb, &conn->buf, r, TCP_WRITE_FLAG_COPY); - //lwipstack->_tcp_output(conn->pcb); + lwipstack->_tcp_output(conn->pcb); if(err != ERR_OK) { fprintf(stderr, "handle_write(): error while writing to PCB, (err = %d)\n", err); return; diff --git a/netcon/NetconService.hpp b/netcon/NetconService.hpp index 8f0d713f..8f192823 100644 --- a/netcon/NetconService.hpp +++ b/netcon/NetconService.hpp @@ -55,6 +55,7 @@ namespace ZeroTier { int perceived_fd; int their_fd; bool pending; + bool listening; PhySocket *rpcSock; PhySocket *dataSock; diff --git a/netcon/libintercept.so.1.0 b/netcon/libintercept.so.1.0 index 7f604256520020bea6e1f26faa645ab0df4132ea..a2bc345bdb4359e710b6f3a8d237d856a49854e0 100755 GIT binary patch delta 11206 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 bOZjyCwekD#H^7hL??G;Rb{qxu?6Cg;d+@WD delta 11035 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 ab08lmo`Bz1#q02UO7s0J;lBW