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 bfc75b588..3a44419af 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 acbccef39..ec3a99947 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 8f0d713f4..8f1928239 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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 delta 11035 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 B-O&I5