Skip to content

Commit

Permalink
sockopts: support many more socket options, including strings
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
robbat2 committed Aug 20, 2023
1 parent 2f9b963 commit 2f9a1e9
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 57 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ aclocal.m4
/auto-build-save
.deps
/*.exe
/sockopts.c
6 changes: 5 additions & 1 deletion Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
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
util1.o util2.o main.o checksum.o match.o syscall.o log.o backup.o delete.o \
sockopts.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@
Expand Down Expand Up @@ -367,3 +368,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 $@
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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 <netinet/in.h>]])
AC_HEADER_MAJOR_FIXED

Expand Down
12 changes: 12 additions & 0 deletions rsync.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
18 changes: 18 additions & 0 deletions rsync.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
101 changes: 46 additions & 55 deletions socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
}

Expand All @@ -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);

Expand Down
131 changes: 131 additions & 0 deletions sockopts.c.sh
Original file line number Diff line number Diff line change
@@ -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 <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifdef HAVE_NETINET_IP6_H
#include <netinet/ip6.h>
#endif
#include <netinet/tcp.h>
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 <<EOF
{NULL, 0, 0, 0, 0}
};
EOF
26 changes: 26 additions & 0 deletions testsuite/daemon-sockopts.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh

# The objective here is to test both --address and --sockopt

. "$suitedir/rsync.fns"

# This is a kitchen sink test.
sockopts='IP_PMTUDISC_DO,TCP_CORK,TCP_DEFER_ACCEPT=1,IP_FREEBIND,IP_LOCAL_PORT_RANGE=1234,TCP_CONGESTION=bbr,TCP_QUICKACK,TCP_NODELAY,TCP_DEFER_ACCEPT=1,TCP_FASTOPEN,TCP_FASTOPEN_CONNECT'

extra_config="
socket options = ${sockopts}
"
build_rsyncd_conf

RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon --sockopts=${sockopts}"
export RSYNC_CONNECT_PROG

hands_setup

# Build chkdir with a normal rsync and an --exclude.
$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"

checkit "'$ignore23' $RSYNC -avvvvzz --sockopts=${sockopts} '$fromdir/' localhost::test-to/" "$chkdir" "$todir"

# The script would have aborted on error, so getting here means we've won.
exit 0
1 change: 1 addition & 0 deletions testsuite/rsync.fns
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ exclude = ? foobar.baz
max verbosity = 4
$uid_setting
$gid_setting
$extra_config
[test-from]
path = $fromdir
Expand Down
Loading

0 comments on commit 2f9a1e9

Please sign in to comment.