/* ****************************************************************************** * INTERPEAK SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipnet_sock_tcp.c,v $ $Revision: 1.42.10.18 $ * $Source: /home/interpeak/CVSRoot/ipnet2/src/ipnet_sock_tcp.c,v $ * $Author: lwang7 $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Kenneth Jonsson ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** * TCP specific socket operations */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ #ifndef IPCOM_KERNEL #define IPCOM_KERNEL #endif #include "ipnet_config.h" /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #ifdef IPTCP #define IPCOM_USE_CLIB_PROTO #include #include #include #include #include #include "ipnet_h.h" #include "ipnet_usr_sock.h" #ifdef IPCOM_WV_INSTRUMENTATION #include #endif /* **************************************************************************** * 4 DEFINES **************************************************************************** */ /* **************************************************************************** * 5 TYPES **************************************************************************** */ typedef struct Ipnet_sock_tcp_ops_struct { Ipnet_sock_inet_ops inet; Ipnet_sock_init_f network_init; Ipnet_sock_destroy_f network_destroy; Ipnet_sock_connect_f network_connect; Ipnet_sock_bind_f network_bind; } Ipnet_sock_tcp_ops; /* **************************************************************************** * 6 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 8 DATA **************************************************************************** */ /* **************************************************************************** * 9 STATIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipnet_sock_tcp_init *=========================================================================== * Description: The TCP transport layer initialization. * Parameters: * user_context - IP_TRUE if this function is called in user process context * IP_FALSE if it called from stack context. * Returns: 0 = success, <0 = error code * */ IP_STATIC int ipnet_sock_tcp_init(Ipnet_socket *sock, Ip_bool user_context) { Ipnet_sock_tcp_ops *ops; sock->proto = IP_IPPROTO_TCP; if (user_context) /* * Socket create in user-context, add an extra reference for * the TCP module */ ipcom_atomic_inc(&sock->ref_count); /* * else: the TCP module will add an reference for the user when * returning the socket in accept() */ sock->chk_offset = 16; IP_BIT_SET(sock->flags, IPNET_SOCKET_FLAG_ADDCHECKSUM); sock->tcb = ipnet_sock_alloc_private_data(sock, sizeof(Iptcp_tcb)+ipnet_conf_cache_bufsiz); sock->tcb = IP_PTR_ALIGN(sock->tcb, ipnet_conf_cache_bufsiz); iptcp_sock_init(sock); ops = (Ipnet_sock_tcp_ops *) ipnet_sock_get_ops(sock); if (ops->network_init != IP_NULL) return ops->network_init(sock, user_context); return 0; } /* *=========================================================================== * ipnet_sock_tcp_destroy *=========================================================================== * Description: Frees all resources held by the TCP socket. * Parameters: sock - The socket that is beeing destroyed. * Returns: * */ IP_STATIC void ipnet_sock_tcp_destroy(Ipnet_socket *sock) { Ipnet_sock_tcp_ops *ops; ops = (Ipnet_sock_tcp_ops *) ipnet_sock_get_ops(sock); if (ops->network_destroy != IP_NULL) ops->network_destroy(sock); iptcp_sock_free(sock); } /* *=========================================================================== * ipnet_sock_tcp_bind *=========================================================================== * Description: The TCP network layer bind. * Parameters: sock - The socket to bind. * name - The socket name. * namelen - The length of the name variable. * Returns: 0 = success, <0 = error code * */ IP_STATIC int ipnet_sock_tcp_bind(struct Ipnet_socket_struct *sock, IP_CONST struct Ip_sockaddr *name, Ip_socklen_t namelen, Ip_bool check_reuse) { int ret; Ipnet_sock_tcp_ops *ops; ops = (Ipnet_sock_tcp_ops *) ipnet_sock_get_ops(sock); ret = ops->network_bind(sock, name, namelen, check_reuse); if (ret >= 0) ret = ipnet_sock_bind_to_port(sock, name, check_reuse); return ret; } /* *=========================================================================== * ipnet_sock_tcp_connect *=========================================================================== * Description: The UDP transport layer connect. * Parameters: sock - The socket to connect. * to - The address to connect to. * tolen - The size of the 'to' buffer. * Returns: 0 = success, <0 = error code * */ IP_STATIC int ipnet_sock_tcp_connect(Ipnet_socket *sock, IP_CONST struct Ip_sockaddr *to, Ip_socklen_t tolen) { Ipnet_sock_tcp_ops *ops; int ret; ops = (Ipnet_sock_tcp_ops *) ipnet_sock_get_ops(sock); ret = ops->network_connect(sock, to, tolen); if (ret < 0) return ret; if (sock->dport == 0) /* Invalid port */ return -IP_ERRNO_EINVAL; /* The socket is a TCP socket, do the TCP connect */ if (IP_BIT_ISSET(sock->flags, IPNET_SOCKET_FLAG_CONNECTING)) /* Asynchronous connect is still not finished */ return -IP_ERRNO_EALREADY; if ((ret = ipnet_sock_set_reachable(sock)) < 0) return ret; return iptcp_connect(sock); } /* *=========================================================================== * ipnet_usr_sock_tcp_recv *=========================================================================== * Description: Receives data from a TCP stream. * Parameters: sock - The socket to receive data from. * msg - Receive parameters. * flags - IP_MSG_xxx flags. * Returns: 0 = a packet was successfully received, -1 = error. * */ IP_STATIC int ipnet_usr_sock_tcp_recv(Ipnet_socket *sock, struct Ip_msghdr *msg, int flags) { int i; int r; int bytes = 0; /* No support for ancillary data on TCP sockets */ msg->msg_controllen = 0; if (msg->msg_name != IP_NULL) { Ip_socklen_t msg_namelen = msg->msg_namelen; /* * The ipnet_sys_getname() function will only fail if this * socket is not connected. No error should be returned if * there is still data queued on this socket, iptcp_usr_recv() * will return not-connected error otherwise. */ (void)ipnet_sys_getname(sock->ipcom.fd, msg->msg_name, &msg_namelen, IP_FALSE); msg->msg_namelen = msg_namelen; } for (i = 0; i < msg->msg_iovlen; i++) { r = iptcp_usr_recv(sock, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len, flags); if (r == 0) /* EOF received */ break; if (r < 0) { if (bytes > 0) break; IPCOM_WV_EVENT_2 (IPCOM_WV_NETD_IP4_DATAPATH_EVENT, IPCOM_WV_NETD_WARNING, 1, 1, IPCOM_WV_NETDEVENT_WARNING, IPCOM_WV_NETD_RECV, ipnet_usr_sock_tcp_recv, IPCOM_WV_NETD_BADSOCK, IPCOM_WV_IPNET_SOCK_TCP_MODULE, IPCOM_WV_NETD_TCP4); IPNET_STATS(sock_recvmsg_tcp_err++); return r; } bytes += r; if ((Ip_size_t) r != msg->msg_iov[i].iov_len) /* Continue only if the current buffer was filled to its limit */ break; if (IP_BIT_ISFALSE(flags, IP_MSG_WAITALL)) /* We have some data, return the data we have so far if there isn't anything more to receive */ IP_BIT_SET(flags, IP_MSG_DONTWAIT); } return bytes; } /* *=========================================================================== * ipnet_sock_tcp_append_send_data *=========================================================================== * Description: Append data to the last packet in the send queue up to * a full MSS segment. * Parameters: send_tail - The last element of the send queue. * pbuf - Pointer to the data buffer pointer. The buffer * pointer (*pbuf) will point to the first byte * that has not been copied. * plen - A pointer to the length length of *pbuf, will * contain the number of bytes not copied when this * function returns. * mss - The maximum segment size for this segment. * Returns: Number of bytes appended. * */ IP_STATIC int ipnet_sock_tcp_append_send_data(Ipcom_pkt *send_tail, Ip_u8 **pbuf, int *plen, int mss) { int bytes_appended; int len = *plen; if (mss <= send_tail->end - send_tail->start) /* * MSS must have been decreased after the send_tail was created */ return 0; bytes_appended = IP_MIN(mss - (send_tail->end - send_tail->start), len); /* * The effective MSS might have grown (e.g. due to max_peerwnd growing), * so that this packet cannot hold the new mss... */ if (IP_UNLIKELY(bytes_appended > send_tail->maxlen - send_tail->end)) bytes_appended = send_tail->maxlen - send_tail->end; if (bytes_appended == 0) return 0; ipnet_copy_from_user(ipcom_pkt_push_back(send_tail, bytes_appended), *pbuf, bytes_appended); /* * Calculate the checksum when sending instead */ send_tail->chk = 0; if (IP_BIT_ISSET(send_tail->flags, IPCOM_PKT_FLAG_ZERO_DATA)) IP_BIT_CLR(send_tail->flags, IPCOM_PKT_FLAG_ZERO_DATA); *pbuf = *pbuf + bytes_appended; *plen -= bytes_appended; return bytes_appended; } /* *=========================================================================== * ipnet_usr_sock_tcp_pkts_from_iov *=========================================================================== * Description: Copies the user data into MSS sized packets. * Parameters: sock - The socket that will be use to send. * msg - 'msg_iov' contains the buffer(s) to send. * flags - IP_MSG_xxx flags. * offset - Number of bytes into the msg->msg_iov buffers * where the copy into packets should start. * total_buf_len - the sum of the length of all * msg->msg_iov buffers in 'msg'. * prslt - Points to result structure where the constructed packet * and transient error (if any) are returned. * prslt->pkt->next points to the next packet in case * more than one packet was allocated. * Returns: <0 = error code (-IPNET_ERRNO_xxx) * 0 = this operation released the socket lock, the * sendmsg operation must be restarted * >0 = number of bytes that have been allocated in the * socket send buffer and copied into the * packet(s) pointed to by prslt->pkt. */ IP_STATIC int ipnet_usr_sock_tcp_pkts_from_iov(Ipnet_socket *sock, IP_CONST struct Ip_msghdr *msg, int flags, int offset, int total_buf_len, Ipnet_pkt_result *prslt) { Ipcom_pkt *pkt_head; Ipcom_pkt *pkt_tail; Iptcp_tcb *tcb = sock->tcb; int mss = tcb->mss; Ip_u8 *buf; int buf_len; int i = 0; int len = 0; int bytes_to_copy = total_buf_len - offset; Ip_bool non_blocking; Ip_bool urgent = IP_BIT_ISSET(flags, IP_MSG_OOB); if (tcb->max_peerwnd > 0 && (int)tcb->max_peerwnd < mss) /* To support tiny window */ mss = (int)tcb->max_peerwnd; prslt->pkt = IP_NULL; prslt->transient_err = 0; if (IP_UNLIKELY(IP_BIT_ISFALSE(sock->flags, (IPNET_SOCKET_FLAG_CONNECTED | IPNET_SOCKET_FLAG_CONNECTING)))) return -IP_ERRNO_ENOTCONN; if (IP_UNLIKELY(IP_BIT_ISSET(sock->flags, IPNET_SOCKET_FLAG_WRITE_CLOSED))) return -IP_ERRNO_ESHUTDOWN; /* * Note, a network task may change sock->flags asynchronously, after the * check above, so it is possible that the socket becomes disconnected * during what we do below. (The caller does hold a reference to the * socket, so at least it won't disappear.) */ /* * If offset == total_buf_len, which can in particular happen * if both are zero, then the loop immediately below can * access beyond the end of the iovec array. Avoid this. */ if (offset == total_buf_len) return 0; for (buf_len = 0; buf_len <= 0; offset = - buf_len) { struct Ip_iovec *iov = &msg->msg_iov[i++]; buf = (Ip_u8 *) iov->iov_base + offset; buf_len = (int) iov->iov_len - offset; } if (IP_LIKELY((urgent == IP_FALSE) && (IP_BIT_ISFALSE(sock->flags, IPNET_SOCKET_FLAG_TCP_NODELAY)))) { iptcp_partial_lock(tcb->send.partial_lock); if (tcb->send.partial != IP_NULL) { len = ipnet_sock_tcp_append_send_data(tcb->send.partial, &buf, &buf_len, mss); if (len > 0) ipcom_atomic_add(&sock->snd_bytes, len); } iptcp_partial_unlock(tcb->send.partial_lock); if (len == bytes_to_copy) /* * All data that should be sent was queued to the end of the * partial finished TCP segment */ return len; } /* * else: urgent packet must be sent in a separate signal since the * flag field must have IP_MSG_OOB set for correct processing. */ non_blocking = IP_BIT_ISSET(sock->flags, IPNET_SOCKET_FLAG_NONBLOCKING) || IP_BIT_ISSET(flags, IP_MSG_DONTWAIT); bytes_to_copy = 0; do { int bytes_to_alloc = IP_MIN(mss, buf_len - bytes_to_copy); if (ipnet_alloc_snd_bytes(sock, bytes_to_alloc)) { bytes_to_copy += bytes_to_alloc; } else { int lowat; int err; if (bytes_to_copy > 0 || non_blocking) break; lowat = IP_MAX(mss, (int) sock->tcb->send.lowat); lowat = IP_MIN(lowat, sock->send_max_bytes); err = ipnet_usr_sock_wait_until_writable(sock, lowat, IP_NULL); /* * If an EINTR occurred while waiting, let the caller know. */ if (IP_LIKELY(err == 0)) { err = sock->ipcom.so_errno; /* * If a TX shutdown occurs concurrently with this send, this task * may be awakened by the TCP state change before sock->ipcom.so_errno * has been set to EPIPE. Also, regardless of whether so_errno has * been set (or possibly cleared), we don't want to try again if the * socket is WRITE_CLOSED, as we would spin. * (Seen in the iptestengine ipnet.tcp.link_failure test.) */ if (0 == err && IP_BIT_ISSET(sock->flags, IPNET_SOCKET_FLAG_WRITE_CLOSED)) err = IP_ERRNO_EPIPE; } else prslt->transient_err = err; if (err != 0) { /* The caller holds a reference */ ipcom_atomic_sub(&sock->snd_bytes, bytes_to_copy); /* * If ANY bytes were successfully appended * we MUST return the length, or else the application * might resend them, corrupting its data stream. */ return (len > 0 ? len : -err); } } } while (bytes_to_copy < buf_len); pkt_head = IP_NULL; pkt_tail = IP_NULL; while (bytes_to_copy > 0) { Ipcom_pkt *pkt; int seg_len = IP_MIN(bytes_to_copy, mss); pkt = ipcom_pkt_malloc(mss + sock->max_hdrspace, IP_FLAG_FC_STACKCONTEXT | (non_blocking ? 0 : IP_FLAG_FC_BLOCKOK)); if (IP_UNLIKELY(pkt == IP_NULL)) break; ipcom_pkt_reserve_data(pkt, mss); pkt->chk = ipnet_in_checksum_memcpy(ipcom_pkt_push_back(pkt, seg_len), buf, seg_len); if (0 == pkt->chk) IP_BIT_SET(pkt->flags, IPCOM_PKT_FLAG_ZERO_DATA); else IP_BIT_CLR(pkt->flags, IPCOM_PKT_FLAG_ZERO_DATA); /* * Add this packet to the list of packets that will be sent * to the network task. */ if (pkt_head == IP_NULL) pkt_head = pkt; else pkt_tail->next = pkt; pkt_tail = pkt; len += seg_len; buf += seg_len; bytes_to_copy -= seg_len; } if (IP_LIKELY(pkt_tail != IP_NULL)) { if (IP_UNLIKELY(urgent)) IPNET_MARK_PKT_AS_URGENT(pkt_tail); iptcp_partial_lock(tcb->send.partial_lock); tcb->send.partial = pkt_tail; iptcp_partial_unlock(tcb->send.partial_lock); } prslt->pkt = pkt_head; return (len == 0 ? -IP_ERRNO_EWOULDBLOCK : len); } /* **************************************************************************** * 10 GLOBAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipnet_sock_tcp_zombie_send_pkt_free *=========================================================================== * Description: Free packet and remove TX socket buffer space allocated. * This routine is called by ipcom_sendmsg() in rare cases * when it has obtained a packet from * ipnet_usr_sock_tcp_pkts_from_iov() but finds itself unable * to send that packet. * Parameters: sock - The socket from which TX space was allocated for pkt * pkt - the packet to free. * Returns: N/A * */ IP_GLOBAL void ipnet_sock_tcp_zombie_send_pkt_free(Ipnet_socket *sock, IP_CONST struct Ip_msghdr *msg, Ipcom_pkt *pkt) { Ipcom_pkt * next; Iptcp_tcb *tcb = sock->tcb; int totlen; /* * Note, tcb->send.partial is likely the tail packet * in the list headed by pkt. We are about to free * the pkt list so we must invalidate tcb->send.partial. * * WARNING: In the case of a concurrent send to the same * socket by another context than this one, it is possible * that the other context has ALREADY appended data * to the last packet in the chain we are about to free. * This is a bug, but it is worth noting that this code * is called only in the case that the system memory heap * is exhausted. Further, concurrent sends by different * contexts on the same TCP socket are themselves rare, * first because of uncontrolled interleaving of the data * from the different sends, and second because we already * do not really support concurrent sends on the same TCP * socket for other reasons (e.g., race conditions which * wind up with tcb->send.partial pointing at a packet which * is not the last one in the data stream as delivered * in IPNET_SIG_SENDMSG requests...). */ iptcp_partial_lock(tcb->send.partial_lock); tcb->send.partial = IP_NULL; iptcp_partial_unlock(tcb->send.partial_lock); for (totlen = 0; pkt != IP_NULL; pkt = next) { next = pkt->next; pkt->next = IP_NULL; totlen += pkt->end - pkt->start; ipcom_pkt_free (pkt); } /* The caller holds a reference */ ipcom_atomic_sub(&sock->snd_bytes, totlen); return; } /* *=========================================================================== * ipnet_sock_tcp_register *=========================================================================== * Description: Registers the TCP protocol for IPv4 and/or IPv6. * Parameters: * Returns: 0 = success, <0 = error code. * */ IP_GLOBAL int ipnet_sock_tcp_register(void) { Ipnet_sock_tcp_ops *ops; int domains[] = { #ifdef IPCOM_USE_INET IP_AF_INET, #endif #ifdef IPCOM_USE_INET6 IP_AF_INET6, #endif -1 }; int protos[] = { 0, IP_IPPROTO_TCP, -1 }; int *domain; int *proto; int ret; for (domain = domains; *domain >= 0; domain++) for (proto = protos; *proto >= 0; proto++) { ops = ipcom_calloc(1, sizeof(*ops)); if (ops == IP_NULL) { IPCOM_LOG0(CRIT, "Could not register the TCP protocol, out of memory"); IP_PANIC(); return -IP_ERRNO_ENOMEM; } switch (*domain) { #ifdef IPCOM_USE_INET case IP_AF_INET: ipnet_sock_ip4_get_ops(&ops->inet); break; #endif #ifdef IPCOM_USE_INET6 case IP_AF_INET6: ipnet_sock_ip6_get_ops(&ops->inet); break; #endif default: break; } ops->network_bind = ops->inet.network_bind; ops->inet.network_bind = ipnet_sock_tcp_bind; ops->network_init = ops->inet.sock.init; ops->inet.sock.init = ipnet_sock_tcp_init; ops->network_destroy = ops->inet.sock.destroy; ops->inet.sock.destroy = ipnet_sock_tcp_destroy; ops->network_connect = ops->inet.sock.connect; ops->inet.sock.connect = ipnet_sock_tcp_connect; ops->inet.sock.type = IP_SOCK_STREAM; ops->inet.sock.proto = *proto; ops->inet.sock.allow_async_send = IP_TRUE; ops->inet.sock.accept = iptcp_accept; ops->inet.sock.close = iptcp_close; ops->inet.sock.listen = iptcp_listen; ops->inet.sock.usr_recv = ipnet_usr_sock_tcp_recv; ops->inet.sock.pkts_from_iov = ipnet_usr_sock_tcp_pkts_from_iov; ops->inet.sock.send = iptcp_send; ops->inet.sock.shutdown = iptcp_shutdown; ops->inet.sock.getopt = iptcp_getsockopt; ops->inet.sock.setopt = iptcp_setsockopt; ops->inet.sock.ioctl = iptcp_ioctl; ops->inet.sock.hdr_space += IPTCP_TCP_HDR_SIZE; ops->inet.sock.extra_sock_space += (sizeof(Iptcp_tcb) + ipnet_conf_cache_bufsiz); ret = ipnet_sock_register_ops(&ops->inet.sock); if (ret < 0) { ipcom_free (ops); return ret; } } return 0; } /* **************************************************************************** * 11 PUBLIC FUNCTIONS **************************************************************************** */ #endif /* IPTCP */ /* **************************************************************************** * END OF FILE **************************************************************************** */