From 073a65dfeafaefad82b14fa08a5d513badd5b4e2 Mon Sep 17 00:00:00 2001 From: "Robin H. Johnson" Date: Sat, 19 Aug 2023 22:36:28 -0700 Subject: [PATCH] sockopts: support many more socket options, including strings Add support for many more socket options, including those that take string inputs, such as `TCP_CONGESTION=bbr`. Options known to the code, but not present at build time will now emit an error that is distict from options unknown to the code (unknown vs not-available). This patch greatly eases running specific rsync configurations, without relying on LD_PRELOAD to modify socket behaviors. Signed-off-by: Robin H. Johnson --- .gitignore | 1 + Makefile.in | 5 +- NEWS.md | 4 + configure.ac | 2 +- rsync.1.md | 12 +++ rsync.h | 18 +++++ socket.c | 101 ++++++++++++------------- sockopts.c.sh | 131 +++++++++++++++++++++++++++++++++ testsuite/daemon-sockopts.test | 26 +++++++ testsuite/rsync.fns | 1 + usage.c | 41 +++++++++++ 11 files changed, 285 insertions(+), 57 deletions(-) create mode 100644 sockopts.c.sh create mode 100644 testsuite/daemon-sockopts.test diff --git a/.gitignore b/.gitignore index 9e59c9c40..b0835af4a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ aclocal.m4 /auto-build-save .deps /*.exe +/sockopts.c diff --git a/Makefile.in b/Makefile.in index a1253e5d5..52b35379e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -47,7 +47,7 @@ OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \ util1.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \ usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o -OBJS3=progress.o pipe.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@ +OBJS3=progress.o pipe.o sockopts.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@ DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \ popt/popthelp.o popt/poptparse.o @@ -367,3 +367,6 @@ doxygen: doxygen-upload: rsync -avzv $(srcdir)/dox/html/ --delete \ $${SAMBA_HOST-samba.org}:/home/httpd/html/rsync/doxygen/head/ + +sockopts.c: sockopts.c.sh + sh $< >$@.tmp && mv $@.tmp $@ diff --git a/NEWS.md b/NEWS.md index ca60c32cb..573f604a7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -49,6 +49,10 @@ - Changed the mapfrom & mapto perl scripts (in the support dir) into a single python script named idmap. Converted a couple more perl scripts into python. +- Recognize many more sockopt inputs, including string inputs. + e.g. TCP_CONGESTION=bbr, TCP_FASTOPEN, TCP_FASTOPEN_CONNECT, IP_FREEBIND, + SO_INCOMING_CPU, TCP_QUICKACK + ### DEVELOPER RELATED: - Updated config.guess (timestamp 2023-01-01) and config.sub (timestamp diff --git a/configure.ac b/configure.ac index ccad7f132..269502a58 100644 --- a/configure.ac +++ b/configure.ac @@ -13,7 +13,7 @@ AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \ sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \ popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \ zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \ - bsd/string.h) + bsd/string.h netinet/ip6.h) AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include ]]) AC_HEADER_MAJOR_FIXED diff --git a/rsync.1.md b/rsync.1.md index 2ae6f4816..8663e46f2 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -3071,6 +3071,18 @@ expand it. able to set. By default no special socket options are set. This only affects direct socket connections to a remote rsync daemon. + `OPTIONS` a space or comma seperated list of one or more `optname` strings + (e.g. `SO_KEEPALIVE`, `SO_SNDBUF=1234`, `SO_BINDTODEVICE=lo`, `IP_FREEBIND`, + `TCP_CONGESTION=reno`, `TCP_FASTOPEN`, `TCP_FASTOPEN_CONNECT`), or `optval` + strings (e.g. `IP_PMTUDISC_DO`, `IPTOS_THROUGHPUT`). + + Unknown options are those not know to the source at build time. + Unsupported options are those known source code, but not present in the + build environment. + + All errors are non-fatal, including unknown options, unsupported options, + missing required arguments or superflous arguments. + See also [the daemon version of the `--sockopts` option](#dopt--sockopts). 0. `--blocking-io` diff --git a/rsync.h b/rsync.h index d3709fe0f..5cb1e1650 100644 --- a/rsync.h +++ b/rsync.h @@ -1171,6 +1171,24 @@ struct name_num_obj { struct name_num_item *list; }; +enum SOCK_OPT_TYPES { + SOCK_OPT_BOOL, + SOCK_OPT_INT, + SOCK_OPT_ON, + SOCK_OPT_STR, + // error sentinal, hopefully never a valid setsockopt level + SOCK_OPT_ERR = 0xDEADCAFE +}; + +struct socket_option +{ + char *name; + int level; + int option; + int value; + int opttype; +}; + #ifdef EXTERNAL_ZLIB #define read_buf read_buf_ #endif diff --git a/socket.c b/socket.c index c2075adf8..d594a24ac 100644 --- a/socket.c +++ b/socket.c @@ -40,6 +40,7 @@ extern char *sockopts; extern int default_af_hint; extern int connect_timeout; extern int pid_file_fd; +extern struct socket_option socket_options[]; #ifdef HAVE_SIGACTION static struct sigaction sigact; @@ -623,53 +624,6 @@ void start_accept_loop(int port, int (*fn)(int, int)) } } - -enum SOCK_OPT_TYPES {OPT_BOOL,OPT_INT,OPT_ON}; - -struct -{ - char *name; - int level; - int option; - int value; - int opttype; -} socket_options[] = { - {"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, 0, OPT_BOOL}, - {"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, 0, OPT_BOOL}, -#ifdef SO_BROADCAST - {"SO_BROADCAST", SOL_SOCKET, SO_BROADCAST, 0, OPT_BOOL}, -#endif -#ifdef TCP_NODELAY - {"TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, 0, OPT_BOOL}, -#endif -#ifdef IPTOS_LOWDELAY - {"IPTOS_LOWDELAY", IPPROTO_IP, IP_TOS, IPTOS_LOWDELAY, OPT_ON}, -#endif -#ifdef IPTOS_THROUGHPUT - {"IPTOS_THROUGHPUT", IPPROTO_IP, IP_TOS, IPTOS_THROUGHPUT, OPT_ON}, -#endif -#ifdef SO_SNDBUF - {"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, 0, OPT_INT}, -#endif -#ifdef SO_RCVBUF - {"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, 0, OPT_INT}, -#endif -#ifdef SO_SNDLOWAT - {"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, 0, OPT_INT}, -#endif -#ifdef SO_RCVLOWAT - {"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, 0, OPT_INT}, -#endif -#ifdef SO_SNDTIMEO - {"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, 0, OPT_INT}, -#endif -#ifdef SO_RCVTIMEO - {"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, 0, OPT_INT}, -#endif - {NULL,0,0,0,0} -}; - - /* Set user socket options. */ void set_socket_options(int fd, char *options) { @@ -682,13 +636,14 @@ void set_socket_options(int fd, char *options) for (tok = strtok(options, " \t,"); tok; tok = strtok(NULL," \t,")) { int ret=0,i; - int value = 1; char *p; + char *value; + int intvalue = 1; int got_value = 0; if ((p = strchr(tok,'='))) { *p = 0; - value = atoi(p+1); + value = p+1; got_value = 1; } @@ -701,16 +656,52 @@ void set_socket_options(int fd, char *options) rprintf(FERROR,"Unknown socket option %s\n",tok); continue; } + if(socket_options[i].level == (int)(SOCK_OPT_ERR)) { + // At compile-time, a potential socket option was NOT present on + // the build system. + rprintf(FERROR,"Unsupported socket option %s\n",tok); + continue; + } switch (socket_options[i].opttype) { - case OPT_BOOL: - case OPT_INT: - ret = setsockopt(fd,socket_options[i].level, - socket_options[i].option, - (char *)&value, sizeof (int)); + case SOCK_OPT_BOOL: + if(got_value) + intvalue = atoi(value); + ret = setsockopt(fd, + socket_options[i].level, + socket_options[i].option, + (char *)&intvalue, + sizeof (int) + ); + break; + + case SOCK_OPT_INT: + if(got_value) { + intvalue = atoi(value); + ret = setsockopt(fd, + socket_options[i].level, + socket_options[i].option, + (char *)&intvalue, + sizeof (int) + ); + } else { + rprintf(FERROR,"syntax error -- %s requires an integer value\n",tok); + } + break; + + case SOCK_OPT_STR: + if (got_value) { + ret = setsockopt(fd, + socket_options[i].level, + socket_options[i].option, + value, + strlen(value)); + } else { + rprintf(FERROR,"syntax error -- %s requires a string value\n",tok); + } break; - case OPT_ON: + case SOCK_OPT_ON: if (got_value) rprintf(FERROR,"syntax error -- %s does not take a value\n",tok); diff --git a/sockopts.c.sh b/sockopts.c.sh new file mode 100644 index 000000000..da04f5c2f --- /dev/null +++ b/sockopts.c.sh @@ -0,0 +1,131 @@ +#!/bin/sh +# vim: noet +# The "noet" is important because of the -EOF behavior. +opt() { + name=$1 + level=$2 + optname=$3 + optval=$4 + opttype=$5 + + check_optval=1 + case ${optval} in + [A-Z_]*) ;; + *) check_optval='' ;; + esac + # If we have configure --disable-ipv6, the options might be available in + # the system headers, but the builder wants to NOT support them. + optdef_extra='' + case $name in + IPV6*) optdef_extra=' && defined(INET6)' ;; + esac + + # If the values are NOT available at compile-time, we should recognize the + # input and emit an error. + cat <<-EOF + #if defined(${level}) && defined(${optname}) ${check_optval:+&& defined(${optval})}${optdef_extra} + {"${name}", ${level}, ${optname}, ${optval}, ${opttype}}, + #else + {"${name}", SOCK_OPT_ERR, SOCK_OPT_ERR, SOCK_OPT_ERR, ${opttype}}, + #endif + EOF +} + +opt_bool() { + opt "$1" "$2" "${3:-$1}" 0 SOCK_OPT_BOOL +} +opt_int() { + opt "$1" "$2" "${3:-$1}" 0 SOCK_OPT_INT +} +opt_val() { + opt "$1" "$2" "$3" "$4" SOCK_OPT_ON +} +opt_str() { + opt "$1" "$2" "${3:-$1}" 0 SOCK_OPT_STR +} + +cat <<-EOF +#include "rsync.h" +#ifdef HAVE_NETINET_IN_SYSTM_H +#include +#endif +#ifdef HAVE_NETINET_IP_H +#include +#endif +#ifdef HAVE_NETINET_IP6_H +#include +#endif +#include +struct socket_option socket_options[] = { +EOF + +# grouped by level, sorted by name. +opt_str SO_BINDTODEVICE SOL_SOCKET +opt_bool SO_BROADCAST SOL_SOCKET +opt_int SO_BUSY_POLL SOL_SOCKET +opt_bool SO_DEBUG SOL_SOCKET +opt_bool SO_DONTROUTE SOL_SOCKET +opt_int SO_INCOMING_CPU SOL_SOCKET +opt_bool SO_KEEPALIVE SOL_SOCKET +opt_int SO_MARK SOL_SOCKET +opt_int SO_PRIORITY SOL_SOCKET +opt_int SO_RCVBUF SOL_SOCKET +opt_int SO_RCVLOWAT SOL_SOCKET +opt_int SO_RCVTIMEO SOL_SOCKET +opt_bool SO_REUSEADDR SOL_SOCKET +opt_bool SO_REUSEPORT SOL_SOCKET +opt_int SO_SNDBUF SOL_SOCKET +opt_int SO_SNDLOWAT SOL_SOCKET +opt_int SO_SNDTIMEO SOL_SOCKET + +opt_bool IP_BIND_ADDRESS_NO_PORT IPPROTO_IP +opt_int IP_CHECKSUM IPPROTO_IP +opt_bool IP_FREEBIND IPPROTO_IP +opt_int IP_LOCAL_PORT_RANGE IPPROTO_IP +opt_int IP_MINTTL IPPROTO_IP +opt_int IP_MTU IPPROTO_IP +opt_val IP_PMTUDISC_DO IPPROTO_IP IP_MTU_DISCOVER IP_PMTUDISC_DO +opt_val IP_PMTUDISC_DONT IPPROTO_IP IP_MTU_DISCOVER IP_PMTUDISC_DONT +opt_val IP_PMTUDISC_INTERFACE IPPROTO_IP IP_MTU_DISCOVER IP_PMTUDISC_INTERFACE +opt_val IP_PMTUDISC_OMIT IPPROTO_IP IP_MTU_DISCOVER IP_PMTUDISC_OMIT +opt_val IP_PMTUDISC_PROBE IPPROTO_IP IP_MTU_DISCOVER IP_PMTUDISC_PROBE +opt_val IP_PMTUDISC_WANT IPPROTO_IP IP_MTU_DISCOVER IP_PMTUDISC_WANT +opt_bool IP_TRANSPARENT IPPROTO_IP + +# sorting exception, to group the IPTOS together. +opt_val IPTOS_LOWDELAY IPPROTO_IP IP_TOS IPTOS_LOWDELAY +opt_val IPTOS_MINCOST IPPROTO_IP IP_TOS IPTOS_MINCOST +opt_val IPTOS_RELIABILITY IPPROTO_IP IP_TOS IPTOS_RELIABILITY +opt_val IPTOS_THROUGHPUT IPPROTO_IP IP_TOS IPTOS_THROUGHPUT + +opt_bool IPV6_FREEBIND IPPROTO_IPV6 +opt_int IPV6_MTU IPPROTO_IPV6 +opt_val IPV6_PMTUDISC_DO IPPROTO_IPV6 IPV6_MTU_DISCOVER IPV6_PMTUDISC_DO +opt_val IPV6_PMTUDISC_DONT IPPROTO_IPV6 IPV6_MTU_DISCOVER IPV6_PMTUDISC_DONT +opt_val IPV6_PMTUDISC_INTERFACE IPPROTO_IPV6 IPV6_MTU_DISCOVER IPV6_PMTUDISC_INTERFACE +opt_val IPV6_PMTUDISC_OMIT IPPROTO_IPV6 IPV6_MTU_DISCOVER IPV6_PMTUDISC_OMIT +opt_val IPV6_PMTUDISC_PROBE IPPROTO_IPV6 IPV6_MTU_DISCOVER IPV6_PMTUDISC_PROBE +opt_val IPV6_PMTUDISC_WANT IPPROTO_IPV6 IPV6_MTU_DISCOVER IPV6_PMTUDISC_WANT +opt_bool IPV6_TRANSPARENT IPPROTO_IPV6 +opt_int IPV6_UNICAST_HOPS IPPROTO_IPV6 + +opt_str TCP_CONGESTION IPPROTO_TCP +opt_bool TCP_CORK IPPROTO_TCP +opt_int TCP_DEFER_ACCEPT IPPROTO_TCP +opt_bool TCP_FASTOPEN_CONNECT IPPROTO_TCP +opt_bool TCP_FASTOPEN IPPROTO_TCP +opt_int TCP_KEEPCNT IPPROTO_TCP +opt_int TCP_KEEPIDLE IPPROTO_TCP +opt_int TCP_KEEPINTVL IPPROTO_TCP +opt_int TCP_LINGER2 IPPROTO_TCP +opt_int TCP_MAXSEG IPPROTO_TCP +opt_bool TCP_NODELAY IPPROTO_TCP +opt_bool TCP_QUICKACK IPPROTO_TCP +opt_int TCP_SYNCNT IPPROTO_TCP +opt_int TCP_USER_TIMEOUT IPPROTO_TCP +opt_int TCP_WINDOW_CLAMP IPPROTO_TCP + +cat < 0) + // Check if different protocol/level of options, or the + // previous line would be tood long. + if(last_level != sockopts[i].level || + ((linelen + strlen(sockopts[i].name) + 1) > WIDTH)) { + rprintf(f, "\n"); + linelen = 0; + } + // This groups the levels together. + last_level = sockopts[i].level; + + nextlen = snprintf(tmpbuf, sizeof(tmpbuf), "%s%s", + (linelen == 0) ? " " : " ", + sockopts[i].name + ); + rprintf(f, "%s", tmpbuf); + linelen += nextlen; + } + rprintf(f, "\n"); + + return; + } + // TODO: JSON path +} + static void output_nno_list(enum logcode f, const char *name, struct name_num_obj *nno) { char namebuf[64], tmpbuf[256]; @@ -288,6 +328,7 @@ void print_rsync_version(enum logcode f) output_nno_list(f, "Checksum list", &valid_checksums); output_nno_list(f, "Compress list", &valid_compressions); output_nno_list(f, "Daemon auth list", &valid_auth_checksums); + output_sockopts_list(f, "sockopts list", socket_options); if (f == FNONE) { json_line("license", "GPLv3");