-
Notifications
You must be signed in to change notification settings - Fork 268
Parent TCP sockets aren't disassociated from the network interface until all child sockets are closed #3563
Description
In Shadow's C TCP code, a parent socket is responsible for forwarding packets to its children. For example, if a listening socket accept()s a new child socket, any incoming packets for the child socket are received by the parent listening socket, and then forwarded to the child socket.
shadow/src/main/host/descriptor/tcp.c
Lines 2063 to 2072 in aa0445f
| /* return TRUE if the packet should be retransmitted */ | |
| static void _tcp_processPacket(LegacySocket* socket, const Host* host, Packet* packet) { | |
| TCP* tcp = _tcp_fromLegacyFile((LegacyFile*)socket); | |
| MAGIC_ASSERT(tcp); | |
| /* fetch the TCP info from the packet */ | |
| gsize packetLength = packet_getPayloadSize(packet); | |
| /* if we run a server, the packet could be for an existing child */ | |
| tcp = _tcp_getSourceTCP(tcp, packet_getSourceIP(packet), packet_getSourcePort(packet)); |
This is problematic when the listening socket is closed before all of its children, or when the child sockets are close()d but need to spend some time in one of the closing states like LAST_ACK before being completely closed. When this happens, the listening socket cannot be disassociated from the network interface, which means that even if the listening socket is closed, another socket cannot bind to that same IP/port.
shadow/src/main/host/descriptor/tcp.c
Lines 709 to 737 in aa0445f
| /* | |
| * servers have to wait for all children to close. | |
| * children need to notify their parents when closing. | |
| */ | |
| if (!tcp->server || !tcp->server->children || | |
| g_hash_table_size(tcp->server->children) <= 0) { | |
| if(tcp->child && tcp->child->parent) { | |
| TCP* parent = tcp->child->parent; | |
| utility_debugAssert(parent->server); | |
| /* tell my server to stop accepting packets for me | |
| * this will destroy the child and NULL out tcp->child */ | |
| g_hash_table_remove(parent->server->children, &(tcp->child->key)); | |
| /* if i was the server's last child and its waiting to close, close it */ | |
| if((parent->state == TCPS_CLOSED) && (g_hash_table_size(parent->server->children) <= 0)) { | |
| if (disassociate) { | |
| /* this will unbind from the network interface and free socket */ | |
| host_disassociateInterface( | |
| host, PTCP, sock_ip, sock_port, peer_ip, peer_port); | |
| } | |
| } | |
| } | |
| if (disassociate) { | |
| /* TODO: we should only be disassociating non-child sockets */ | |
| host_disassociateInterface(host, PTCP, sock_ip, sock_port, peer_ip, peer_port); | |
| } | |
| } |