New option bind-tempname for parallel UNIX domain SENDTO clients

This commit is contained in:
Gerhard Rieger 2023-10-03 21:02:13 +02:00
parent 8641344c73
commit b2914a0cf3
17 changed files with 551 additions and 86 deletions

View file

@ -152,6 +152,15 @@ Features:
afterwards.
Tests: UMASK_ON_CREATE UMASK_ON_SYSTEM
Added option unix-bind-tempname (bind-tempname) to allow UNIX (and
ABSTRACT) client addresses to bind to unique addresses even when
invoked in forked off sub processes.
Tests: UNIX_LISTEN_CONNECT_BIND_TEMPNAME UNIX_LISTEN_CLIENT_BIND_TEMPNAME
UNIX_RECVFROM_CLIENT_BIND_TEMPNAME UNIX_RECVFROM_SENDTO_BIND_TEMPNAME
ABSTRACT_LISTEN_CONNECT_BIND_TEMPNAME ABSTRACT_LISTEN_CLIENT_BIND_TEMPNAME
ABSTRACT_RECVFROM_CLIENT_BIND_TEMPNAME ABSTRACT_RECVFROM_SENDTO_BIND_TEMPNAME
Thanks to Kai Lüke for sending an initial patch.
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

@ -22,7 +22,8 @@ srcdir = @srcdir@
VPATH = @srcdir@
CC = @CC@
#CCOPT=-fcf-protection=none # for gdb on Ubuntu-20.04
#CCOPT1=-no-pie -fstack-protector
#CCOPT=$(CCOPT1) -fcf-protection=none # for gdb on Ubuntu-20.04
CCOPTS = $(CCOPT)
SYSDEFS = @SYSDEFS@

View file

@ -831,6 +831,13 @@ typedef unsigned long T_sigset;
# endif
#endif
/* OpenBSD (at least 7.2) does better with this special setting */
#if __FreeBSD__ || __OpenBSD__
# define UNIX_TIGHTSOCKLEN false
#else
# define UNIX_TIGHTSOCKLEN true
#endif
/* Cygwin 1.3.22 has the prototypes, but not the type... */
#ifndef HAVE_TYPE_STAT64
# undef HAVE_STAT64

View file

@ -2136,8 +2136,8 @@ label(OPTION_BIND)dit(bf(tt(bind=<sockname>)))
Binds the socket to the given socket address using the code(bind()) system
call. The form of <sockname> is socket domain dependent:
IP4 and IP6 allow the form [hostname|hostaddress][:(service|port)] (link(example)(EXAMPLE_OPTION_BIND_TCP4)),
unixdomain() sockets require link(<filename>)(TYPE_FILENAME),
VSOCK allow the form [cid][:(port)].
VSOCK allows the form [cid][:(port)].nl()
See also: link(unix-bind-tempname)(OPTION_UNIX_BIND_TEMPNAME)
label(OPTION_CONNECT_TIMEOUT)dit(bf(tt(connect-timeout=<seconds>)))
Abort the connection attempt after <seconds> [link(timeval)(TYPE_TIMEVAL)]
with error status.
@ -2292,6 +2292,15 @@ label(GROUP_SOCK_UNIX)em(bf(UNIX option group))
These options apply to UNIX domain based addresses.
startdit()
label(OPTION_UNIX_BIND_TEMPNAME)dit(bf(tt(bind-tempname[=/tmp/pre-XXXXXX],
unix-bind-tempname[=/tmp/pre-XXXXXX])))
Binds to a random path or random address (on abstract namespace sockets).
This is useful with datagram client addresses (tt(SENDTO), or tt(CLIENT))
that are opened in child processes forked off from a common
parent process where the child processes cannot have different bind options.
In the path code(X)'s get replaced with a random character sequence
similar to NOEXPAND(tempnam(3)). When no argument is given socat() takes a
default like code(/tmp/fileXXXXXX).nl()
label(OPTION_UNIX_TIGHTSOCKLEN)dit(bf(tt(unix-tightsocklen[=(0|1)])))
On socket operations, pass a socket address length that does not include the
whole code(struct sockaddr_un) record but (besides other components) only

View file

@ -128,6 +128,8 @@ static int diag_sock_pair(void) {
fcntl(diag_sock_send, F_SETFL, O_NONBLOCK);
fcntl(diag_sock_recv, F_SETFL, O_NONBLOCK);
#endif
fcntl(diag_sock_send, F_SETFD, FD_CLOEXEC);
fcntl(diag_sock_recv, F_SETFD, FD_CLOEXEC);
return 0;
}

View file

@ -156,13 +156,23 @@ int procan(FILE *outfile) {
rlim.rlim_cur, rlim.rlim_max);
}
#endif
}
#ifdef SIZE_MAX
fprintf(outfile, "SIZE_MAX = %-24lu\n", SIZE_MAX);
#endif
#ifdef P_tmpdir
fprintf(outfile, "P_tmpdir = \"%s\"\n", P_tmpdir);
#endif
#ifdef L_tmpnam
fprintf(outfile, "L_tmpnam = %u\n", L_tmpnam);
#endif
#ifdef TMP_MAX
fprintf(outfile, "TMP_MAX = %d\n", TMP_MAX);
#endif
#ifdef PIPE_BUF
fprintf(outfile, "PIPE_BUF = %-24d\n", PIPE_BUF);
#endif
}
/* Name spaces */
{

View file

@ -1146,6 +1146,7 @@ int Listen(int s, int backlog) {
#if _WITH_SOCKET
/* don't forget to handle EINTR when using Accept() ! */
int Accept(int s, struct sockaddr *addr, socklen_t *addrlen) {
char infobuff[256];
int result, _errno;
fd_set accept_s;
if (!diag_in_handler) diag_flush();
@ -1155,15 +1156,14 @@ int Accept(int s, struct sockaddr *addr, socklen_t *addrlen) {
return -1;
}
#if WITH_SYCLS
Debug3("accept(%d, %p, %p)", s, addr, addrlen);
sockaddr_info(addr, *addrlen, infobuff, sizeof(infobuff));
Debug3("accept(%d, %p, %p)", s, infobuff, addrlen);
#endif /* WITH_SYCLS */
result = accept(s, addr, addrlen);
_errno = errno;
if (!diag_in_handler) diag_flush();
#if WITH_SYCLS
if (result >= 0) {
char infobuff[256];
sockaddr_info(addr, *addrlen, infobuff, sizeof(infobuff));
Info5("accept(%d, {%d, %s}, "F_socklen") -> %d", s,
addr->sa_family,
sockaddr_info(addr, *addrlen, infobuff, sizeof(infobuff)),

220
test.sh
View file

@ -1462,6 +1462,11 @@ waitunixport () {
waitfile "$1" "$2" "$3"
}
# Not implemented
waitabstractport () {
relsleep 5
}
# wait until a filesystem entry exists
waitfile () {
local crit=-e
@ -5610,7 +5615,7 @@ N=$((N+1))
# Test if Filan can determine UNIX domain socket in file system
NAME=FILANSOCKET
case "$TESTS" in
*%$N%*|*%filan%*|*%listen%*|*%$NAME%*)
*%$N%*|*%filan%*|*%unix%*|*%listen%*|*%$NAME%*)
TEST="$NAME: capability to analyze named unix socket"
# Run Filan on a listening UNIX domain socket.
# When its output gives "socket" as type (2nd column), the test succeeded
@ -5701,7 +5706,7 @@ fi
NAME=PTMXWAITSLAVE
PTYTYPE=ptmx
case "$TESTS" in
*%$N%*|*%functions%*|*%pty%*|*%$NAME%*)
*%$N%*|*%functions%*|*%pty%*|*%unix%*|*%listen%*|*%$NAME%*)
TEST="$NAME: test if master pty ($PTYTYPE) waits for slave connection"
if ! eval $NUMCOND; then :; else
if ! feat=$(testfeats pty); then
@ -5722,7 +5727,7 @@ N=$((N+1))
NAME=OPENPTYWAITSLAVE
PTYTYPE=openpty
case "$TESTS" in
*%$N%*|*%functions%*|*%pty%*|*%$NAME%*)
*%$N%*|*%functions%*|*%pty%*|*%unix%*|*%listen%*|*%$NAME%*)
TEST="$NAME: test if master pty ($PTYTYPE) waits for slave connection"
if ! eval $NUMCOND; then :;
elif ! feat=$(testfeats pty); then
@ -7803,7 +7808,7 @@ N=$((N+1))
NAME=EXECENDCLOSE
case "$TESTS" in
*%$N%*|*%functions%*|*%exec%*|*%listen%*|*%fork%*|*%$NAME%*)
*%$N%*|*%functions%*|*%exec%*|*%listen%*|*%unix%*|*%fork%*|*%$NAME%*)
TEST="$NAME: end-close keeps EXEC child running"
if ! eval $NUMCOND; then :; else
tf="$td/test$N.stdout"
@ -7867,7 +7872,7 @@ printf "test $F_n $TEST... " $N
# output for the PTY name
{ $CMD 2>"${te}"; echo $? >"$td/test$N.rc0"; } &
waitfile "${te}"
psleep 0.1
psleep 0.5 # 0.1 is too few for FreeBSD-10
PTY=$(grep "N PTY is " $te |sed 's/.*N PTY is //')
[ -e "$PTY" ] && cat $PTY >/dev/null 2>/dev/null
rc=$(cat "$td/test$N.rc0")
@ -9015,7 +9020,7 @@ N=$((N+1))
# process under some circumstances.
NAME=EXECPTYKILL
case "$TESTS" in
*%$N%*|*%functions%*|*%bugs%*|*%exec%*|*%pty%*|*%listen%*|*%fork%*|*%$NAME%*)
*%$N%*|*%functions%*|*%bugs%*|*%exec%*|*%pty%*|*%listen%*|*%unix%*|*%fork%*|*%$NAME%*)
TEST="$NAME: exec:...,pty explicitely kills sub process"
# we want to check if the exec'd sub process is killed in time
# for this we have a shell script that generates a file after two seconds;
@ -14920,7 +14925,7 @@ for addr in exec system; do
ADDR=$(echo $addr |tr a-z A-Z)
NAME=${ADDR}SOCKETPAIRPACKETS
case "$TESTS" in
*%$N%*|*%functions%*|*%exec%*|*%socketpair%*|*%packets%*|*%$NAME%*)
*%$N%*|*%functions%*|*%exec%*|*%socketpair%*|*%unix%*|*%dgram%*|*%packets%*|*%$NAME%*)
TEST="$NAME: simple echo via $addr of cat with socketpair, keeping packet boundaries"
# Start a Socat process with a UNIX datagram socket on the left side and with
# a sub process connected via datagram socketpair that keeps packet boundaries
@ -16478,7 +16483,7 @@ N=$((N+1))
# Test the netns (net namespace) feature with EXEC and reset
NAME=NETNS_EXEC
case "$TESTS" in
*%$N%*|*%functions%*|*%root%*|*%namespace%*|*%netns%*|*%socket%*|*%$NAME%*)
*%$N%*|*%functions%*|*%root%*|*%namespace%*|*%netns%*|*%socket%*|*%abstract%*|*%dgram%*|*%$NAME%*)
ns=socat-$$-test$N
TEST="$NAME: option netns with EXEC (net namespace $ns)"
# Start a simple server with option netns on localhost of a net namespace that
@ -17490,20 +17495,21 @@ else
tdiff="$td/test$N.diff"
tdebug="$td/test$N.debug"
opwd=$(pwd)
CMD0="$TRACE $absSOCAT $opts -U SHELL:\"cat\ >$tc\",chdir=$td SYSTEM:pwd"
CMD0="$TRACE $absSOCAT $opts SHELL:\"cat\ >$tc\",chdir=$td SYSTEM:pwd"
printf "test $F_n $TEST... " $N
mkdir "$td/$tdd"
pushd "$td/$tdd" >/dev/null
eval "$CMD0" >/dev/null 2>"${te}0"
rc0=$?
popd >/dev/null
waitfile "$td/$tc"
tpwd=$(find $td -name $tc -print); tpwd=${tpwd%/*}
pwd2=$(cat $tpwd/$tc </dev/null)
echo "Original pwd: $opwd" >>$tdebug
echo "Temporary pwd: $tpwd" >>$tdebug
echo "Addr2 pwd: $pwd2" >>$tdebug
if [ "$rc0" -ne 0 ]; then
$PRINTF "$FAILED\n"
$PRINTF "$FAILED (rc=$rc0)\n"
echo "$CMD0 &"
cat "${te}0" >&2
numFAIL=$((numFAIL+1))
@ -17696,6 +17702,200 @@ esac
N=$((N+1))
while read _UNIX _SRV _CLI; do
if [ -z "$_UNIX" ] || [[ "$_UNIX" == \#* ]]; then continue; fi
SRV=${_UNIX}-$_SRV
CLI=${_UNIX}-$_CLI
CLI_=$(echo $CLI |tr x- x_)
PROTO=${_UNIX}
proto=$(tolower $PROTO)
# Test the unix-bind-tempname option
NAME=${_UNIX}_${_SRV}_${_CLI}_BIND_TEMPNAME
case "$TESTS" in
*%$N%*|*%functions%*|*%$proto%*|*%socket%*|*%tempname%*|*%listen%*|*%fork%*|*%$NAME%*)
TEST="$NAME: Option unix-bind-tempname"
# Start a UNIX domain service with forking
# Start a TCP service with forking that relays to the UNIX domain service
# Open two concurrent client sessions to the TCP service.
# When both sessions work (in particular, when the UNIX domain service does not
# log "Transport endpoint is not connected" and the TCP service does not fail
# with "Address already in use"), the test succeeded.
if ! eval $NUMCOND; then :;
elif [[ $CLI_ =~ ABSTRACT-* ]] && ! feat=$(testfeats abstract-unixsocket); then
$PRINTF "test $F_n $TEST... ${YELLOW}$feat not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
else
ts="$td/test$N.sock"
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM"
CMD0="$TRACE $SOCAT $opts -lp server $SRV:${ts}0,fork PIPE"
# Using this command would show the principal problem: UNIX (and ABSTRACT)
# datagram clients do not internally bind to a defined address and thus cannot
# receive replies. Applies to all(?) Linux, (some)FreeBSD, (some)Solaris, others
# not tried
#CMD1="$TRACE $SOCAT $opts -lp bind-tempname TCP4-LISTEN:$PORT,reuseaddr,fork $CLI:${ts}0"
# Attempt to bind the datagram client to some address works, but only for a
# single client; when multiple clients are forked they conflict
# The following command is the solution: option unix-bind-tempname generates
# random names (like tempnam(2)) for binding the datagram client socket;
# creating the XXXXXX file makes sure that the (non abstract) clients cannot
# erronously bind there (part of the test)
CMD1="$TRACE $SOCAT $opts -lp bind-tempname TCP4-LISTEN:$PORT,reuseaddr,fork $CLI:${ts}0,bind=${ts}1"
touch ${ts}1.XXXXXX; CMD1="$TRACE $SOCAT $opts -lp tempname TCP4-LISTEN:$PORT,reuseaddr,fork $CLI:${ts}0,bind-tempname=${ts}1.XXXXXX"
CMD2="$TRACE $SOCAT $opts -lp client - TCP4-CONNECT:$LOCALHOST:$PORT"
printf "test $F_n $TEST... " $N
$CMD0 2>"${te}0" &
pid0=$!
wait${proto}port ${ts}0 1
$CMD1 2>"${te}1" &
pid1=$!
waittcp4port $PORT 1
{ echo "$da a"; sleep 2; } |$CMD2 >"${tf}2a" 2>"${te}2a" &
pid2a=$!
sleep 1
echo "$da b" |$CMD2 >"${tf}2b" 2>"${te}2b"
rc2b=$?
sleep 1
kill $pid0 $pid1 $pid2a 2>/dev/null; wait
if [ $rc2b -ne 0 ]; then
$PRINTF "$FAILED\n"
echo "$CMD0 &"
cat "${te}0" >&2
echo "$CMD1 &"
cat "${te}1" >&2
echo "$CMD2 &"
cat "${te}2a" >&2
echo "$CMD2"
cat "${te}2b" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
elif ! echo "$da a" |diff - ${tf}2a >${tdiff}2a; then
$PRINTF "$FAILED (phase a)\n"
echo "$CMD0 &"
cat "${te}0" >&2
echo "$CMD1 &"
cat "${te}1" >&2
echo "$CMD2"
cat "${te}2a" >&2
echo "diff a:" >&2
cat ${tdiff}2a >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
elif ! echo "$da b" |diff - ${tf}2b >${tdiff}2b; then
$PRINTF "$FAILED\n"
echo "$CMD0 &"
cat "${te}0" >&2
echo "$CMD1 &"
cat "${te}1" >&2
echo "$CMD2 &"
cat "${te}2a" >&2
echo "$CMD2"
cat "${te}2b" >&2
echo "diff b:" >&2
cat ${tdiff}2b >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
else
$PRINTF "$OK\n"
if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi
if [ "$DEBUG" ]; then cat "${te}0" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD1"; fi
if [ "$DEBUG" ]; then cat "${te}1" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD2"; fi
if [ "$DEBUG" ]; then cat "${te}2a" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD2"; fi
if [ "$DEBUG" ]; then cat "${te}2b" >&2; fi
numOK=$((numOK+1))
fi
fi # NUMCOND
;;
esac
PORT=$((PORT+1))
N=$((N+1))
done <<<"
UNIX LISTEN CONNECT
UNIX LISTEN CLIENT
UNIX RECVFROM CLIENT
UNIX RECVFROM SENDTO
ABSTRACT LISTEN CONNECT
ABSTRACT LISTEN CLIENT
ABSTRACT RECVFROM CLIENT
ABSTRACT RECVFROM SENDTO
"
# Test if OS/libc is not prone to symlink attacks on UNIX bind()
NAME=TEMPNAME_SEC
case "$TESTS" in
*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%unix%*|*%dgram%*|*%security%*|*%$NAME%*)
TEST="$NAME: test if a symlink attack works against bind()"
# Create a symlink .sock2 pointing to non-existing .sock3
# Start Socat with UNIX-SENDTO...,bind=.sock2
# When .sock3 exists the test failed
if ! eval $NUMCOND; then :; else
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
ts1="$td/test$N.sock1"
ts2="$td/test$N.sock2"
ts3="$td/test$N.sock3"
tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM"
CMD0a="rm -f $ts3"
CMD0b="ln -s $ts3 $ts2"
CMD1="$TRACE $SOCAT $opts UNIX-SENDTO:$ts1,bind=$ts2 PIPE"
rc1=$?
printf "test $F_n $TEST... " $N
$CMD0a
$CMD0b
#echo; ls -l $ts2 $ts3
$CMD1 2>"${te}1" &
pid1=$!
waitunixport $ts1 1 1 2>/dev/null
#res="$(ls -l $ts3 2>/dev/null)"
kill $pid1 2>/dev/null
if [ -e $ts3 ]; then
$PRINTF "$FAILED\n"
echo "symlink target has been created" >&2
echo "$CMD0a" >&2
cat "${te}0a" >&2
echo "$CMD0b" >&2
cat "${te}0b" >&2
echo "$CMD1" >&2
cat "${te}1" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
elif ! grep -q " E " ${te}1; then
$PRINTF "$FAILED\n"
echo "Socat did not fail"
echo "$CMD0a" >&2
cat "${te}0a" >&2
echo "$CMD0b" >&2
cat "${te}0b" >&2
echo "$CMD1" >&2
cat "${te}1" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
else
$PRINTF "$OK\n"
if [ "$VERBOSE" ]; then echo "$CMD0a"; fi
if [ "$DEBUG" ]; then cat "${te}0a" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD0b"; fi
if [ "$DEBUG" ]; then cat "${te}0b" >&2; fi
if [ "$VERBOSE" ]; then echo "$CMD1"; fi
if [ "$DEBUG" ]; then cat "${te}1" >&2; fi
numOK=$((numOK+1))
fi
fi # NUMCOND
;;
esac
PORT=$((PORT+1))
N=$((N+1))
# end of common tests
##################################################################################

View file

@ -62,7 +62,8 @@ static int xioopen_gopen(
Info1("\"%s\" is a socket, connecting to it", filename);
result =
_xioopen_unix_client(sfd, xioflags, addrdesc->groups, 0, opts, filename);
_xioopen_unix_client(sfd, xioflags, addrdesc->groups, 0, opts,
filename, addrdesc);
if (result < 0) {
return result;
}

View file

@ -244,7 +244,7 @@ int xiogetaddrinfo(const char *node, const char *service,
}
if (error_num == EAI_SERVICE && protocol != 0) {
if (hints.ai_protocol == 0) {
Error7("getaddrinfo\"%s\", \"%s\", {0x%02x,%d,%d,%d}, {}): %s",
Error7("getaddrinfo(\"%s\", \"%s\", {0x%02x,%d,%d,%d}, {}): %s",
node?node:"NULL", service?service:"NULL",
hints.ai_flags, hints.ai_family,
hints.ai_socktype, hints.ai_protocol,
@ -438,7 +438,7 @@ int xioresolve(const char *node, const char *service,
}
aip = res;
if (ai_flags[0] & AI_PASSIVE && family == PF_UNSPEC) {
if (ai_flags != NULL && ai_flags[0] & AI_PASSIVE && family == PF_UNSPEC) {
/* We select the first IPv6 address, if available,
because this might accept IPv4 connections too */
while (aip != NULL) {

View file

@ -212,7 +212,6 @@ int _xioopen_accept_fd(
/* Under some circumstances (e.g., TCP listen on port 0) bind() fills empty
fields that we want to know. */
salen = sizeof(sa);
if (Getsockname(sfd->fd, us, &uslen) < 0) {
Warn4("getsockname(%d, %p, {%d}): %s",
sfd->fd, &us, uslen, strerror(errno));
@ -255,7 +254,6 @@ int _xioopen_accept_fd(
pa = &_peername;
la = &_sockname;
salen = sizeof(struct sockaddr);
do {
/*? int level = E_ERROR;*/
Notice1("listening on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname)));
@ -304,6 +302,7 @@ int _xioopen_accept_fd(
Exit(0);
}
}
salen = sizeof(sa);
ps = Accept(sfd->fd, (struct sockaddr *)&sa, &salen);
if (ps >= 0) {
/*0 Info4("accept(%d, %p, {"F_Zu"}) -> %d", sfd->fd, &sa, salen, ps);*/

View file

@ -775,6 +775,10 @@ int xiogetpacketinfo(struct single *sfd, int fd)
OFUNC_OFFSET,
OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC
Does not fork, does not retry.
Alternate (alt) bind semantics are:
with IP sockets: lowport (selects randomly a free port from 640 to 1023)
with UNIX and abstract sockets: uses tmpname() to find a free file system
entry.
returns 0 on success.
*/
int _xioopen_connect(struct single *sfd, union sockaddr_union *us, size_t uslen,
@ -1113,7 +1117,7 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */
/* waits for incoming packet, checks its source address and port. Depending
on fork option, it may fork a subprocess.
Returns STAT_OK if a the packet was accepted; with fork option, this is already in
Returns STAT_OK if a packet was accepted; with fork option, this is already in
a new subprocess!
Other return values indicate a problem; this can happen in the master
process or in a subprocess.
@ -1393,10 +1397,10 @@ int _xioopen_dgram_recv(struct single *sfd, int xioflags,
#endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */
if (xioparms.logopt == 'm') {
Info("starting recvfrom loop, switching to syslog");
Info("starting recv loop, switching to syslog");
diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y';
} else {
Info("starting recvfrom loop");
Info("starting recv loop");
}
return STAT_OK;
@ -2053,6 +2057,15 @@ xiosocketpair(struct opt *opts, int pf, int socktype, int proto, int sv[2]) {
return result;
}
/* Binds a socket to a socket address. Handles IP (internet protocol), UNIX
domain, Linux abstract UNIX domain.
The bind address us may be NULL in which case no bind() happens, except with
alt (on option unix-bind-tempname (bind-tempname)).
Alternate (atl) bind semantics are:
with IP sockets: lowport (selects randomly a free port from 640 to 1023)
with UNIX and abstract sockets: uses a method similar to tmpname() to
find a free file system entry.
*/
int xiobind(
struct single *sfd,
union sockaddr_union *us,
@ -2065,19 +2078,89 @@ int xiobind(
char infobuff[256];
int result;
if (false /* for canonical reasons */) {
;
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
} else if (pf == PF_UNIX) {
if (alt && us != NULL) {
bool abstract = false;
char *usrname = NULL, *sockname;
#if WITH_ABSTRACT_UNIXSOCKET
abstract = (us->un.sun_path[0] == '\0');
#endif
if (uslen == ((char *)&us->un.sun_path-(char *)us)) {
usrname = NULL;
} else {
#if WITH_ABSTRACT_UNIXSOCKET
if (abstract)
usrname = strndup(us->un.sun_path+1, sizeof(us->un.sun_path)-1);
else
#endif
usrname = strndup(us->un.sun_path, sizeof(us->un.sun_path));
if (usrname == NULL) {
int _errno = errno;
Error2("strndup(\"%s\", "F_Zu"): out of memory",
us->un.sun_path, sizeof(us->un.sun_path));
errno = _errno;
return -1;
}
}
do { /* loop over tempnam bind() attempts */
sockname = xio_tempnam(usrname, abstract);
if (sockname == NULL) {
Error2("tempnam(\"%s\"): %s", usrname, strerror(errno));
free(usrname);
return -1;
}
strncpy(us->un.sun_path+(abstract?1:0), sockname, sizeof(us->un.sun_path));
uslen = sizeof(&((struct sockaddr_un *)0)->sun_path) +
Min(strlen(sockname), sizeof(us->un.sun_path)); /*?*/
free(sockname);
if (Bind(sfd->fd, (struct sockaddr *)us, uslen) < 0) {
Msg4(errno==EADDRINUSE?E_INFO:level, "bind(%d, {%s}, "F_Zd"): %s",
sfd->fd, sockaddr_info((struct sockaddr *)us, uslen,
infobuff, sizeof(infobuff)),
uslen, strerror(errno));
if (errno != EADDRINUSE) {
free(usrname);
Close(sfd->fd);
return STAT_RETRYLATER;
}
} else {
break; /* could bind to path, good, continue past loop */
}
} while (true);
free(usrname);
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
} else
if (us != NULL) {
if (Bind(sfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_Zd"): %s",
sfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
uslen, strerror(errno));
Close(sfd->fd);
return STAT_RETRYLATER;
}
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
}
#endif
applyopts(sfd, sfd->fd, opts, PH_PREBIND);
applyopts(sfd, sfd->fd, opts, PH_BIND);
#endif /* WITH_UNIX */
#if WITH_TCP || WITH_UDP
if (alt) {
} else if (alt) {
union sockaddr_union sin, *sinp;
unsigned short *port, i, N;
div_t dv;
applyopts(sfd, sfd->fd, opts, PH_PREBIND);
applyopts(sfd, sfd->fd, opts, PH_BIND);
/* prepare sockaddr for bind probing */
if (us) {
sinp = us;
@ -2147,15 +2230,12 @@ int xiobind(
return STAT_RETRYLATER;
}
} while (i != N);
} else
#endif /* WITH_TCP || WITH_UDP */
} else {
applyopts(sfd, sfd->fd, opts, PH_PREBIND);
if (us) {
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
}
#endif
applyopts(sfd, sfd->fd, opts, PH_BIND);
if (Bind(sfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_Zd"): %s",
sfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
@ -2164,11 +2244,7 @@ int xiobind(
return STAT_RETRYLATER;
}
}
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_PASTOPEN);
}
#endif
applyopts(sfd, -1, opts, PH_PASTBIND);
return 0;

View file

@ -53,6 +53,7 @@ const struct addrdesc xioaddr_abstract_recv = { "ABSTRACT-RECV", 1+XIO_RD
const struct addrdesc xioaddr_abstract_client = { "ABSTRACT-CLIENT", 1+XIO_RDWR, xioopen_unix_client, GROUP_FD|GROUP_SOCKET|GROUP_SOCK_UNIX|GROUP_RETRY, 1, 0, 0 HELP(":<filename>") };
#endif /* WITH_ABSTRACT_UNIXSOCKET */
const struct optdesc xioopt_unix_bind_tempname = { "unix-bind-tempname", "bind-tempname", OPT_UNIX_BIND_TEMPNAME, GROUP_SOCK_UNIX, PH_PREOPEN, TYPE_STRING_NULL, OFUNC_SPEC };
const struct optdesc xioopt_unix_tightsocklen = { "unix-tightsocklen", "tightsocklen", OPT_UNIX_TIGHTSOCKLEN, GROUP_SOCK_UNIX, PH_PREBIND, TYPE_BOOL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.un.tight), XIO_SIZEOF(para.socket.un.tight) };
@ -90,7 +91,7 @@ xiosetunix(int pf,
len = sizeof(struct sockaddr_un);
}
return len;
}
} else
#endif /* WITH_ABSTRACT_UNIXSOCKET */
if ((pathlen = strlen(path)) > sizeof(saun->sun_path)) {
@ -139,7 +140,7 @@ static int xioopen_unix_listen(
}
name = argv[1];
sfd->para.socket.un.tight = true;
sfd->para.socket.un.tight = UNIX_TIGHTSOCKLEN;
retropt_socket_pf(opts, &pf);
sfd->howtoend = END_SHUTDOWN;
@ -221,11 +222,13 @@ static int xioopen_unix_connect(
struct sockaddr_un them, us;
socklen_t themlen, uslen = sizeof(us);
bool needbind = false;
bool needtemp = false;
bool opt_unlink_close = (addrdesc->arg1/*abstract*/ != 1);
bool dofork = false;
struct opt *opts0;
char infobuff[256];
int level;
char *opt_bind_tempname = NULL;
int result;
if (argc != 2) {
@ -234,7 +237,7 @@ static int xioopen_unix_connect(
}
name = argv[1];
sfd->para.socket.un.tight = true;
sfd->para.socket.un.tight = UNIX_TIGHTSOCKLEN;
retropt_socket_pf(opts, &pf);
sfd->howtoend = END_SHUTDOWN;
if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY;
@ -249,6 +252,7 @@ static int xioopen_unix_connect(
/* Only for non abstract because abstract do not work in file system */
retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close);
}
if (retropt_bind(opts, pf, socktype, protocol, (struct sockaddr *)&us, &uslen,
(addrdesc->arg1/*abstract*/<<1)|sfd->para.socket.un.tight,
sfd->para.socket.ip.ai_flags)
@ -256,18 +260,25 @@ static int xioopen_unix_connect(
needbind = true;
}
if (retropt_string(opts, OPT_UNIX_BIND_TEMPNAME, &opt_bind_tempname) == 0) {
if (needbind) {
Error("do not use both options bind and unix-bind-tempnam");
return -1;
}
needbind = true;
needtemp = true;
xiosetunix(pf, &us, opt_bind_tempname?opt_bind_tempname:"",
addrdesc->arg1/*abstract*/, sfd->para.socket.un.tight);
if (opt_bind_tempname == NULL && !addrdesc->arg1/*abstract*/) {
us.sun_path[0] = 0x01; /* mark as non abstract */
}
}
if (!needbind &&
(namedopt = searchopt(opts, GROUP_NAMED, 0, 0, 0))) {
Error1("Option \"%s\" only with bind option", namedopt->desc->defname);
}
if (opt_unlink_close && needbind) {
if ((sfd->unlink_close = strdup(us.sun_path)) == NULL) {
Error1("strdup(\"%s\"): out of memory", name);
}
sfd->opt_unlink_close = true;
}
retropt_bool(opts, OPT_FORK, &dofork);
opts0 = copyopts(opts, GROUP_ALL);
@ -288,7 +299,7 @@ static int xioopen_unix_connect(
_xioopen_connect(sfd,
needbind?(union sockaddr_union *)&us:NULL, uslen,
(struct sockaddr *)&them, themlen,
opts, pf, socktype, protocol, false, level);
opts, pf, socktype, protocol, needtemp, level);
if (result != 0) {
char infobuff[256];
/* we caller must handle this */
@ -382,10 +393,12 @@ static int xioopen_unix_sendto(
int pf = PF_UNIX;
int socktype = SOCK_DGRAM;
int protocol = 0;
union sockaddr_union us;
struct sockaddr_un us;
socklen_t uslen = sizeof(us);
bool needbind = false;
bool needtemp = false;
bool opt_unlink_close = (addrdesc->arg1/*abstract*/ != 1);
char *opt_bind_tempname = NULL;
int result;
if (argc != 2) {
@ -394,7 +407,7 @@ static int xioopen_unix_sendto(
}
name = argv[1];
sfd->para.socket.un.tight = true;
sfd->para.socket.un.tight = UNIX_TIGHTSOCKLEN;
retropt_socket_pf(opts, &pf);
sfd->howtoend = END_SHUTDOWN;
applyopts_offset(sfd, opts);
@ -408,13 +421,24 @@ static int xioopen_unix_sendto(
sfd->dtype = XIODATA_RECVFROM;
if (retropt_bind(opts, pf, socktype, protocol, &us.soa, &uslen,
if (retropt_bind(opts, pf, socktype, protocol, (struct sockaddr *)&us, &uslen,
(addrdesc->arg1/*abstract*/<<1)| sfd->para.socket.un.tight,
sfd->para.socket.ip.ai_flags)
== STAT_OK) {
needbind = true;
}
if (retropt_string(opts, OPT_UNIX_BIND_TEMPNAME, &opt_bind_tempname) == 0) {
if (needbind) {
Error("do not use both options bind and bind-tempnam");
return STAT_NORETRY;
}
needbind = true;
needtemp = true;
xiosetunix(pf, &us, opt_bind_tempname?opt_bind_tempname:"",
addrdesc->arg1/*abstract*/, sfd->para.socket.un.tight);
}
if (!needbind &&
(namedopt = searchopt(opts, GROUP_NAMED, 0, 0, 0))) {
Error1("Option \"%s\" only with bind option", namedopt->desc->defname);
@ -426,14 +450,14 @@ static int xioopen_unix_sendto(
result =
_xioopen_dgram_sendto(needbind?(union sockaddr_union *)&us:NULL, uslen,
opts, xioflags, sfd, addrdesc->groups,
pf, socktype, protocol, 0);
pf, socktype, protocol, needtemp);
if (result != 0) {
return result;
}
if (opt_unlink_close && needbind) {
if ((sfd->unlink_close = strndup(us.un.sun_path, sizeof(us.un.sun_path))) == NULL) {
Error2("strndup(\"%s\", "F_Zu"): out of memory", name, sizeof(us.un.sun_path));
if ((sfd->unlink_close = strndup(us.sun_path, sizeof(us.sun_path))) == NULL) {
Error2("strndup(\"%s\", "F_Zu"): out of memory", name, sizeof(us.sun_path));
}
sfd->opt_unlink_close = true;
}
@ -468,7 +492,7 @@ int xioopen_unix_recvfrom(
}
name = argv[1];
sfd->para.socket.un.tight = true;
sfd->para.socket.un.tight = UNIX_TIGHTSOCKLEN;
retropt_socket_pf(opts, &pf);
sfd->howtoend = END_NONE;
if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY;
@ -554,7 +578,7 @@ static int xioopen_unix_recv(
}
name = argv[1];
sfd->para.socket.un.tight = true;
sfd->para.socket.un.tight = UNIX_TIGHTSOCKLEN;
retropt_socket_pf(opts, &pf);
sfd->howtoend = END_SHUTDOWN;
if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY;
@ -625,7 +649,7 @@ static int xioopen_unix_client(
return
_xioopen_unix_client(&xxfd->stream, xioflags, addrdesc->groups,
addrdesc->arg1/*abstract*/, opts, argv[1]);
addrdesc->arg1/*abstract*/, opts, argv[1], addrdesc);
}
/* establishes communication with an existing UNIX type socket. supports stream
@ -639,8 +663,15 @@ static int xioopen_unix_client(
OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_CLOEXEC, OPT_USER, OPT_GROUP, ?OPT_FORK,
*/
int
_xioopen_unix_client(xiosingle_t *sfd, int xioflags, groups_t groups,
int abstract, struct opt *opts, const char *name) {
_xioopen_unix_client(
xiosingle_t *sfd,
int xioflags,
groups_t groups,
int abstract,
struct opt *opts,
const char *name,
const struct addrdesc *addrdesc)
{
const struct opt *namedopt;
int pf = PF_UNIX;
int socktype = 0; /* to be determined by server socket type */
@ -648,11 +679,13 @@ _xioopen_unix_client(xiosingle_t *sfd, int xioflags, groups_t groups,
union sockaddr_union them, us;
socklen_t themlen, uslen = sizeof(us);
bool needbind = false;
bool needtemp = false;
bool opt_unlink_close = false;
char *opt_bind_tempname = NULL;
struct opt *opts0;
int result;
sfd->para.socket.un.tight = true;
sfd->para.socket.un.tight = UNIX_TIGHTSOCKLEN;
retropt_socket_pf(opts, &pf);
sfd->howtoend = END_SHUTDOWN;
if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY;
@ -667,6 +700,7 @@ _xioopen_unix_client(xiosingle_t *sfd, int xioflags, groups_t groups,
/* only for non abstract because abstract do not work in file system */
retropt_bool(opts, OPT_UNLINK_CLOSE, &opt_unlink_close);
}
if (retropt_bind(opts, pf, socktype, protocol, &us.soa, &uslen,
(abstract<<1)|sfd->para.socket.un.tight,
sfd->para.socket.ip.ai_flags)
@ -674,6 +708,15 @@ _xioopen_unix_client(xiosingle_t *sfd, int xioflags, groups_t groups,
needbind = true;
}
if (retropt_string(opts, OPT_UNIX_BIND_TEMPNAME, &opt_bind_tempname) == 0) {
if (needbind) {
Error("do not use both options bind and unix-bind-tempname");
return STAT_NORETRY;
}
needbind = true;
needtemp = true;
}
if (!needbind &&
(namedopt = searchopt(opts, GROUP_NAMED, 0, 0, 0))) {
Error1("Option \"%s\" only with bind option", namedopt->desc->defname);
@ -692,40 +735,57 @@ _xioopen_unix_client(xiosingle_t *sfd, int xioflags, groups_t groups,
/* just a breakable block, helps to avoid goto */
do {
/* sfd->dtype = DATA_STREAM; // is default */
if (needtemp)
xiosetunix(pf, &us.un, opt_bind_tempname?opt_bind_tempname:"",
abstract, sfd->para.socket.un.tight);
/* this function handles AF_UNIX with EPROTOTYPE specially for us */
if ((result =
_xioopen_connect(sfd,
needbind?&us:NULL, uslen,
&them.soa, themlen,
opts, pf, socktype?socktype:SOCK_STREAM, protocol,
false, E_INFO)) == 0)
needtemp, E_INFO)) == 0)
break;
if (errno != EPROTOTYPE || socktype != 0)
if ((errno != EPROTOTYPE
#if WITH_ABSTRACT_UNIXSOCKET
&& !(abstract && errno == ECONNREFUSED)
#endif
) || socktype != 0)
break;
if (needbind)
xio_unlink(us.un.sun_path, E_ERROR);
dropopts2(opts, PH_INIT, PH_SPEC); opts = opts0;
if (needtemp)
xiosetunix(pf, &us.un, opt_bind_tempname?opt_bind_tempname:"",
abstract, sfd->para.socket.un.tight);
socktype = SOCK_SEQPACKET;
if ((result =
_xioopen_connect(sfd,
needbind?&us:NULL, uslen,
(struct sockaddr *)&them, themlen,
opts, pf, SOCK_SEQPACKET, protocol,
false, E_INFO)) == 0)
needtemp, E_INFO)) == 0)
break;
if (errno != EPROTOTYPE && errno != EPROTONOSUPPORT/*AIX*/)
if (errno != EPROTOTYPE && errno != EPROTONOSUPPORT/*AIX*/
#if WITH_ABSTRACT_UNIXSOCKET
&& !(abstract && errno == ECONNREFUSED)
#endif
)
break;
if (needbind)
xio_unlink(us.un.sun_path, E_ERROR);
dropopts2(opts, PH_INIT, PH_SPEC); opts = opts0;
if (needtemp)
xiosetunix(pf, &us.un, opt_bind_tempname?opt_bind_tempname:"",
abstract, sfd->para.socket.un.tight);
sfd->peersa = them;
sfd->salen = sizeof(struct sockaddr_un);
sfd->salen = themlen;
if ((result =
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, sfd, groups,
pf, SOCK_DGRAM, protocol, 0))
pf, SOCK_DGRAM, protocol, needtemp))
== 0) {
sfd->dtype = XIODATA_RECVFROM;
break;
@ -733,7 +793,7 @@ _xioopen_unix_client(xiosingle_t *sfd, int xioflags, groups_t groups,
} while (0);
if (result != 0) {
Error2("UNIX-CLIENT:%s: %s", name, strerror(errno));
Error3("%s: %s: %s", addrdesc->defname, name, strerror(errno));
if (needbind)
xio_unlink(us.un.sun_path, E_ERROR);
return result;
@ -767,4 +827,79 @@ xiosetsockaddrenv_unix(int idx, char *namebuff, size_t namelen,
return 0;
}
static const char tmpchars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static size_t numchars = sizeof(tmpchars)-1;
/* Simplyfied version of tempnam(). Uses the current directory when pathx is
not absolute.
Returns a malloc()'ed string with a probably free name,
or NULL when an error occurred */
char *xio_tempnam(
const char *pathx,
bool donttry) /* for abstract, do not check if it exists */
{
int len;
char *X; /* begin of XXXXXX */
unsigned int i = TMP_MAX;
unsigned int r1, r2;
uint64_t v;
char *patht;
char readl[PATH_MAX];
int rlc;
if (pathx == NULL || pathx[0] == '\0')
pathx = "/tmp/socat-bind.XXXXXX";
len = strlen(pathx);
if (len < 6 || strstr(pathx, "XXXXXX") == NULL) {
Warn1("xio_tempnam(\"%s\"): path pattern is not valid", pathx);
errno = EINVAL;
return NULL;
}
patht = strdup(pathx);
if (patht == NULL) {
Error1("strdup("F_Zu"): out of memory", strlen(pathx));
return patht;
}
X = strstr(patht, "XXXXXX");
Debug1("xio_tempnam(\"%s\"): trying path names, suppressing stat() logs",
patht);
while (i > 0) {
r1 = random();
r2 = random();
v = r2*RAND_MAX + r1;
X[0] = tmpchars[v%numchars];
v /= numchars;
X[1] = tmpchars[v%numchars];
v /= numchars;
X[2] = tmpchars[v%numchars];
v /= numchars;
X[3] = tmpchars[v%numchars];
v /= numchars;
X[4] = tmpchars[v%numchars];
v /= numchars;
X[5] = tmpchars[v%numchars];
v /= numchars;
if (donttry)
return patht;
/* readlink() might be faster than lstat() */
rlc = readlink(patht, readl, sizeof(readl));
if (rlc < 0 && errno == ENOENT)
break;
--i;
}
if (i == 0) {
errno = EEXIST;
return NULL;
}
return patht;
}
#endif /* WITH_UNIX */

View file

@ -18,6 +18,7 @@ extern const struct addrdesc xioaddr_abstract_recvfrom;
extern const struct addrdesc xioaddr_abstract_recv;
extern const struct addrdesc xioaddr_abstract_client;
extern const struct optdesc xioopt_unix_bind_tempname;
extern const struct optdesc xioopt_unix_tightsocklen;
extern socklen_t
@ -32,7 +33,8 @@ xiosetsockaddrenv_unix(int idx, char *namebuff, size_t namelen,
struct sockaddr_un *sa, socklen_t salen, int ipproto);
extern int
_xioopen_unix_client(xiosingle_t *xfd, int xioflags, groups_t groups,
int abstract, struct opt *opts, const char *name);
_xioopen_unix_client(xiosingle_t *xfd, int xioflags, groups_t groups, int abstract, struct opt *opts, const char *name, const struct addrdesc *addrdesc);
extern char *xio_tempnam(const char *pathx, bool donttry);
#endif /* !defined(__xio_unix_h_included) */

View file

@ -48,7 +48,7 @@ int xio_chdir(
free(tmp_dir);
return -1;
}
*orig_dir = Realloc(*orig_dir, strlen(*orig_dir+1));
*orig_dir = Realloc(*orig_dir, strlen(*orig_dir)+1);
if (Chdir(tmp_dir) < 0) {
Error2("chdir(\"%s\"): %s", tmp_dir, strerror(errno));

View file

@ -292,6 +292,7 @@ const struct optname optionnames[] = {
IF_OPEN ("binary", &opt_o_binary)
#endif
IF_SOCKET ("bind", &opt_bind)
IF_UNIX ("bind-tempname", &xioopt_unix_bind_tempname)
#ifdef SO_BINDTODEVICE
IF_SOCKET ("bindtodevice", &opt_so_bindtodevice)
#endif
@ -1880,6 +1881,7 @@ const struct optname optionnames[] = {
IF_ANY ("uid-l", &opt_user_late)
IF_NAMED ("umask", &opt_umask)
IF_IP6 ("unicast-hops", &opt_ipv6_unicast_hops)
IF_UNIX ("unix-bind-tempname", &xioopt_unix_bind_tempname)
IF_UNIX ("unix-tightsocklen", &xioopt_unix_tightsocklen)
IF_NAMED ("unlink", &opt_unlink)
IF_NAMED ("unlink-close", &opt_unlink_close)
@ -3223,6 +3225,7 @@ int retropt_bind(struct opt *opts,
3..address and port allowed
UNIX (or'd): 1..tight
2..abstract
4..templatename
*/
const int ai_flags[2])
{
@ -3313,7 +3316,17 @@ int retropt_bind(struct opt *opts,
{
bool abstract = (feats&2);
bool tight = (feats&1);
bool templatename = (feats&4);
struct sockaddr_un *s_un = (struct sockaddr_un *)sa;
if (templatename) {
int i = 0;
srandom(getpid());
for (; i < strlen(bindname); i++) {
if (bindname[i] == 'X') {
bindname[i] = 'a' + (char) (random() % ('z' - 'a'));
}
}
}
*salen = xiosetunix(af, s_un, bindname, abstract, tight);
}
break;

View file

@ -859,7 +859,8 @@ enum e_optcode {
OPT_TUN_NAME, /* tun: tun0 */
OPT_TUN_TYPE, /* tun: tun|tap */
OPT_UMASK,
OPT_UNIX_TIGHTSOCKLEN, /* UNIX domain sockets */
OPT_UNIX_BIND_TEMPNAME, /* UNIX domain sockets */
OPT_UNIX_TIGHTSOCKLEN,
OPT_UNLINK,
OPT_UNLINK_CLOSE,
OPT_UNLINK_EARLY,