Files
2026-07-30 11:19:44 +05:30

762 lines
25 KiB
C

/*
******************************************************************************
* 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 <kenneth@interpeak.se>
******************************************************************************
*/
/*
****************************************************************************
* 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 <ipcom_clib.h>
#include <ipcom_cstyle.h>
#include <ipcom_syslog.h>
#include <ipcom_type.h>
#include <iptcp.h>
#include "ipnet_h.h"
#include "ipnet_usr_sock.h"
#ifdef IPCOM_WV_INSTRUMENTATION
#include <ipcom_windview.h>
#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
****************************************************************************
*/