diff mbox series

[2/3,2/3] usbipd: add long options to set TCP_KEEPIDLE/TCP_KEEPCNT/TCP_KEEPINTVL socket options

Message ID 20250516075204.7950-2-vadimgrn@gmail.com
State New
Headers show
Series [1/3,1/3] usbipd: enable SO_KEEPALIVE socket option for accepted connection | expand

Commit Message

Vadym Hrynchyshyn May 16, 2025, 7:52 a.m. UTC
- Add usbip daemon options "--tcp-keepidle SECS", "--tcp-keepcnt CNT",
      "--tcp-keepintvl SECS". A user can add these parameters if default
      values do not satisfy him/her.

    - If the option is passed, set corresponding socket option for accepted
      connection. For example, if "--tcp-keepidle 60" is passed, call
      setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, ...).

    - configure.ac handles presence of these socket options.
      If TCP_KEEPIDLE/TCP_KEEPCNT/TCP_KEEPINTVL are not available,
      corresponding program options will not be added.

Signed-off-by: Vadym Hrynchyshyn <vadimgrn@gmail.com>
---
 tools/usb/usbip/configure.ac        | 24 +++++++-
 tools/usb/usbip/src/usbip_network.c | 85 +++++++++++++++++++++++++++++
 tools/usb/usbip/src/usbip_network.h | 14 +++++
 tools/usb/usbip/src/usbipd.c        | 59 ++++++++++++++++++--
 4 files changed, 176 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/tools/usb/usbip/configure.ac b/tools/usb/usbip/configure.ac
index 8debf934f8b7..439377cfe23b 100644
--- a/tools/usb/usbip/configure.ac
+++ b/tools/usb/usbip/configure.ac
@@ -29,7 +29,7 @@  AC_PROG_MAKE_SET
 AC_HEADER_DIRENT
 AC_HEADER_STDC
 AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdint.h stdlib.h dnl
-		  string.h sys/socket.h syslog.h unistd.h])
+		  string.h sys/socket.h syslog.h unistd.h netinet/tcp.h])
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_TYPE_INT32_T
@@ -107,5 +107,27 @@  AC_ARG_WITH([fortify],
 			    dnl [ACTION-IF-NOT-GIVEN]
 			    [AC_MSG_RESULT([default])])
 
+AC_MSG_CHECKING([whether tcp keepalive options are available])
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM(
+		[ #include <sys/socket.h>
+		  #include <arpa/inet.h>
+		  #include <netinet/tcp.h> ],
+		[
+			int n = 0;
+			setsockopt(-1, IPPROTO_TCP, TCP_KEEPIDLE, &n, sizeof(n));
+			setsockopt(-1, IPPROTO_TCP, TCP_KEEPCNT, &n, sizeof(n));
+			setsockopt(-1, IPPROTO_TCP, TCP_KEEPINTVL, &n, sizeof(n));
+		]
+	)],
+	[
+		AC_MSG_RESULT([yes])
+		AC_DEFINE([HAVE_KEEPALIVE_OPTS], [1],
+			  [Define to 1 if TCP_KEEPIDLE/TCP_KEEPCNT/TCP_KEEPINTVL
+			   socket-level options are available.])
+	],
+	[ AC_MSG_RESULT([no]) ],
+)
+
 AC_CONFIG_FILES([Makefile libsrc/Makefile src/Makefile])
 AC_OUTPUT
diff --git a/tools/usb/usbip/src/usbip_network.c b/tools/usb/usbip/src/usbip_network.c
index ed4dc8c14269..15787044a7c9 100644
--- a/tools/usb/usbip/src/usbip_network.c
+++ b/tools/usb/usbip/src/usbip_network.c
@@ -50,6 +50,34 @@  void usbip_setup_port_number(char *arg)
 	info("using port %d (\"%s\")", usbip_port, usbip_port_string);
 }
 
+int usbip_to_int(const char *name, const char *val, int maxval)
+{
+	char *end;
+	long value = strtol(val, &end, 10);
+
+	if (end == val) {
+		err("%s: could not parse '%s' as a decimal integer", name, val);
+		return 0;
+	}
+
+	if (*end != '\0') {
+		err("%s: garbage at end of '%s'", name, val);
+		return 0;
+	}
+
+	if (value <= 0) {
+		err("%s: must be greater than zero, actual %s", name, val);
+		return 0;
+	}
+
+	if (value > maxval) {
+		err("%s: %s is too high, max=%d", name, val, maxval);
+		return 0;
+	}
+
+	return value;
+}
+
 uint32_t usbip_net_pack_uint32_t(int pack, uint32_t num)
 {
 	uint32_t i;
@@ -243,6 +271,63 @@  int usbip_net_set_keepalive(int sockfd)
 	return ret;
 }
 
+#if HAVE_KEEPALIVE_OPTS
+
+int usbip_net_set_keepidle(int sockfd, int seconds)
+{
+	int ret;
+
+	ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &seconds, sizeof(seconds));
+	if (ret < 0)
+		dbg("setsockopt: TCP_KEEPIDLE");
+
+	return ret;
+}
+
+int usbip_net_set_keepcnt(int sockfd, int cnt)
+{
+	int ret;
+
+	ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
+	if (ret < 0)
+		dbg("setsockopt: TCP_KEEPCNT");
+
+	return ret;
+}
+
+int usbip_net_set_keepintvl(int sockfd, int seconds)
+{
+	int ret;
+
+	ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &seconds, sizeof(seconds));
+	if (ret < 0)
+		dbg("setsockopt: TCP_KEEPINTVL");
+
+	return ret;
+}
+
+#else
+
+int usbip_net_set_keepidle(int, int)
+{
+	dbg("setsockopt: TCP_KEEPIDLE is not supported");
+	return -1;
+}
+
+int usbip_net_set_keepcnt(int, int)
+{
+	dbg("setsockopt: TCP_KEEPCNT is not supported");
+	return -1;
+}
+
+int usbip_net_set_keepintvl(int, int)
+{
+	dbg("setsockopt: TCP_KEEPINTVL is not supported");
+	return -1;
+}
+
+#endif /* HAVE_KEEPALIVE_OPTS */
+
 int usbip_net_set_v6only(int sockfd)
 {
 	const int val = 1;
diff --git a/tools/usb/usbip/src/usbip_network.h b/tools/usb/usbip/src/usbip_network.h
index 83b4c5344f72..9611ddede0cf 100644
--- a/tools/usb/usbip/src/usbip_network.h
+++ b/tools/usb/usbip/src/usbip_network.h
@@ -18,6 +18,17 @@  extern int usbip_port;
 extern char *usbip_port_string;
 void usbip_setup_port_number(char *arg);
 
+/**
+ * usbip_to_int - convert string to positive integer
+ * @name:	name of the @val for logging
+ * @val:	string to convert to integer using base 10
+ * @maxval:	max allowed result value
+ *
+ * Return: positive integer on success, zero if conversion
+ *	   is not possible or value is out of range [1...maxval].
+ */
+int usbip_to_int(const char *name, const char *val, int maxval);
+
 /* ---------------------------------------------------------------------- */
 /* Common header for all the kinds of PDUs. */
 struct op_common {
@@ -172,6 +183,9 @@  int usbip_net_recv_op_common(int sockfd, uint16_t *code, int *status);
 int usbip_net_set_reuseaddr(int sockfd);
 int usbip_net_set_nodelay(int sockfd);
 int usbip_net_set_keepalive(int sockfd);
+int usbip_net_set_keepidle(int sockfd, int seconds);
+int usbip_net_set_keepcnt(int sockfd, int cnt);
+int usbip_net_set_keepintvl(int sockfd, int seconds);
 int usbip_net_set_v6only(int sockfd);
 int usbip_net_tcp_connect(char *hostname, char *port);
 
diff --git a/tools/usb/usbip/src/usbipd.c b/tools/usb/usbip/src/usbipd.c
index d89633d8f799..8d83f870b89c 100644
--- a/tools/usb/usbip/src/usbipd.c
+++ b/tools/usb/usbip/src/usbipd.c
@@ -46,6 +46,10 @@ 
 
 #define DEFAULT_PID_FILE "/var/run/" PROGNAME ".pid"
 
+static int tcp_keepidle;
+static int tcp_keepcnt;
+static int tcp_keepintvl;
+
 static const char usbip_version_string[] = PACKAGE_STRING;
 
 static const char usbipd_help_string[] =
@@ -75,6 +79,19 @@  static const char usbipd_help_string[] =
 	"	-tPORT, --tcp-port PORT\n"
 	"		Listen on TCP/IP port PORT.\n"
 	"\n"
+#if HAVE_KEEPALIVE_OPTS
+	"	--tcp-keepidle SECONDS\n"
+	"		Number of SECONDS a connection needs to be idle\n"
+	"		before TCP begins sending out keep-alive probes.\n"
+	"\n"
+	"	--tcp-keepcnt PROBES\n"
+	"		Max. number of TCP keep-alive PROBES to send\n"
+	"		before killing the connection.\n"
+	"\n"
+	"	--tcp-keepintvl SECONDS\n"
+	"		Number of SECONDS between TCP keep-alive probes.\n"
+	"\n"
+#endif
 	"	-h, --help\n"
 	"		Print this help.\n"
 	"\n"
@@ -88,6 +105,24 @@  static void usbipd_help(void)
 	printf("%s\n", usbipd_help_string);
 }
 
+static void set_socket_options(int sockfd)
+{
+	/* should set TCP_NODELAY for usbip */
+	usbip_net_set_nodelay(sockfd);
+
+	if (usbip_net_set_keepalive(sockfd) < 0)
+		return;
+
+	if (tcp_keepidle)
+		usbip_net_set_keepidle(sockfd, tcp_keepidle);
+
+	if (tcp_keepcnt)
+		usbip_net_set_keepcnt(sockfd, tcp_keepcnt);
+
+	if (tcp_keepintvl)
+		usbip_net_set_keepintvl(sockfd, tcp_keepintvl);
+}
+
 static int recv_request_import(int sockfd)
 {
 	struct op_import_request req;
@@ -117,9 +152,7 @@  static int recv_request_import(int sockfd)
 	}
 
 	if (found) {
-		/* should set TCP_NODELAY for usbip */
-		usbip_net_set_nodelay(sockfd);
-		usbip_net_set_keepalive(sockfd);
+		set_socket_options(sockfd);
 
 		/* export device needs a TCP/IP socket descriptor */
 		status = usbip_export_device(edev, sockfd);
@@ -586,15 +619,21 @@  static int do_standalone_mode(int daemonize, int ipv4, int ipv6)
 
 int main(int argc, char *argv[])
 {
+	enum { KEEPIDLE = 1, KEEPCNT, KEEPINTVL };
+
 	static const struct option longopts[] = {
 		{ "ipv4",     no_argument,       NULL, '4' },
 		{ "ipv6",     no_argument,       NULL, '6' },
 		{ "daemon",   no_argument,       NULL, 'D' },
-		{ "daemon",   no_argument,       NULL, 'D' },
 		{ "debug",    no_argument,       NULL, 'd' },
 		{ "device",   no_argument,       NULL, 'e' },
 		{ "pid",      optional_argument, NULL, 'P' },
 		{ "tcp-port", required_argument, NULL, 't' },
+#if HAVE_KEEPALIVE_OPTS
+		{ "tcp-keepidle",  required_argument, NULL, KEEPIDLE },
+		{ "tcp-keepcnt",   required_argument, NULL, KEEPCNT },
+		{ "tcp-keepintvl", required_argument, NULL, KEEPINTVL },
+#endif
 		{ "help",     no_argument,       NULL, 'h' },
 		{ "version",  no_argument,       NULL, 'v' },
 		{ NULL,	      0,                 NULL,  0  }
@@ -609,6 +648,7 @@  int main(int argc, char *argv[])
 	int daemonize = 0;
 	int ipv4 = 0, ipv6 = 0;
 	int opt, rc = -1;
+	int longidx = 0;
 
 	pid_file = NULL;
 
@@ -621,7 +661,7 @@  int main(int argc, char *argv[])
 	cmd = cmd_standalone_mode;
 	driver = &host_driver;
 	for (;;) {
-		opt = getopt_long(argc, argv, "46DdeP::t:hv", longopts, NULL);
+		opt = getopt_long(argc, argv, "46DdeP::t:hv", longopts, &longidx);
 
 		if (opt == -1)
 			break;
@@ -654,6 +694,15 @@  int main(int argc, char *argv[])
 		case 'e':
 			driver = &device_driver;
 			break;
+		case KEEPIDLE:
+			tcp_keepidle = usbip_to_int(longopts[longidx].name, optarg, UINT16_MAX);
+			break;
+		case KEEPCNT:
+			tcp_keepcnt = usbip_to_int(longopts[longidx].name, optarg, UINT16_MAX);
+			break;
+		case KEEPINTVL:
+			tcp_keepintvl = usbip_to_int(longopts[longidx].name, optarg, UINT16_MAX);
+			break;
 		case '?':
 			usbipd_help();
 		default: