Socket functions: _poll becomes _prepoll, which never waits

A layer over the sockets, TLS or a plugin's, no longer waits itself.
Its _prepoll only tells what it knows without asking the system: the
fds it knows to be ready, data TLS has already decrypted, or, returning
0, that the sockets have to be polled, for events it may rewrite: a
TLS read waiting for its socket to be writable. The wait is
sockpoll()'s, which calls _prepoll first and then polls, so that a wait
can be made other than with poll() in one place: a client of an event
loop is to wait in the loop. Whatever a rewritten wait gets is given to
the caller as what it asked for, its chance to try again, and the
events it asked for are given back. With no fds sockpoll() sleeps,
which WSAPoll() would refuse.

TLS remembers what a read or a write it could not do waits for. A read
needing to write, or a write to read, was an error which ended the
connection, as when TLS 1.3 answers a key update while the socket
cannot take more; it now waits for what TLS needs, as one needing what
it was asked for always did.

Incompatible for plugins, which have to be built again with the same
version of 3proxy, the structures they share not kept compatible:
- struct sockfuncs has _prepoll(state, fds, nfds) in place of
  _poll(state, fds, nfds, timeout). A plugin replacing _poll replaces
  _prepoll: it must not wait, returns the number of fds it set revents
  of (events then left as they are), 0 for the fds to be polled (with
  events as it leaves them, revents left to the poll) or -1, and calls
  the _prepoll it replaced when it has nothing to say itself.
- Waiting for sockets is sockpoll(sf, state, fds, nfds, timeout), not
  so._poll() of the socket functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-24 19:54:03 +03:00
parent 718798c7fc
commit b4bbcb9e9a
16 changed files with 109 additions and 53 deletions

View File

@ -560,7 +560,7 @@ int radsend(struct clientparam * param, int auth, int stop){
memset(fds, 0, sizeof(fds));
fds[0].fd = remsock;
fds[0].events = POLLIN;
if(so._poll(so.state, fds, 1, param->srv->config->timeouts[SINGLEBYTE_L]*1000) <= 0) {
if(sockpoll(&so, so.state, fds, 1, param->srv->config->timeouts[SINGLEBYTE_L]*1000) <= 0) {
continue;
}

View File

@ -360,16 +360,8 @@ int
int WINAPI def_setsockopt(void* state, SOCKET s, int level, int optname, const char *optval, int optlen){
return setsockopt(s, level, optname, optval, optlen);
}
int WINAPI def_poll(void* state, struct pollfd *fds, unsigned int nfds, int timeout){
#ifndef WITH_POLL
#ifndef WITH_WSAPOLL
return mypoll(fds, nfds, timeout);
#else
return WSAPoll(fds, nfds, timeout);
#endif
#else
return poll(fds, nfds, timeout);
#endif
int WINAPI def_prepoll(void* state, struct pollfd *fds, unsigned int nfds){
return 0;
}
int WINAPI def_send(void* state, SOCKET s, const char *msg, int len, int flags){
return send(s, msg, len, flags);
@ -419,12 +411,8 @@ int
return setsockopt(s, level, optname, optval, optlen);
}
int def_poll(void* state, struct pollfd *fds, nfds_t nfds, int timeout){
#ifndef WITH_POLL
return mypoll(fds, nfds, timeout);
#else
return poll(fds, nfds, timeout);
#endif
int def_prepoll(void* state, struct pollfd *fds, nfds_t nfds){
return 0;
}
/* a peer gone is an error of the send, not SIGPIPE, where the system can
@ -466,7 +454,7 @@ struct sockfuncs so = {
def_getsockname,
def_getsockopt,
def_setsockopt,
def_poll,
def_prepoll,
def_send,
def_sendto,
def_recv,
@ -475,6 +463,43 @@ struct sockfuncs so = {
def_closesocket
};
/* Waits as poll() does for fds of sockets made with sf, once their layers
know of nothing ready already. A layer may have a socket waited for
other than as asked, a TLS read for it to be writable: whatever it gets
is then the caller's chance to try again, revents what the caller asked
for, and events are given back as they were. With no fds it only
sleeps. */
#define POLLFAIL (POLLERR|POLLHUP|POLLNVAL)
int sockpoll(struct sockfuncs *sf, void *state, struct pollfd *fds, unsigned nfds, int timeout){
short own[64], *events = own;
unsigned i;
int res;
if(!nfds){
if(timeout > 0) usleep(timeout * SLEEPTIME);
return 0;
}
if(nfds > sizeof(own)/sizeof(own[0]) && !(events = malloc(nfds * sizeof(*events)))) return -1;
for(i = 0; i < nfds; i++) events[i] = fds[i].events;
if(!(res = sf->_prepoll(state, fds, nfds))){
#ifndef WITH_POLL
#ifndef WITH_WSAPOLL
res = mypoll(fds, nfds, timeout);
#else
res = WSAPoll(fds, nfds, timeout);
#endif
#else
res = poll(fds, (nfds_t)nfds, timeout);
#endif
for(i = 0; res > 0 && i < nfds; i++)
if(fds[i].events != events[i] && (fds[i].revents & ~POLLFAIL))
fds[i].revents = events[i] | (fds[i].revents & POLLFAIL);
}
for(i = 0; i < nfds; i++) fds[i].events = events[i];
if(events != own) free(events);
return res;
}
#ifdef _WINCE
static char cebuf[1024];
@ -664,7 +689,7 @@ int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa,
memset(fds, 0, sizeof(fds));
fds[0].fd = sock;
fds[0].events = POLLOUT;
if((param?param->srv->so._poll(param->sostate, fds, 1, to*1000):so._poll(so.state, fds, 1, to*1000)) <= 0 || !(fds[0].revents & POLLOUT) || (fds[0].revents & (POLLERR|POLLHUP))) {
if((param?sockpoll(&param->srv->so, param->sostate, fds, 1, to*1000):sockpoll(&so, so.state, fds, 1, to*1000)) <= 0 || !(fds[0].revents & POLLOUT) || (fds[0].revents & (POLLERR|POLLHUP))) {
return 13;
}
return 0;

View File

@ -222,7 +222,7 @@ void * ftpprchild(struct clientparam* param) {
fds.fd = clidatasock;
fds.events = POLLIN;
res = param->srv->so._poll (param->sostate, &fds, 1, param->srv->timeouts[STRING_L]*1000);
res = sockpoll(&param->srv->so, param->sostate, &fds, 1, param->srv->timeouts[STRING_L]*1000);
if(res != 1) {
RETURN(857);
}

View File

@ -1190,7 +1190,7 @@ static void httpsrv_linger(struct clientparam *param)
memset(&fd, 0, sizeof(fd));
fd.fd = param->clisock;
fd.events = POLLIN;
if(param->srv->so._poll(param->sostate, &fd, 1, timeo * 1000) <= 0) break;
if(sockpoll(&param->srv->so, param->sostate, &fd, 1, timeo * 1000) <= 0) break;
got = (int)param->srv->so._recv(param->sostate, param->clisock, buf,
(left > HTTPSRV_BLOCK)? (int)HTTPSRV_BLOCK : (int)left, 0);
if(got <= 0) break;
@ -1225,7 +1225,7 @@ static int proxypass(struct clientparam *param, struct httpreq *r)
memset(&fd, 0, sizeof(fd));
fd.fd = param->remsock;
fd.events = POLLIN;
if(param->srv->so._poll(param->sostate, &fd, 1, 0) > 0
if(sockpoll(&param->srv->so, param->sostate, &fd, 1, 0) > 0
&& (fd.revents & (POLLIN|POLLHUP|POLLERR|POLLNVAL))){
/* anything arriving now belongs to no request */
stale = 1;

View File

@ -47,7 +47,7 @@ static void serveparam(struct clientparam *param){
fds.events = POLLIN;
fds.revents = 0;
for(i=5+(param->srv->maxchild>>10); i; i--){
if(param->srv->so._poll(param->sostate, &fds, 1, 1000*CONNBACK_TO)!=1){
if(sockpoll(&param->srv->so, param->sostate, &fds, 1, 1000*CONNBACK_TO)!=1){
dolog(param, (unsigned char *)"Connect back not received, check connback client");
i = 0;
break;
@ -86,7 +86,7 @@ static void serveparam(struct clientparam *param){
rf.fd = param->remsock;
rf.events = POLLIN;
rf.revents = 0;
if(param->srv->so._poll(param->sostate, &rf, 1, 0) == 1){
if(sockpoll(&param->srv->so, param->sostate, &rf, 1, 0) == 1){
param->srv->so._closesocket(param->sostate, param->remsock);
param->remsock = INVALID_SOCKET;
if(skips > 0){ skips--; i++; }

View File

@ -210,7 +210,7 @@ static int tcploop(struct srvstart *s, int *haveslot){
}
if (connectback) break;
if (srv->config->paused != srv->paused) break;
error = srv->so._poll(srv->so.state, &srv->fds, 1, 1000);
error = sockpoll(&srv->so, srv->so.state, &srv->fds, 1, 1000);
if (error >= 1) break;
if (error == 0) continue;
if (errno != EAGAIN && errno != EINTR) {

View File

@ -638,7 +638,7 @@ static int udploop(struct srvstart *s){
st.of[nfds++] = ss;
}
}
n = srv->so._poll(srv->so.state, st.fds, nfds, timeo);
n = sockpoll(&srv->so, srv->so.state, st.fds, nfds, timeo);
if(n < 0 && errno != EINTR && errno != EAGAIN){
res = 91;
break;

View File

@ -411,9 +411,9 @@ static int copyfdtosock(struct fp_stream * fps, DIRECTION which, long len){
}
#ifdef _WIN32
static int WINAPI fp_poll(void *state, struct pollfd *fds, unsigned int nfds, int timeout){
static int WINAPI fp_prepoll(void *state, struct pollfd *fds, unsigned int nfds){
#else
static int fp_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
static int fp_prepoll(void *state, struct pollfd *fds, nfds_t nfds){
#endif
struct fp_stream *fps = NULL;
int res;
@ -458,7 +458,7 @@ static int fp_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
}
}
return sso._poll(sso.state, fds, nfds, timeout);
return sso._prepoll(sso.state, fds, nfds);
}
#ifdef _WIN32
@ -916,7 +916,7 @@ static int file_loaded=0;
file_loaded = 1;
pl = pluginlink;
memcpy(&sso, pl->so, sizeof(struct sockfuncs));
pl->so->_poll = fp_poll;
pl->so->_prepoll = fp_prepoll;
pl->so->_send = fp_send;
pl->so->_sendto = fp_sendto;
pl->so->_recv = fp_recv;

View File

@ -567,7 +567,7 @@ for(;;){
fds[0].events = POLLIN;
fds[1].fd = param->remsock;
fds[1].events = POLLIN;
res = param->srv->so._poll(param->sostate, fds, 2, param->srv->timeouts[STRING_S]*1000);
res = sockpoll(&param->srv->so, param->sostate, fds, 2, param->srv->timeouts[STRING_S]*1000);
if(res<=0) {
RETURN(555);
}

View File

@ -332,6 +332,7 @@ int nsbroken(void);
/* a function everywhere, never a macro: its callers have preprocessor
lines among its arguments */
SOCKET nssocket(struct srvparam *srv, struct sockfuncs *sf, void *state, int side, int domain, int type, int protocol);
int sockpoll(struct sockfuncs *sf, void *state, struct pollfd *fds, unsigned nfds, int timeout);
int socksend(struct clientparam *param, SOCKET sock, unsigned char * buf, int bufsize, int to);
int socksendto(struct clientparam *param, SOCKET sock, struct sockaddr * sin, unsigned char * buf, int bufsize, int to);
int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin, unsigned char * buf, int bufsize, int to);

View File

@ -23,7 +23,7 @@ int socksend(struct clientparam *param, SOCKET sock, unsigned char * buf, int bu
continue;
}
if(res < 0 && errno != EAGAIN && errno != EINTR) break;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to*1000):so._poll(so.state, &fds, 1, to*1000);
res = param?sockpoll(&param->srv->so, param->sostate, &fds, 1, to*1000):sockpoll(&so, so.state, &fds, 1, to*1000);
if(res < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if(res < 1) break;
} while (sent < bufsize);
@ -40,7 +40,7 @@ int socksendto(struct clientparam *param, SOCKET sock, struct sockaddr * sin, un
do {
if(timetoexit) return 0;
fds.events = POLLOUT;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
res = param?sockpoll(&param->srv->so, param->sostate, &fds, 1, to):sockpoll(&so, so.state, &fds, 1, to);
if(res < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if(res < 1) break;
res = param?param->srv->so._sendto(param->sostate, sock, (char *)buf + sent, bufsize - sent, 0, sin, SASIZE(sin)):so._sendto(so.state, sock, (char *)buf + sent, bufsize - sent, 0, sin, SASIZE(sin));
@ -62,7 +62,7 @@ int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin,
fds.events = POLLIN;
for(;;){
if(timetoexit) return EOF;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
res = param?sockpoll(&param->srv->so, param->sostate, &fds, 1, to):sockpoll(&so, so.state, &fds, 1, to);
if (res<1) return 0;
sasize = sizeof(PROXYSOCKADDRTYPE);
/* sin NULL for a stream socket: nothing to learn the address into */

View File

@ -260,7 +260,7 @@ log(logbuf);
memset(fds, 0, sizeof(fds));
fds[0].fd = param->clisock;
fds[1].fd = param->remsock;
param->srv->so._poll(param->sostate, fds, 2, sleeptime);
sockpoll(&param->srv->so, param->sostate, fds, 2, sleeptime);
sleeptime = 0;
}
if((param->srv->logdumpsrv && (param->statssrv64 > param->srv->logdumpsrv)) ||
@ -966,7 +966,7 @@ log("ready reading from server pipe");
#ifdef WITHLOG
log("entering poll");
#endif
res = param->srv->so._poll(param->sostate, fds, fdsc,
res = sockpoll(&param->srv->so, param->sostate, fds, fdsc,
((CLIENTTERMREAD || SERVERTERMREAD)?
MIN(timeo, param->srv->timeouts[LINGER_TO]) : timeo) * 1000);
#ifdef WITHLOG

View File

@ -355,7 +355,7 @@ fflush(stderr);
fds[0].fd = param->remsock;
fds[1].fd = param->clisock;
fds[0].events = fds[1].events = POLLIN;
res = param->srv->so._poll(param->sostate, fds, 2, param->srv->timeouts[CONNECTION_L] * 1000);
res = sockpoll(&param->srv->so, param->sostate, fds, 2, param->srv->timeouts[CONNECTION_L] * 1000);
if (res < 1 || fds[1].revents) {
res = 460;
break;

View File

@ -75,6 +75,9 @@ static int ktls = 0;
struct SSLsock {
SOCKET s;
SSL_CONN conn;
/* what a read and a write which could not be done wait for, POLLIN
or POLLOUT, 0 once one is: TLS may have to write to read */
short rwant, wwant;
};
struct SSLstate {
@ -103,10 +106,12 @@ static void addSSL(
if (cli_s != INVALID_SOCKET){
SOSTATE->cli.s = cli_s;
SOSTATE->cli.conn = cli_conn;
SOSTATE->cli.rwant = SOSTATE->cli.wwant = 0;
}
if (srv_s != INVALID_SOCKET){
SOSTATE->srv.s = srv_s;
SOSTATE->srv.conn = srv_conn;
SOSTATE->srv.rwant = SOSTATE->srv.wwant = 0;
}
}
@ -137,12 +142,14 @@ static ssize_t ssl_send(void *state, SOCKET s, const void *msg, size_t len, int
int res, err;
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_WRITE){
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->wwant = 0;
return res;
}
@ -159,14 +166,16 @@ static ssize_t ssl_sendto(void *state, SOCKET s, const void *msg, size_t len, in
if ((sslq = searchSSL(state, s))){
int res, err;
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0) {
if((res = ssl_write(sslq->conn, (void *)msg, len)) <= 0){
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_WRITE){
if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ){
sslq->wwant = (err == SSL_ERROR_WANT_READ)? POLLIN : POLLOUT;
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->wwant = 0;
return res;
}
@ -184,12 +193,14 @@ static ssize_t ssl_recvfrom(void *state, SOCKET s, void *msg, size_t len, int f
int res, err;
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_READ) {
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->rwant = 0;
return res;
}
return sso._recvfrom(sso.state, s, msg, len, flags, from, fromlen);
@ -206,12 +217,14 @@ static ssize_t ssl_recv(void *state, SOCKET s, void *msg, size_t len, int flags)
int res, err;
if((res = ssl_read(sslq->conn, (void *)msg, len)) <= 0) {
err = SSL_get_error((SSL *)((ssl_conn*)sslq->conn)->ssl, res);
if (err == SSL_ERROR_WANT_READ) {
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
sslq->rwant = (err == SSL_ERROR_WANT_WRITE)? POLLOUT : POLLIN;
seterrno3(EAGAIN);
return -1;
}
else seterrno3(err);
}
else sslq->rwant = 0;
return res;
}
@ -228,14 +241,18 @@ static int WINAPI ssl_closesocket(void *state, SOCKET s){
return sso._closesocket(sso.state, s);
}
/* Data already decrypted makes its socket readable. A read or a write TLS
could not do has its socket waited for as TLS needs it. */
#ifdef _WIN32
static int WINAPI ssl_poll(void *state, struct pollfd *fds, unsigned int nfds, int timeout){
static int WINAPI ssl_prepoll(void *state, struct pollfd *fds, unsigned int nfds){
#else
static int ssl_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
static int ssl_prepoll(void *state, struct pollfd *fds, nfds_t nfds){
#endif
struct SSLsock *sslq = NULL;
unsigned int i;
int ret = 0;
short events;
for(i = 0; i < nfds; i++){
if((fds[i].events & POLLIN) && (sslq = searchSSL(state, fds[i].fd)) && ssl_pending(sslq->conn)){
fds[i].revents = POLLIN;
@ -244,9 +261,14 @@ static int ssl_poll(void *state, struct pollfd *fds, nfds_t nfds, int timeout){
else fds[i].revents = 0;
}
if(ret) return ret;
ret = sso._poll(state, fds, nfds, timeout);
return ret;
for(i = 0; i < nfds; i++){
if(!(sslq = searchSSL(state, fds[i].fd))) continue;
events = fds[i].events & ~(POLLIN|POLLOUT);
if(fds[i].events & POLLIN) events |= sslq->rwant? sslq->rwant : POLLIN;
if(fds[i].events & POLLOUT) events |= sslq->wwant? sslq->wwant : POLLOUT;
fds[i].events = events;
}
return sso._prepoll(sso.state, fds, nfds);
}
SSL_CONN ssl_handshake_to_server(SOCKET s, char * hostname, SSL_CONFIG *config, SSL_CERT *server_cert, char **errSSL)
@ -298,7 +320,7 @@ SSL_CONN ssl_handshake_to_server(SOCKET s, char * hostname, SSL_CONFIG *config,
fds[0].events = POLLOUT;
}
else break;
if(sso._poll(sso.state, fds, 1, config->timeout*1000) <= 0 || !(fds[0].revents & (POLLOUT|POLLIN))) break;
if(sockpoll(&sso, sso.state, fds, 1, config->timeout*1000) <= 0 || !(fds[0].revents & (POLLOUT|POLLIN))) break;
} while (err == -1);
if ( err != 1 ) {
@ -373,7 +395,7 @@ SSL_CONN ssl_handshake_to_client(SOCKET s, SSL_CONFIG *config, X509 *server_cert
fds[0].events = POLLOUT;
}
else break;
if(sso._poll(sso.state, fds, 1, config->timeout*1000) <= 0 || !(fds[0].revents & (POLLOUT|POLLIN))) break;
if(sockpoll(&sso, sso.state, fds, 1, config->timeout*1000) <= 0 || !(fds[0].revents & (POLLOUT|POLLIN))) break;
} while (err == -1);
@ -737,7 +759,7 @@ static void* ssl_filter_open(void * idata, struct srvparam * srv){
srv->so._recvfrom = ssl_recvfrom;
srv->so._shutdown = ssl_shutdown;
srv->so._closesocket = ssl_closesocket;
srv->so._poll = ssl_poll;
srv->so._prepoll = ssl_prepoll;
}
if(sc && (sc->mitm || sc->cli)){
#if !defined(WITH_WOLFSSL) && OPENSSL_VERSION_NUMBER < 0x10100000L

View File

@ -573,6 +573,14 @@ struct sockoptset {
};
/* _prepoll never waits: it tells what a layer over the socket knows to be
ready without asking the system, data a TLS connection has already
decrypted, and what the system is to be asked instead of what the caller
asked, events rewritten: a TLS read waiting for its socket to be
writable. It returns the number of fds it has set revents of, the rest
to be ignored and events left as they are; 0 when the sockets have to be
polled, for their events as it leaves them, revents left to the poll;
or -1. The wait itself is sockpoll()'s, which calls it first. */
struct sockfuncs {
void * state;
void (*freefunc)(void* state);
@ -586,7 +594,7 @@ struct sockfuncs {
int (WINAPI *_getsockname)(void* state, SOCKET s, struct sockaddr * name, int * namelen);
int (WINAPI *_getsockopt)(void* state, SOCKET s, int level, int optname, char * optval, int * optlen);
int (WINAPI *_setsockopt)(void* state, SOCKET s, int level, int optname, const char *optval, int optlen);
int (WINAPI *_poll)(void* state, struct pollfd *fds, unsigned int nfds, int timeout);
int (WINAPI *_prepoll)(void* state, struct pollfd *fds, unsigned int nfds);
int (WINAPI *_send)(void* state, SOCKET s, const char *msg, int len, int flags);
int (WINAPI *_sendto)(void* state, SOCKET s, const char *msg, int len, int flags, const struct sockaddr *to, int tolen);
int (WINAPI *_recv)(void* state, SOCKET s, char *buf, int len, int flags);
@ -603,7 +611,7 @@ struct sockfuncs {
int (*_getsockname)(void* state, SOCKET s, struct sockaddr * name, socklen_t * namelen);
int (*_getsockopt)(void* state, SOCKET s, int level, int optname, void * optval, socklen_t * optlen);
int (*_setsockopt)(void* state, int s, int level, int optname, const void *optval, socklen_t optlen);
int (*_poll)(void* state, struct pollfd *fds, nfds_t nfds, int timeout);
int (*_prepoll)(void* state, struct pollfd *fds, nfds_t nfds);
ssize_t (*_send)(void* state, SOCKET s, const void *msg, size_t len, int flags);
ssize_t (*_sendto)(void* state, SOCKET s, const void *msg, size_t len, int flags, const struct sockaddr *to, SASIZETYPE tolen);
ssize_t (*_recv)(void* state, SOCKET s, void *buf, size_t len, int flags);

View File

@ -209,7 +209,7 @@ int udpsockmap(struct clientparam *param, int timeo)
udpfds(param, &p);
for (;;) {
res = param->srv->so._poll(param->sostate, p.fds, p.nfds, timeo * 1000);
res = sockpoll(&param->srv->so, param->sostate, p.fds, p.nfds, timeo * 1000);
if (res < 0) return 481;
if (res == 0) return 92;