transports: Resize socket send buffer if needed

This was originally added in openbmc/pldm to make the socket send buffer
big enough to send a message if the message is longer than the current
send buffer size.

When you call set_sock_opt with buffer size X, given that X is within a
certain range, the send buffer is actually set to 2*X. get_sock_opt
returns the 2*X value, not X. So add in some helper functions.

Change-Id: I8ded9357db4268cd264cf0ecfb80479223106c09
Signed-off-by: Rashmica Gupta <rashmica@linux.ibm.com>
diff --git a/src/transport/af-mctp.c b/src/transport/af-mctp.c
index 5084f7d..2ba854e 100644
--- a/src/transport/af-mctp.c
+++ b/src/transport/af-mctp.c
@@ -4,9 +4,11 @@
 #include "container-of.h"
 #include "libpldm/pldm.h"
 #include "libpldm/transport.h"
+#include "socket.h"
 #include "transport.h"
 
 #include <errno.h>
+#include <limits.h>
 #include <linux/mctp.h>
 #include <poll.h>
 #include <stdlib.h>
@@ -21,6 +23,7 @@
 	struct pldm_transport transport;
 	int socket;
 	pldm_tid_t tid_eid_map[MCTP_MAX_NUM_EID];
+	struct pldm_socket_sndbuf socket_send_buf;
 };
 
 #define transport_to_af_mctp(ptr)                                              \
@@ -119,6 +122,12 @@
 	addr.smctp_type = MCTP_MSG_TYPE_PLDM;
 	addr.smctp_tag = MCTP_TAG_OWNER;
 
+	if (req_msg_len > INT_MAX ||
+	    pldm_socket_sndbuf_accomodate(&(af_mctp->socket_send_buf),
+					  (int)req_msg_len)) {
+		return PLDM_REQUESTER_SEND_FAIL;
+	}
+
 	ssize_t rc = sendto(af_mctp->socket, pldm_req_msg, req_msg_len, 0,
 			    (struct sockaddr *)&addr, sizeof(addr));
 	if (rc == -1) {
@@ -149,6 +158,14 @@
 		free(af_mctp);
 		return -1;
 	}
+
+	if (pldm_socket_sndbuf_init(&af_mctp->socket_send_buf,
+				    af_mctp->socket)) {
+		close(af_mctp->socket);
+		free(af_mctp);
+		return -1;
+	}
+
 	*ctx = af_mctp;
 	return 0;
 }
diff --git a/src/transport/mctp-demux.c b/src/transport/mctp-demux.c
index 466d33c..2518d93 100644
--- a/src/transport/mctp-demux.c
+++ b/src/transport/mctp-demux.c
@@ -4,9 +4,11 @@
 #include "container-of.h"
 #include "libpldm/pldm.h"
 #include "libpldm/transport.h"
+#include "socket.h"
 #include "transport.h"
 
 #include <errno.h>
+#include <limits.h>
 #include <poll.h>
 #include <stdlib.h>
 #include <string.h>
@@ -24,6 +26,7 @@
 	/* In the future this probably needs to move to a tid-eid-uuid/network
 	 * id mapping for multi mctp networks */
 	pldm_tid_t tid_eid_map[MCTP_MAX_NUM_EID];
+	struct pldm_socket_sndbuf socket_send_buf;
 };
 
 #define transport_to_demux(ptr)                                                \
@@ -181,6 +184,12 @@
 	msg.msg_iov = iov;
 	msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
 
+	if (req_msg_len > INT_MAX ||
+	    pldm_socket_sndbuf_accomodate(&(demux->socket_send_buf),
+					  (int)req_msg_len)) {
+		return PLDM_REQUESTER_SEND_FAIL;
+	}
+
 	ssize_t rc = sendmsg(demux->socket, &msg, 0);
 	if (rc == -1) {
 		return PLDM_REQUESTER_SEND_FAIL;
@@ -211,6 +220,13 @@
 		free(demux);
 		return -1;
 	}
+
+	if (pldm_socket_sndbuf_init(&demux->socket_send_buf, demux->socket)) {
+		close(demux->socket);
+		free(demux);
+		return -1;
+	}
+
 	*ctx = demux;
 	return 0;
 }
@@ -249,6 +265,13 @@
 		free(demux);
 		return NULL;
 	}
+
+	if (pldm_socket_sndbuf_init(&demux->socket_send_buf, demux->socket)) {
+		close(demux->socket);
+		free(demux);
+		return NULL;
+	}
+
 	return demux;
 }
 
diff --git a/src/transport/meson.build b/src/transport/meson.build
index 1fcc06a..511b1c4 100644
--- a/src/transport/meson.build
+++ b/src/transport/meson.build
@@ -1,5 +1,6 @@
 libpldm_sources += files(
   'af-mctp.c',
   'mctp-demux.c',
+  'socket.c',
   'transport.c'
 )
diff --git a/src/transport/socket.c b/src/transport/socket.c
new file mode 100644
index 0000000..6c299a8
--- /dev/null
+++ b/src/transport/socket.c
@@ -0,0 +1,87 @@
+#include "socket.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+
+int pldm_socket_sndbuf_init(struct pldm_socket_sndbuf *ctx, int socket)
+{
+	FILE *fp;
+	long max_buf_size;
+	char line[128];
+	char *endptr;
+
+	if (socket == -1) {
+		return -1;
+	}
+	ctx->socket = socket;
+
+	fp = fopen("/proc/sys/net/core/wmem_max", "r");
+	if (fp == NULL || fgets(line, sizeof(line), fp) == NULL) {
+		fclose(fp);
+		return -1;
+	}
+
+	errno = 0;
+	max_buf_size = strtol(line, &endptr, 10);
+	if (errno != 0 || endptr == line) {
+		fclose(fp);
+		return -1;
+	}
+
+	fclose(fp);
+
+	if (max_buf_size > INT_MAX) {
+		max_buf_size = INT_MAX;
+	}
+	ctx->max_size = (int)max_buf_size;
+
+	if (pldm_socket_sndbuf_get(ctx)) {
+		return -1;
+	}
+
+	return 0;
+}
+
+int pldm_socket_sndbuf_accomodate(struct pldm_socket_sndbuf *ctx, int msg_len)
+{
+	if (msg_len < ctx->size) {
+		return 0;
+	}
+	/* If message is bigger than the max size, don't return a failure. Set
+	 * the buffer to the max size and see what happens. We don't know how
+	 * much of the extra space the kernel actually uses so let it tell us if
+	 * there wasn't enough space */
+	if (msg_len > ctx->max_size) {
+		msg_len = ctx->max_size;
+	}
+	if (ctx->size == ctx->max_size) {
+		return 0;
+	}
+	int rc = setsockopt(ctx->socket, SOL_SOCKET, SO_SNDBUF, &(msg_len),
+			    sizeof(msg_len));
+	if (rc == -1) {
+		return -1;
+	}
+	ctx->size = msg_len;
+	return 0;
+}
+
+int pldm_socket_sndbuf_get(struct pldm_socket_sndbuf *ctx)
+{
+	/* size returned by getsockopt is the actual size of the buffer - twice
+	 * the size of the value used by setsockopt. So for consistency, return
+	 * half of the buffer size */
+	int buf_size;
+	socklen_t optlen = sizeof(buf_size);
+	int rc = getsockopt(ctx->socket, SOL_SOCKET, SO_SNDBUF, &(buf_size),
+			    &optlen);
+	if (rc == -1) {
+		return -1;
+	}
+	ctx->size = buf_size / 2;
+	return 0;
+}
diff --git a/src/transport/socket.h b/src/transport/socket.h
new file mode 100644
index 0000000..17751ca
--- /dev/null
+++ b/src/transport/socket.h
@@ -0,0 +1,14 @@
+#ifndef LIBPLDM_SRC_TRANSPORT_SOCKET_H
+#define LIBPLDM_SRC_TRANSPORT_SOCKET_H
+
+struct pldm_socket_sndbuf {
+	int size;
+	int socket;
+	int max_size;
+};
+
+int pldm_socket_sndbuf_init(struct pldm_socket_sndbuf *ctx, int socket);
+int pldm_socket_sndbuf_accomodate(struct pldm_socket_sndbuf *ctx, int msg_len);
+int pldm_socket_sndbuf_get(struct pldm_socket_sndbuf *ctx);
+
+#endif // LIBPLDM_SRC_TRANSPORT_SOCKET_H