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. With this option Socat restores the VLAN tag.
Feature inspired by Zhao Dong. 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: Corrections:
When a sub process (EXEC, SYSTEM) terminated with exit code other than 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/ 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(range)(OPTION_RANGE),
link(tcpwrap)(OPTION_TCPWRAPPERS), link(tcpwrap)(OPTION_TCPWRAPPERS),
link(su)(OPTION_SUBSTUSER), link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR), link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl() link(retry)(OPTION_RETRY)nl()
See also: See also:
link(OPENSSL)(ADDRESS_OPENSSL_CONNECT), 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(range)(OPTION_RANGE),
link(tcpwrap)(OPTION_TCPWRAPPERS), link(tcpwrap)(OPTION_TCPWRAPPERS),
link(su)(OPTION_SUBSTUSER), link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR), link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl() link(retry)(OPTION_RETRY)nl()
link(rcvtimeo)(OPTION_SO_RCVTIMOE)nl() link(rcvtimeo)(OPTION_SO_RCVTIMOE)nl()
See also: See also:
@ -788,7 +788,7 @@ label(ADDRESS_SCTP_LISTEN)dit(bf(tt(SCTP-LISTEN:<port>)))
link(sctp-maxseg)(OPTION_SCTP_MAXSEG), link(sctp-maxseg)(OPTION_SCTP_MAXSEG),
link(sctp-nodelay)(OPTION_SCTP_NODELAY), link(sctp-nodelay)(OPTION_SCTP_NODELAY),
link(su)(OPTION_SUBSTUSER), link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR), link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl() link(retry)(OPTION_RETRY)nl()
See also: See also:
link(SCTP4-LISTEN)(ADDRESS_SCTP4_LISTEN), 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(accept-timeout)(OPTION_ACCEPT_TIMEOUT),
link(mss)(OPTION_MSS), link(mss)(OPTION_MSS),
link(su)(OPTION_SUBSTUSER), link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR), link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY), link(retry)(OPTION_RETRY),
link(cool-write)(OPTION_COOL_WRITE)nl() link(cool-write)(OPTION_COOL_WRITE)nl()
See also: See also:
@ -1413,7 +1413,7 @@ label(ADDRESS_VSOCK_LISTEN)dit(bf(tt(VSOCK-LISTEN:<port>)))
link(max-children)(OPTION_MAX_CHILDREN), link(max-children)(OPTION_MAX_CHILDREN),
link(backlog)(OPTION_BACKLOG), link(backlog)(OPTION_BACKLOG),
link(su)(OPTION_SUBSTUSER), link(su)(OPTION_SUBSTUSER),
link(reuseaddr)(OPTION_REUSEADDR), link(reuseaddr)(OPTION_SO_REUSEADDR),
link(retry)(OPTION_RETRY)nl() link(retry)(OPTION_RETRY)nl()
See also: See also:
link(VSOCK-CONNECT)(ADDRESS_VSOCK_CONNECT) 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>))) 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 Like link(so-recvtimeo)(OPTION_SO_RCVTIMOE), but for code(send). Not usecase
known. 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 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>))) label(OPTION_SNDBUF)dit(bf(tt(sndbuf=<bytes>)))
Sets the size of the send buffer after the code(socket()) call to Sets the size of the send buffer after the code(socket()) call to
<bytes> [link(int)(TYPE_INT)]. <bytes> [link(int)(TYPE_INT)].
@ -3295,7 +3302,7 @@ connection comes in, accepts it, then connects to the remote host
a second connection. a second connection.
label(EXAMPLE_OPTION_BIND_TCP4) label(EXAMPLE_OPTION_BIND_TCP4)
label(EXAMPLE_OPTION_REUSEADDR) label(EXAMPLE_OPTION_SO_REUSEADDR)
label(EXAMPLE_OPTION_FORK) label(EXAMPLE_OPTION_FORK)
label(EXAMPLE_OPTION_SUBSTUSER) label(EXAMPLE_OPTION_SUBSTUSER)
label(EXAMPLE_OPTION_RANGE) 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 process after each code(accept()). It provides a little security by
link(su)(OPTION_SUBSTUSER)'ing to user link(su)(OPTION_SUBSTUSER)'ing to user
nobody after forking; it only permits connections from the private 10 network 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 allows immediate restart after master process's termination, even if some child
sockets are not completely shut down. sockets are not completely shut down.
With link(-lmlocal2)(option_lm), socat logs to stderr until successfully 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)). 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. 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. 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. process.
COMMENT( COMMENT(
@ -3523,7 +3530,7 @@ For each client connecting to port 3335, a new child process is generated
(option link(fork)(OPTION_FORK)). (option link(fork)(OPTION_FORK)).
The contents of the file /tmp/motd is sent to each client. 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). 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. process.
) )
COMMENT( 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_offset(xfd, opts);
applyopts_cloexec(xfd->fd, 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_PREBIND);
applyopts(xfd->fd, opts, PH_BIND); applyopts(xfd->fd, opts, PH_BIND);
if (Bind(xfd->fd, (struct sockaddr *)us, uslen) < 0) { if (Bind(xfd->fd, (struct sockaddr *)us, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_socklen"): %s", xfd->fd, 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}; 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 */ #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_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}; 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 #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 }; 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); applyopts(xfd->fd, opts, PH_PASTBIND);
return 0; 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); xiosocket(struct opt *opts, int pf, int socktype, int proto, int level);
extern int extern int
xiosocketpair(struct opt *opts, int pf, int socktype, int proto, int sv[2]); 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) */ #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 */ while (true) { /* we loop with fork or prohibited packets */
/* now wait for some packet on this datagram socket, get its sender /* now wait for some packet on this datagram socket, get its sender
address, connect there, and return */ address, connect there, and return */
int reuseaddr = dofork; union integral notnull;
union integral reuseaddr;
int doreuseaddr = (dofork != 0); int doreuseaddr = (dofork != 0);
char infobuff[256]; char infobuff[256];
union sockaddr_union _sockname; union sockaddr_union _sockname;
union sockaddr_union *la = &_sockname; /* local address */ union sockaddr_union *la = &_sockname; /* local address */
reuseaddr.u_int = dofork;
if ((sfd->fd = xiosocket(opts, pf, socktype, ipproto, E_ERROR)) < 0) { if ((sfd->fd = xiosocket(opts, pf, socktype, ipproto, E_ERROR)) < 0) {
return STAT_RETRYLATER; 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); applyopts(sfd->fd, opts, PH_PASTSOCKET);
/* SO_REUSEADDR handling of UDP sockets is helpful on Solaris */
if (doreuseaddr) { if (doreuseaddr) {
if (Setsockopt(sfd->fd, opt_so_reuseaddr.major, 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) { < 0) {
Warn6("setsockopt(%d, %d, %d, {%d}, "F_Zd"): %s", Warn6("setsockopt(%d, %d, %d, {%d}, "F_Zd"): %s",
sfd->fd, opt_so_reuseaddr.major, 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)); 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, Info2("setting option \"%s\" to %d", ent->desc->defname,
(*opts)[i].value.u_int); (*opts)[i].value.u_int);
break; 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: case TYPE_BOOL:
if (!assign) { if (!assign) {
(*opts)[i].value.u_bool = 1; (*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_INT: *result = opt->value.u_int; break;
case TYPE_STRING: *result = strtol(opt->value.u_string, &rest, 0); case TYPE_STRING: *result = strtol(opt->value.u_string, &rest, 0);
if (*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); opt->desc->defname);
} }
break; break;
@ -2941,6 +2956,36 @@ int retropt_int(struct opt *opts, int optcode, int *result) {
return -1; 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, /* Looks for the first option of type <optcode>. If the option is found,
this function stores its unsigned int value in *result, "consumes" the this function stores its unsigned int value in *result, "consumes" the
option, and returns 0. 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; opt->desc = ODESC_ERROR; ++opt; continue;
} }
break; 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: case TYPE_LONG:
if (Setsockopt(fd, opt->desc->major, opt->desc->minor, if (Setsockopt(fd, opt->desc->major, opt->desc->minor,
&opt->value.u_long, sizeof(long)) < 0) { &opt->value.u_long, sizeof(long)) < 0) {

View file

@ -27,6 +27,7 @@ enum e_types {
TYPE_BYTE, /* unsigned char */ TYPE_BYTE, /* unsigned char */
TYPE_INT, /* int */ TYPE_INT, /* int */
TYPE_INT_NULL, /* int, or opt= for no action (instead of default) */
TYPE_LONG, /* long */ TYPE_LONG, /* long */
TYPE_STRING, /* char * */ TYPE_STRING, /* char * */
TYPE_NAME = TYPE_STRING, TYPE_NAME = TYPE_STRING,
@ -125,7 +126,7 @@ enum e_func {
groups. to keep the search for options simple, we allow each option to 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 belong to at most one group only. (we have a dummy GROUP_NONE for those
that don't want to belong to any...) 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. 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_short(struct opt *opts, int optcode, short *result);
extern int retropt_ushort(struct opt *opts, int optcode, unsigned 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_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_uint(struct opt *opts, int optcode, unsigned int *result);
extern int retropt_long(struct opt *opts, int optcode, long *result); extern int retropt_long(struct opt *opts, int optcode, long *result);
extern int retropt_ulong(struct opt *opts, int optcode, unsigned long *result); extern int retropt_ulong(struct opt *opts, int optcode, unsigned long *result);