SO_REUSEADDR per default on TCP LISTEN; so-reuseaddr=

This commit is contained in:
Gerhard Rieger 2023-10-26 19:45:01 +02:00
parent b5640dd707
commit 2a9623d61c
9 changed files with 714 additions and 242 deletions

View file

@ -66,6 +66,10 @@ Features:
With this option Socat restores the VLAN tag.
Feature inspired by Zhao Dong.
Socket option SO_REUSEADDR is now automatically applied to TCP LISTEN
addresses. reuseaddr= restores the old behaviour.
Tests: TCP4_REUSEADDR OPENSSL_6_REUSEADDR REUSEADDR_NULL
Corrections:
When a sub process (EXEC, SYSTEM) terminated with exit code other than
0, its last sent data might have been lost depending on timing of read/

View file

@ -586,7 +586,7 @@ label(ADDRESS_OPENSSL_LISTEN)dit(bf(tt(OPENSSL-LISTEN:<port>)))
link(range)(OPTION_RANGE),
link(tcpwrap)(OPTION_TCPWRAPPERS),
link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR),
link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl()
See also:
link(OPENSSL)(ADDRESS_OPENSSL_CONNECT),
@ -652,7 +652,7 @@ label(ADDRESS_OPENSSL_DTLS_SERVER)dit(bf(tt(OPENSSL-DTLS-SERVER:<port>)))
link(range)(OPTION_RANGE),
link(tcpwrap)(OPTION_TCPWRAPPERS),
link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR),
link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl()
link(rcvtimeo)(OPTION_SO_RCVTIMOE)nl()
See also:
@ -788,7 +788,7 @@ label(ADDRESS_SCTP_LISTEN)dit(bf(tt(SCTP-LISTEN:<port>)))
link(sctp-maxseg)(OPTION_SCTP_MAXSEG),
link(sctp-nodelay)(OPTION_SCTP_NODELAY),
link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR),
link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl()
See also:
link(SCTP4-LISTEN)(ADDRESS_SCTP4_LISTEN),
@ -1053,7 +1053,7 @@ label(ADDRESS_TCP_LISTEN)dit(bf(tt(TCP-LISTEN:<port>)))
link(accept-timeout)(OPTION_ACCEPT_TIMEOUT),
link(mss)(OPTION_MSS),
link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR),
link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY),
link(cool-write)(OPTION_COOL_WRITE)nl()
See also:
@ -1413,7 +1413,7 @@ label(ADDRESS_VSOCK_LISTEN)dit(bf(tt(VSOCK-LISTEN:<port>)))
link(max-children)(OPTION_MAX_CHILDREN),
link(backlog)(OPTION_BACKLOG),
link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR),
link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl()
See also:
link(VSOCK-CONNECT)(ADDRESS_VSOCK_CONNECT)
@ -2004,9 +2004,16 @@ label(OPTION_SO_RCVTIMOE)dit(bf(tt(so-rcvtimeo=<time>, rcvtimeo=<time>)))
label(OPTION_SO_SNDTIMOE)dit(bf(tt(so-sndtimeo=<time>, sndtimeo=<time>)))
Like link(so-recvtimeo)(OPTION_SO_RCVTIMOE), but for code(send). Not usecase
known.
label(OPTION_REUSEADDR)dit(bf(tt(reuseaddr)))
label(OPTION_RCVLOWAT)dit(bf(tt(rcvlowat=<bytes>)))
Specifies the minimum number of received bytes [link(int)(TYPE_INT)] until
the socket layer will pass the buffered data to socat().
label(OPTION_SO_REUSEADDR)dit(bf(tt(reuseaddr[=[0|1]])))
Allows other sockets to bind to an address even if parts of it (e.g. the
local port) are already in use by socat() (link(example)(EXAMPLE_OPTION_REUSEADDR)).
local port) are already in use by socat().nl()
With version 1.7.5, this socket option is set automatically for TCP LISTEN
addresses. If you prefer the system default (no related
tt(setsockopt(...SO_REUSEADDR...)) call at all), use form tt(reuseaddr=).nl()
(link(example)(EXAMPLE_OPTION_SO_REUSEADDR)).
label(OPTION_SNDBUF)dit(bf(tt(sndbuf=<bytes>)))
Sets the size of the send buffer after the code(socket()) call to
<bytes> [link(int)(TYPE_INT)].
@ -3295,7 +3302,7 @@ connection comes in, accepts it, then connects to the remote host
a second connection.
label(EXAMPLE_OPTION_BIND_TCP4)
label(EXAMPLE_OPTION_REUSEADDR)
label(EXAMPLE_OPTION_SO_REUSEADDR)
label(EXAMPLE_OPTION_FORK)
label(EXAMPLE_OPTION_SUBSTUSER)
label(EXAMPLE_OPTION_RANGE)
@ -3319,7 +3326,7 @@ link(fork)(OPTION_FORK)'ing a new
process after each code(accept()). It provides a little security by
link(su)(OPTION_SUBSTUSER)'ing to user
nobody after forking; it only permits connections from the private 10 network
(link(range)(OPTION_RANGE)); due to link(reuseaddr)(OPTION_REUSEADDR), it
(link(range)(OPTION_RANGE)); due to link(reuseaddr)(OPTION_SO_REUSEADDR), it
allows immediate restart after master process's termination, even if some child
sockets are not completely shut down.
With link(-lmlocal2)(option_lm), socat logs to stderr until successfully
@ -3512,7 +3519,7 @@ implements a simple network based message collector.
For each client connecting to port 3334, a new child process is generated (option link(fork)(OPTION_FORK)).
All data sent by the clients are link(append)(OPTION_APPEND)'ed to the file /tmp/in.log.
If the file does not exist, socat link(creat)(OPTION_O_CREAT)'s it.
Option link(reuseaddr)(OPTION_REUSEADDR) allows immediate restart of the server
Option link(reuseaddr)(OPTION_SO_REUSEADDR) allows immediate restart of the server
process.
COMMENT(
@ -3523,7 +3530,7 @@ For each client connecting to port 3335, a new child process is generated
(option link(fork)(OPTION_FORK)).
The contents of the file /tmp/motd is sent to each client.
Messages sent by clients result in an error due to option link(rdonly)(OPTION_RDONLY).
Option link(reuseaddr)(OPTION_REUSEADDR) allows immediate restart of the server
Option link(reuseaddr)(OPTION_SO_REUSEADDR) allows immediate restart of the server
process.
)
COMMENT(

808
test.sh

File diff suppressed because it is too large Load diff

View file

@ -153,7 +153,10 @@ int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, sockl
applyopts_offset(xfd, opts);
applyopts_cloexec(xfd->fd, opts);
/* Phase prebind */
xiosock_reuseaddr(xfd->fd, proto, opts);
applyopts(xfd->fd, opts, PH_PREBIND);
applyopts(xfd->fd, opts, PH_BIND);
if (Bind(xfd->fd, (struct sockaddr *)us, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd,

View file

@ -97,7 +97,7 @@ const struct optdesc opt_so_debug = { "so-debug", "debug", OPT_SO_DEBUG,
const struct optdesc opt_so_acceptconn={ "so-acceptconn","acceptconn",OPT_SO_ACCEPTCONN,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_ACCEPTCONN};
#endif /* SO_ACCEPTCONN */
const struct optdesc opt_so_broadcast= { "so-broadcast", "broadcast", OPT_SO_BROADCAST,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_BROADCAST};
const struct optdesc opt_so_reuseaddr= { "so-reuseaddr", "reuseaddr", OPT_SO_REUSEADDR,GROUP_SOCKET, PH_PREBIND, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_REUSEADDR};
const struct optdesc opt_so_reuseaddr= { "so-reuseaddr", "reuseaddr", OPT_SO_REUSEADDR,GROUP_SOCKET, PH_PREBIND, TYPE_INT_NULL, OFUNC_SOCKOPT, SOL_SOCKET, SO_REUSEADDR};
const struct optdesc opt_so_keepalive= { "so-keepalive", "keepalive", OPT_SO_KEEPALIVE,GROUP_SOCKET, PH_FD, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_KEEPALIVE};
#if HAVE_STRUCT_LINGER
const struct optdesc opt_so_linger = { "so-linger", "linger", OPT_SO_LINGER, GROUP_SOCKET, PH_PASTSOCKET, TYPE_LINGER,OFUNC_SOCKOPT,SOL_SOCKET, SO_LINGER };
@ -2153,3 +2153,37 @@ int xiobind(
applyopts(xfd->fd, opts, PH_PASTBIND);
return 0;
}
/* Handles the SO_REUSEADDR socket option for TCP LISTEN addresses depending on
Socat option so-reuseaddr:
Option not applied: set it to 1
Option applied with a value: set it to the value
Option applied eith empty value "so-reuseaddr=": do not call setsockopt() for
SO_REUSEADDR
Return 0 on success, or -1 with errno when an error occurred.
*/
int xiosock_reuseaddr(int fd, int ipproto, struct opt *opts)
{
union integral val;
union integral notnull;
int _errno;
val.u_int = 0;
notnull.u_bool = false;
if (ipproto == IPPROTO_TCP) {
val.u_int = 1;
notnull.u_bool = true;
}
retropt_2integrals(opts, OPT_SO_REUSEADDR, &val, &notnull);
if (notnull.u_bool) {
if (Setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val.u_int, sizeof(int))
!= 0) {
_errno = errno;
Error4("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, { %d }, "F_Zu"): %s",
fd, val.u_int, sizeof(val.u_int), strerror(errno));
errno = _errno;
return -1;
}
}
return 0;
}

View file

@ -135,5 +135,6 @@ extern int
xiosocket(struct opt *opts, int pf, int socktype, int proto, int level);
extern int
xiosocketpair(struct opt *opts, int pf, int socktype, int proto, int sv[2]);
extern int xiosock_reuseaddr(int fd, int ipproto, struct opt *opts);
#endif /* !defined(__xio_socket_h_included) */

View file

@ -133,24 +133,30 @@ int _xioopen_ipdgram_listen(struct single *sfd,
while (true) { /* we loop with fork or prohibited packets */
/* now wait for some packet on this datagram socket, get its sender
address, connect there, and return */
int reuseaddr = dofork;
union integral notnull;
union integral reuseaddr;
int doreuseaddr = (dofork != 0);
char infobuff[256];
union sockaddr_union _sockname;
union sockaddr_union *la = &_sockname; /* local address */
reuseaddr.u_int = dofork;
if ((sfd->fd = xiosocket(opts, pf, socktype, ipproto, E_ERROR)) < 0) {
return STAT_RETRYLATER;
}
doreuseaddr |= (retropt_int(opts, OPT_SO_REUSEADDR, &reuseaddr) >= 0);
doreuseaddr |= (retropt_2integrals(opts, OPT_SO_REUSEADDR,
&reuseaddr, &notnull) >= 0);
applyopts(sfd->fd, opts, PH_PASTSOCKET);
/* SO_REUSEADDR handling of UDP sockets is helpful on Solaris */
if (doreuseaddr) {
if (Setsockopt(sfd->fd, opt_so_reuseaddr.major,
opt_so_reuseaddr.minor, &reuseaddr, sizeof(reuseaddr))
opt_so_reuseaddr.minor, &reuseaddr.u_int, sizeof(reuseaddr.u_int))
< 0) {
Warn6("setsockopt(%d, %d, %d, {%d}, "F_Zd"): %s",
sfd->fd, opt_so_reuseaddr.major,
opt_so_reuseaddr.minor, reuseaddr, sizeof(reuseaddr),
opt_so_reuseaddr.minor, reuseaddr.u_int, sizeof(reuseaddr.u_int),
strerror(errno));
}
}

View file

@ -2094,6 +2094,21 @@ int parseopts_table(const char **a, groups_t groups, struct opt **opts,
Info2("setting option \"%s\" to %d", ent->desc->defname,
(*opts)[i].value.u_int);
break;
case TYPE_INT_NULL:
(*opts)[i].value2.u_bool = true;
if (assign && token[0] != '\0') {
char *rest;
(*opts)[i].value.u_int = Strtoul(token, &rest, 0, a0);
} else if (assign) {
(*opts)[i].value2.u_bool = false; /* NULL / no value */
Info1("setting option \"%s\" to NULL", ent->desc->defname);
break;
} else {
(*opts)[i].value.u_int = 1;
}
Info2("setting option \"%s\" to %d", ent->desc->defname,
(*opts)[i].value.u_int);
break;
case TYPE_BOOL:
if (!assign) {
(*opts)[i].value.u_bool = 1;
@ -2924,7 +2939,7 @@ int retropt_int(struct opt *opts, int optcode, int *result) {
case TYPE_INT: *result = opt->value.u_int; break;
case TYPE_STRING: *result = strtol(opt->value.u_string, &rest, 0);
if (*rest != '\0') {
Error1("retropts: trailing garbage in numerical arg of option \"%s\"",
Error1("retropts_int(): trailing garbage in numerical arg of option \"%s\"",
opt->desc->defname);
}
break;
@ -2941,6 +2956,36 @@ int retropt_int(struct opt *opts, int optcode, int *result) {
return -1;
}
/* Looks for the first option of type <optcode>. If the option is found,
this function stores its int value in *result, "consumes" the
option, and returns 0.
If the option is not found, *result is not modified, and -1 is returned. */
int retropt_2integrals(struct opt *opts, int optcode,
union integral *value1, union integral *value2)
{
struct opt *opt = opts;
while (opt->desc != ODESC_END) {
if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) {
switch (opt->desc->type) {
case TYPE_INT_NULL:
/* ...and many more types */
*value1 = opt->value;
*value2 = opt->value2;
break;
default: Error2("cannot convert type %d of option %s to int/NULL",
opt->desc->type, opt->desc->defname);
opt->desc = ODESC_ERROR;
return -1;
}
opt->desc = ODESC_DONE;
return 0;
}
++opt;
}
return -1;
}
/* Looks for the first option of type <optcode>. If the option is found,
this function stores its unsigned int value in *result, "consumes" the
option, and returns 0.
@ -3335,6 +3380,16 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) {
opt->desc = ODESC_ERROR; ++opt; continue;
}
break;
case TYPE_INT_NULL:
if (opt->value2.u_bool &&
Setsockopt(fd, opt->desc->major, opt->desc->minor,
&opt->value.u_int, sizeof(int)) < 0) {
Error6("setsockopt(%d, %d, %d, {%d}, "F_Zu"): %s",
fd, opt->desc->major, opt->desc->minor,
opt->value.u_int, sizeof(int), strerror(errno));
opt->desc = ODESC_ERROR; ++opt; continue;
}
break;
case TYPE_LONG:
if (Setsockopt(fd, opt->desc->major, opt->desc->minor,
&opt->value.u_long, sizeof(long)) < 0) {

View file

@ -27,6 +27,7 @@ enum e_types {
TYPE_BYTE, /* unsigned char */
TYPE_INT, /* int */
TYPE_INT_NULL, /* int, or opt= for no action (instead of default) */
TYPE_LONG, /* long */
TYPE_STRING, /* char * */
TYPE_NAME = TYPE_STRING,
@ -125,7 +126,7 @@ enum e_func {
groups. to keep the search for options simple, we allow each option to
belong to at most one group only. (we have a dummy GROUP_NONE for those
that don't want to belong to any...)
The caller of parseopts() specifies per bitpatter a set of groups where it
The caller of parseopts() specifies per bit pattern a set of groups where it
accepts options from.
*/
@ -946,6 +947,7 @@ extern int retropt_bool(struct opt *opts, int optcode, bool *result);
extern int retropt_short(struct opt *opts, int optcode, short *result);
extern int retropt_ushort(struct opt *opts, int optcode, unsigned short *result);
extern int retropt_int(struct opt *opts, int optcode, int *result);
extern int retropt_2integrals(struct opt *opts, int optcode, union integral *value1, union integral *value2);
extern int retropt_uint(struct opt *opts, int optcode, unsigned int *result);
extern int retropt_long(struct opt *opts, int optcode, long *result);
extern int retropt_ulong(struct opt *opts, int optcode, unsigned long *result);