/**************************************************************************** * net/tcp/tcp_close.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #ifdef CONFIG_NET_TCP #include #include #include #include #include #include #include #include "netdev/netdev.h" #include "devif/devif.h" #include "tcp/tcp.h" #include "socket/socket.h" /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: tcp_close_work ****************************************************************************/ static void tcp_close_work(FAR void *param) { FAR struct tcp_conn_s *conn = (FAR struct tcp_conn_s *)param; net_lock(); if (conn && conn->crefs == 0) { /* Stop the network monitor for all sockets */ tcp_stop_monitor(conn, TCP_CLOSE); tcp_free(conn); } net_unlock(); } /**************************************************************************** * Name: tcp_close_eventhandler ****************************************************************************/ static uint16_t tcp_close_eventhandler(FAR struct net_driver_s *dev, FAR void *pvpriv, uint16_t flags) { FAR struct tcp_conn_s *conn = pvpriv; ninfo("flags: %04x\n", flags); /* TCP_DISCONN_EVENTS: * TCP_CLOSE: The remote host has closed the connection * TCP_ABORT: The remote host has aborted the connection * TCP_TIMEDOUT: The remote did not respond, the connection timed out * NETDEV_DOWN: The network device went down */ if ((flags & TCP_DISCONN_EVENTS) != 0) { /* The disconnection is complete. Wake up the waiting thread with an * appropriate result. Success is returned in these cases: * * * TCP_CLOSE indicates normal successful closure. The TCP_CLOSE * event is sent when the remote ACKs the outgoing FIN in the * FIN_WAIT_1 state. That is the appropriate time for the * application to close the socket. * * NOTE: The underlying connection, however, will persist, waiting * for the FIN to be returned by the remote in the TIME_WAIT state. * * * TCP_ABORT is less likely but still means that the socket was * closed, albeit abnormally due to a RST from the remote. * * * TCP_TIMEDOUT would be reported in this context if there is no * ACK response to the FIN in the FIN_WAIT_2 state. The socket will * again be closed abnormally. * * This is the only true error case. * * * NETDEV_DOWN would indicate that the network went down before the * close completed. A non-standard ENODEV error will be returned * in this case. The socket will be left in a limbo state if the * network is taken down but should recover later when the * NETWORK_DOWN event is processed further. */ goto end_wait; } /* Check if all outstanding bytes have been ACKed. * * Note: in case of passive close, this ensures our FIN is acked. */ else if (conn->tx_unacked != 0 #ifdef CONFIG_NET_TCP_WRITE_BUFFERS || !sq_empty(&conn->write_q) #endif /* CONFIG_NET_TCP_WRITE_BUFFERS */ ) { /* No... we are still waiting for ACKs. Drop any received data, but * do not yet report TCP_CLOSE in the response. */ dev->d_len = 0; flags &= ~TCP_NEWDATA; ninfo("waiting for ack\n"); } else { /* Note: the following state shouldn't reach here because * * FIN_WAIT_1, CLOSING, LAST_ACK * should have tx_unacked != 0, already handled above * * CLOSED, TIME_WAIT * a TCP_CLOSE callback should have already cleared this callback * when transitioning to these states. * * FIN_WAIT_2 * new data is dropped by tcp_input without invoking tcp_callback. * timer is handled by tcp_timer without invoking tcp_callback. * TCP_CLOSE is handled above. */ DEBUGASSERT(conn->tcpstateflags == TCP_ESTABLISHED); /* Drop data received in this state and make sure that TCP_CLOSE * is set in the response */ #ifdef CONFIG_NET_TCP_WRITE_BUFFERS /* We don't need the send callback anymore. */ if (conn->sndcb != NULL) { conn->sndcb->flags = 0; conn->sndcb->event = NULL; /* The callback will be freed by tcp_free. */ conn->sndcb = NULL; } #endif dev->d_len = 0; flags = (flags & ~TCP_NEWDATA) | TCP_CLOSE; } UNUSED(conn); /* May not be used */ return flags; end_wait: if (conn->clscb != NULL) { tcp_callback_free(conn, conn->clscb); conn->clscb = NULL; } /* Free network resources */ work_queue(LPWORK, &conn->clswork, tcp_close_work, conn, 0); return flags; } /**************************************************************************** * Name: tcp_close_txnotify * * Description: * Notify the appropriate device driver that we have data ready to * be sent (TCP) * * Input Parameters: * psock - Socket state structure * conn - The TCP connection structure * * Returned Value: * None * ****************************************************************************/ static inline void tcp_close_txnotify(FAR struct socket *psock, FAR struct tcp_conn_s *conn) { #ifdef CONFIG_NET_IPv4 #ifdef CONFIG_NET_IPv6 /* If both IPv4 and IPv6 support are enabled, then we will need to select * the device driver using the appropriate IP domain. */ if (psock->s_domain == PF_INET) #endif { /* Notify the device driver that send data is available */ netdev_ipv4_txnotify(conn->u.ipv4.laddr, conn->u.ipv4.raddr); } #endif /* CONFIG_NET_IPv4 */ #ifdef CONFIG_NET_IPv6 #ifdef CONFIG_NET_IPv4 else /* if (psock->s_domain == PF_INET6) */ #endif /* CONFIG_NET_IPv4 */ { /* Notify the device driver that send data is available */ DEBUGASSERT(psock->s_domain == PF_INET6); netdev_ipv6_txnotify(conn->u.ipv6.laddr, conn->u.ipv6.raddr); } #endif /* CONFIG_NET_IPv6 */ } /**************************************************************************** * Name: tcp_close_disconnect * * Description: * Break any current TCP connection * * Input Parameters: * conn - TCP connection structure * * Returned Value: * None * * Assumptions: * Called from normal user-level logic * ****************************************************************************/ static inline int tcp_close_disconnect(FAR struct socket *psock) { FAR struct tcp_conn_s *conn; int ret = OK; /* Interrupts are disabled here to avoid race conditions */ net_lock(); conn = (FAR struct tcp_conn_s *)psock->s_conn; DEBUGASSERT(conn != NULL); #ifdef CONFIG_NET_SOLINGER /* SO_LINGER * Lingers on a close() if data is present. This option controls the * action taken when unsent messages queue on a socket and close() is * performed. If SO_LINGER is set, the system shall block the calling * thread during close() until it can transmit the data or until the * time expires. If SO_LINGER is not specified, and close() is issued, * the system handles the call in a way that allows the calling thread * to continue as quickly as possible. This option takes a linger * structure, as defined in the header, to specify the * state of the option and linger interval. */ if (_SO_GETOPT(conn->sconn.s_options, SO_LINGER)) { /* Wait until for the buffered TX data to be sent. */ ret = tcp_txdrain(psock, _SO_TIMEOUT(conn->sconn.s_linger)); if (ret < 0) { /* tcp_txdrain may fail, but that won't stop us from closing * the socket. */ nerr("ERROR: tcp_txdrain() failed: %d\n", ret); } } #endif /* Discard our reference to the connection */ conn->crefs = 0; /* TCP_ESTABLISHED * We need to initiate an active close and wait for its completion. * * TCP_LAST_ACK * We still need to wait for the ACK for our FIN, possibly * retransmitting the FIN, before disposing the connection. */ if ((conn->tcpstateflags == TCP_ESTABLISHED || conn->tcpstateflags == TCP_LAST_ACK) && (conn->clscb = tcp_callback_alloc(conn)) != NULL) { /* Set up to receive TCP data event callbacks */ conn->clscb->flags = TCP_NEWDATA | TCP_POLL | TCP_DISCONN_EVENTS; conn->clscb->event = tcp_close_eventhandler; conn->clscb->priv = conn; /* reference for event handler to free cb */ /* Notify the device driver of the availability of TX data */ tcp_close_txnotify(psock, conn); } else { /* Stop the network monitor for all sockets */ tcp_stop_monitor(conn, TCP_CLOSE); /* Free network resources */ tcp_free(conn); } psock->s_conn = NULL; net_unlock(); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: tcp_close * * Description: * Break any current TCP connection * * Input Parameters: * psock - An instance of the internal socket structure. * * Assumptions: * Called from normal user-level logic * ****************************************************************************/ int tcp_close(FAR struct socket *psock) { FAR struct tcp_conn_s *conn = psock->s_conn; /* Perform the disconnection now */ tcp_unlisten(conn); /* No longer accepting connections */ /* Break any current connections and close the socket */ return tcp_close_disconnect(psock); } #endif /* CONFIG_NET_TCP */