pxtcp.c revision 336abf709b664cf025b12d41a0970c332e21b0c2
/* -*- indent-tabs-mode: nil; -*- */
#define LOG_GROUP LOG_GROUP_NAT_SERVICE
#include "winutils.h"
#include "pxtcp.h"
#include "proxy.h"
#include "proxy_pollmgr.h"
#include "pxremap.h"
#include "portfwd.h" /* fwspec */
#ifndef RT_OS_WINDOWS
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#ifdef RT_OS_SOLARIS
#include <sys/filio.h> /* FIONREAD is BSD'ism */
#endif
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <err.h> /* BSD'ism */
#else
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iprt/stdint.h>
#include "winpoll.h"
#endif
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/tcpip.h"
#include "lwip/netif.h"
#include "lwip/tcp_impl.h" /* XXX: to access tcp_abandon() */
#include "lwip/icmp.h"
#include "lwip/icmp6.h"
/*
* Different OSes have different quirks in reporting POLLHUP for TCP
* sockets.
*
* Using shutdown(2) "how" values here would be more readable, but
* since SHUT_RD is 0, we can't use 0 for "none", unfortunately.
*/
#if defined(RT_OS_NETBSD) || defined(RT_OS_SOLARIS)
# define HAVE_TCP_POLLHUP 0 /* not reported */
#elif defined(RT_OS_DARWIN)
# define HAVE_TCP_POLLHUP POLLIN /* reported when remote closes */
#else
# define HAVE_TCP_POLLHUP (POLLIN|POLLOUT) /* reported when both directions are closed */
#endif
/**
* Ring buffer for inbound data. Filled with data from the host
* socket on poll manager thread. Data consumed by scheduling
* tcp_write() to the pcb on the lwip thread.
*
* NB: There is actually third party present, the lwip stack itself.
* Thus the buffer doesn't have dual free vs. data split, but rather
* three-way free / send and unACKed data / unsent data split.
*/
struct ringbuf {
char *buf;
size_t bufsize;
/*
* Start of free space, producer writes here (up till "unacked").
*/
volatile size_t vacant;
/*
* Start of sent but unacknowledged data. The data are "owned" by
* the stack as it may need to retransmit. This is the free space
* limit for producer.
*/
volatile size_t unacked;
/*
* Start of unsent data, consumer reads/sends from here (up till
* "vacant"). Not declared volatile since it's only accessed from
* the consumer thread.
*/
size_t unsent;
};
/**
*/
struct pxtcp {
/**
* Our poll manager handler. Must be first, strong/weak
* references depend on this "inheritance".
*/
struct pollmgr_handler pmhdl;
/**
* lwIP (internal/guest) side of the proxied connection.
*/
struct tcp_pcb *pcb;
/**
* Host (external) side of the proxied connection.
*/
SOCKET sock;
/**
* Socket events we are currently polling for.
*/
int events;
/**
* Socket error. Currently used to save connect(2) errors so that
* we can decide if we need to send ICMP error.
*/
int sockerr;
/**
* Interface that we have got the SYN from. Needed to send ICMP
* with correct source address.
*/
struct netif *netif;
/**
* For tentatively accepted connections for which we are in
* process of connecting to the real destination this is the
* initial pbuf that we might need to build ICMP error.
*
* When connection is established this is used to hold outbound
* pbuf chain received by pxtcp_pcb_recv() but not yet completely
* forwarded over the socket. We cannot "return" it to lwIP since
* the head of the chain is already sent and freed.
*/
struct pbuf *unsent;
/**
* Guest has closed its side. Reported to pxtcp_pcb_recv() only
* once and we might not be able to forward it immediately if we
* have unsent pbuf.
*/
int outbound_close;
/**
* Outbound half-close has been done on the socket.
*/
int outbound_close_done;
/**
* External has closed its side. We might not be able to forward
* it immediately if we have unforwarded data.
*/
int inbound_close;
/**
* Inbound half-close has been done on the pcb.
*/
int inbound_close_done;
/**
* On systems that report POLLHUP as soon as the final FIN is
* received on a socket we cannot continue polling for the rest of
* input, so we have to read (pull) last data from the socket on
* the lwIP thread instead of polling/pushing it from the poll
* manager thread. See comment in pxtcp_pmgr_pump() POLLHUP case.
*/
int inbound_pull;
/**
* When poll manager schedules delete we may not be able to delete
* a pxtcp immediately if not all inbound data has been acked by
* the guest: lwIP may need to resend and the data are in pxtcp's
* inbuf::buf. We defer delete until all data are acked to
* pxtcp_pcb_sent().
*/
int deferred_delete;
/**
* Ring-buffer for inbound data.
*/
struct ringbuf inbuf;
/**
* lwIP thread's strong reference to us.
*/
struct pollmgr_refptr *rp;
/*
* We use static messages to call functions on the lwIP thread to
* void malloc/free overhead.
*/
struct tcpip_msg msg_delete; /* delete pxtcp */
struct tcpip_msg msg_reset; /* reset connection and delete pxtcp */
struct tcpip_msg msg_accept; /* confirm accept of proxied connection */
struct tcpip_msg msg_outbound; /* trigger send of outbound data */
struct tcpip_msg msg_inbound; /* trigger send of inbound data */
struct tcpip_msg msg_inpull; /* trigger pull of last inbound data */
};
static struct pxtcp *pxtcp_allocate(void);
static void pxtcp_free(struct pxtcp *);
static void pxtcp_pcb_associate(struct pxtcp *, struct tcp_pcb *);
static void pxtcp_pcb_dissociate(struct pxtcp *);
/* poll manager callbacks for pxtcp related channels */
static int pxtcp_pmgr_chan_add(struct pollmgr_handler *, SOCKET, int);
static int pxtcp_pmgr_chan_pollout(struct pollmgr_handler *, SOCKET, int);
static int pxtcp_pmgr_chan_pollin(struct pollmgr_handler *, SOCKET, int);
#if !(HAVE_TCP_POLLHUP & POLLOUT)
static int pxtcp_pmgr_chan_del(struct pollmgr_handler *, SOCKET, int);
#endif
static int pxtcp_pmgr_chan_reset(struct pollmgr_handler *, SOCKET, int);
/* helper functions for sending/receiving pxtcp over poll manager channels */
static ssize_t pxtcp_chan_send(enum pollmgr_slot_t, struct pxtcp *);
static ssize_t pxtcp_chan_send_weak(enum pollmgr_slot_t, struct pxtcp *);
static struct pxtcp *pxtcp_chan_recv(struct pollmgr_handler *, SOCKET, int);
static struct pxtcp *pxtcp_chan_recv_strong(struct pollmgr_handler *, SOCKET, int);
/* poll manager callbacks for individual sockets */
static int pxtcp_pmgr_connect(struct pollmgr_handler *, SOCKET, int);
static int pxtcp_pmgr_pump(struct pollmgr_handler *, SOCKET, int);
/* get incoming traffic into ring buffer */
static ssize_t pxtcp_sock_read(struct pxtcp *, int *);
static ssize_t pxtcp_sock_recv(struct pxtcp *, IOVEC *, size_t); /* default */
/* convenience functions for poll manager callbacks */
static int pxtcp_schedule_delete(struct pxtcp *);
static int pxtcp_schedule_reset(struct pxtcp *);
static int pxtcp_schedule_reject(struct pxtcp *);
/* lwip thread callbacks called via proxy_lwip_post() */
static void pxtcp_pcb_delete_pxtcp(void *);
static void pxtcp_pcb_reset_pxtcp(void *);
static void pxtcp_pcb_accept_refuse(void *);
static void pxtcp_pcb_accept_confirm(void *);
static void pxtcp_pcb_write_outbound(void *);
static void pxtcp_pcb_write_inbound(void *);
static void pxtcp_pcb_pull_inbound(void *);
/* tcp pcb callbacks */
static err_t pxtcp_pcb_heard(void *, struct tcp_pcb *, err_t); /* global */
static err_t pxtcp_pcb_accept(void *, struct tcp_pcb *, err_t);
static err_t pxtcp_pcb_connected(void *, struct tcp_pcb *, err_t);
static err_t pxtcp_pcb_recv(void *, struct tcp_pcb *, struct pbuf *, err_t);
static err_t pxtcp_pcb_sent(void *, struct tcp_pcb *, u16_t);
static err_t pxtcp_pcb_poll(void *, struct tcp_pcb *);
static void pxtcp_pcb_err(void *, err_t);
static err_t pxtcp_pcb_forward_outbound(struct pxtcp *, struct pbuf *);
static void pxtcp_pcb_forward_outbound_close(struct pxtcp *);
static ssize_t pxtcp_sock_send(struct pxtcp *, IOVEC *, size_t);
static void pxtcp_pcb_forward_inbound(struct pxtcp *);
static void pxtcp_pcb_forward_inbound_close(struct pxtcp *);
DECLINLINE(int) pxtcp_pcb_forward_inbound_done(const struct pxtcp *);
static void pxtcp_pcb_schedule_poll(struct pxtcp *);
static void pxtcp_pcb_cancel_poll(struct pxtcp *);
static void pxtcp_pcb_reject(struct netif *, struct tcp_pcb *, struct pbuf *, int);
DECLINLINE(void) pxtcp_pcb_maybe_deferred_delete(struct pxtcp *);
/* poll manager handlers for pxtcp channels */
static struct pollmgr_handler pxtcp_pmgr_chan_add_hdl;
static struct pollmgr_handler pxtcp_pmgr_chan_pollout_hdl;
static struct pollmgr_handler pxtcp_pmgr_chan_pollin_hdl;
#if !(HAVE_TCP_POLLHUP & POLLOUT)
static struct pollmgr_handler pxtcp_pmgr_chan_del_hdl;
#endif
static struct pollmgr_handler pxtcp_pmgr_chan_reset_hdl;
/**
* Init PXTCP - must be run when neither lwIP tcpip thread, nor poll
* manager threads haven't been created yet.
*/
void
pxtcp_init(void)
{
/*
* Create channels.
*/
#define CHANNEL(SLOT, NAME) do { \
NAME##_hdl.callback = NAME; \
NAME##_hdl.data = NULL; \
NAME##_hdl.slot = -1; \
pollmgr_add_chan(SLOT, &NAME##_hdl); \
} while (0)
CHANNEL(POLLMGR_CHAN_PXTCP_ADD, pxtcp_pmgr_chan_add);
CHANNEL(POLLMGR_CHAN_PXTCP_POLLIN, pxtcp_pmgr_chan_pollin);
CHANNEL(POLLMGR_CHAN_PXTCP_POLLOUT, pxtcp_pmgr_chan_pollout);
#if !(HAVE_TCP_POLLHUP & POLLOUT)
CHANNEL(POLLMGR_CHAN_PXTCP_DEL, pxtcp_pmgr_chan_del);
#endif
CHANNEL(POLLMGR_CHAN_PXTCP_RESET, pxtcp_pmgr_chan_reset);
#undef CHANNEL
/*
* Listen to outgoing connection from guest(s).
*/
tcp_proxy_accept(pxtcp_pcb_heard);
}
/**
* Syntactic sugar for sending pxtcp pointer over poll manager
* channel. Used by lwip thread functions.
*/
static ssize_t
pxtcp_chan_send(enum pollmgr_slot_t slot, struct pxtcp *pxtcp)
{
return pollmgr_chan_send(slot, &pxtcp, sizeof(pxtcp));
}
/**
* Syntactic sugar for sending weak reference to pxtcp over poll
* manager channel. Used by lwip thread functions.
*/
static ssize_t
pxtcp_chan_send_weak(enum pollmgr_slot_t slot, struct pxtcp *pxtcp)
{
pollmgr_refptr_weak_ref(pxtcp->rp);
return pollmgr_chan_send(slot, &pxtcp->rp, sizeof(pxtcp->rp));
}
/**
* Counterpart of pxtcp_chan_send().
*/
static struct pxtcp *
pxtcp_chan_recv(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
pxtcp = (struct pxtcp *)pollmgr_chan_recv_ptr(handler, fd, revents);
return pxtcp;
}
/**
* Counterpart of pxtcp_chan_send_weak().
*/
static struct pxtcp *
pxtcp_chan_recv_strong(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pollmgr_refptr *rp;
struct pollmgr_handler *base;
struct pxtcp *pxtcp;
rp = (struct pollmgr_refptr *)pollmgr_chan_recv_ptr(handler, fd, revents);
base = (struct pollmgr_handler *)pollmgr_refptr_get(rp);
pxtcp = (struct pxtcp *)base;
return pxtcp;
}
/**
* Register pxtcp with poll manager.
*
* Used for POLLMGR_CHAN_PXTCP_ADD and by port-forwarding. Since
* error handling is different in these two cases, we leave it up to
* the caller.
*/
int
pxtcp_pmgr_add(struct pxtcp *pxtcp)
{
int status;
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->sock >= 0);
LWIP_ASSERT1(pxtcp->pmhdl.callback != NULL);
LWIP_ASSERT1(pxtcp->pmhdl.data == (void *)pxtcp);
LWIP_ASSERT1(pxtcp->pmhdl.slot < 0);
status = pollmgr_add(&pxtcp->pmhdl, pxtcp->sock, pxtcp->events);
return status;
}
/**
* Unregister pxtcp with poll manager.
*
* Used for POLLMGR_CHAN_PXTCP_RESET and by port-forwarding (on error
* leg).
*/
void
pxtcp_pmgr_del(struct pxtcp *pxtcp)
{
LWIP_ASSERT1(pxtcp != NULL);
pollmgr_del_slot(pxtcp->pmhdl.slot);
}
/**
* POLLMGR_CHAN_PXTCP_ADD handler.
*
* Get new pxtcp from lwip thread and start polling its socket.
*/
static int
pxtcp_pmgr_chan_add(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
int status;
pxtcp = pxtcp_chan_recv(handler, fd, revents);
DPRINTF0(("pxtcp_add: new pxtcp %p; pcb %p; sock %d\n",
(void *)pxtcp, (void *)pxtcp->pcb, pxtcp->sock));
status = pxtcp_pmgr_add(pxtcp);
if (status < 0) {
(void) pxtcp_schedule_reset(pxtcp);
}
return POLLIN;
}
/**
* POLLMGR_CHAN_PXTCP_POLLOUT handler.
*
* pxtcp_pcb_forward_outbound() on the lwIP thread tried to send data
* and failed, it now requests us to poll the socket for POLLOUT and
* schedule pxtcp_pcb_forward_outbound() when sock is writable again.
*/
static int
pxtcp_pmgr_chan_pollout(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
pxtcp = pxtcp_chan_recv_strong(handler, fd, revents);
DPRINTF0(("pxtcp_pollout: pxtcp %p\n", (void *)pxtcp));
if (pxtcp == NULL) {
return POLLIN;
}
LWIP_ASSERT1(pxtcp->pmhdl.data == (void *)pxtcp);
LWIP_ASSERT1(pxtcp->pmhdl.slot > 0);
pxtcp->events |= POLLOUT;
pollmgr_update_events(pxtcp->pmhdl.slot, pxtcp->events);
return POLLIN;
}
/**
* POLLMGR_CHAN_PXTCP_POLLIN handler.
*/
static int
pxtcp_pmgr_chan_pollin(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
pxtcp = pxtcp_chan_recv_strong(handler, fd, revents);
DPRINTF2(("pxtcp_pollin: pxtcp %p\n", (void *)pxtcp));
if (pxtcp == NULL) {
return POLLIN;
}
LWIP_ASSERT1(pxtcp->pmhdl.data == (void *)pxtcp);
LWIP_ASSERT1(pxtcp->pmhdl.slot > 0);
if (pxtcp->inbound_close) {
return POLLIN;
}
pxtcp->events |= POLLIN;
pollmgr_update_events(pxtcp->pmhdl.slot, pxtcp->events);
return POLLIN;
}
#if !(HAVE_TCP_POLLHUP & POLLOUT)
/**
* POLLMGR_CHAN_PXTCP_DEL handler.
*
* Schedule pxtcp deletion. We only need this if host system doesn't
* report POLLHUP for fully closed tcp sockets.
*/
static int
pxtcp_pmgr_chan_del(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
pxtcp = pxtcp_chan_recv_strong(handler, fd, revents);
if (pxtcp == NULL) {
return POLLIN;
}
DPRINTF(("PXTCP_DEL: pxtcp %p; pcb %p; sock %d\n",
(void *)pxtcp, (void *)pxtcp->pcb, pxtcp->sock));
LWIP_ASSERT1(pxtcp->pmhdl.callback != NULL);
LWIP_ASSERT1(pxtcp->pmhdl.data == (void *)pxtcp);
LWIP_ASSERT1(pxtcp->inbound_close); /* EOF read */
LWIP_ASSERT1(pxtcp->outbound_close_done); /* EOF sent */
pxtcp_pmgr_del(pxtcp);
(void) pxtcp_schedule_delete(pxtcp);
return POLLIN;
}
#endif /* !(HAVE_TCP_POLLHUP & POLLOUT) */
/**
* POLLMGR_CHAN_PXTCP_RESET handler.
*
* Close the socket with RST and delete pxtcp.
*/
static int
pxtcp_pmgr_chan_reset(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
pxtcp = pxtcp_chan_recv_strong(handler, fd, revents);
if (pxtcp == NULL) {
return POLLIN;
}
DPRINTF0(("PXTCP_RESET: pxtcp %p; pcb %p; sock %d\n",
(void *)pxtcp, (void *)pxtcp->pcb, pxtcp->sock));
LWIP_ASSERT1(pxtcp->pmhdl.callback != NULL);
LWIP_ASSERT1(pxtcp->pmhdl.data == (void *)pxtcp);
pxtcp_pmgr_del(pxtcp);
proxy_reset_socket(pxtcp->sock);
pxtcp->sock = INVALID_SOCKET;
(void) pxtcp_schedule_reset(pxtcp);
return POLLIN;
}
static struct pxtcp *
pxtcp_allocate(void)
{
struct pxtcp *pxtcp;
pxtcp = (struct pxtcp *)malloc(sizeof(*pxtcp));
if (pxtcp == NULL) {
return NULL;
}
pxtcp->pmhdl.callback = NULL;
pxtcp->pmhdl.data = (void *)pxtcp;
pxtcp->pmhdl.slot = -1;
pxtcp->pcb = NULL;
pxtcp->sock = INVALID_SOCKET;
pxtcp->events = 0;
pxtcp->sockerr = 0;
pxtcp->netif = NULL;
pxtcp->unsent = NULL;
pxtcp->outbound_close = 0;
pxtcp->outbound_close_done = 0;
pxtcp->inbound_close = 0;
pxtcp->inbound_close_done = 0;
pxtcp->inbound_pull = 0;
pxtcp->deferred_delete = 0;
pxtcp->inbuf.bufsize = 64 * 1024;
pxtcp->inbuf.buf = (char *)malloc(pxtcp->inbuf.bufsize);
if (pxtcp->inbuf.buf == NULL) {
free(pxtcp);
return NULL;
}
pxtcp->inbuf.vacant = 0;
pxtcp->inbuf.unacked = 0;
pxtcp->inbuf.unsent = 0;
pxtcp->rp = pollmgr_refptr_create(&pxtcp->pmhdl);
if (pxtcp->rp == NULL) {
free(pxtcp->inbuf.buf);
free(pxtcp);
return NULL;
}
#define CALLBACK_MSG(MSG, FUNC) \
do { \
pxtcp->MSG.type = TCPIP_MSG_CALLBACK_STATIC; \
pxtcp->MSG.sem = NULL; \
pxtcp->MSG.msg.cb.function = FUNC; \
pxtcp->MSG.msg.cb.ctx = (void *)pxtcp; \
} while (0)
CALLBACK_MSG(msg_delete, pxtcp_pcb_delete_pxtcp);
CALLBACK_MSG(msg_reset, pxtcp_pcb_reset_pxtcp);
CALLBACK_MSG(msg_accept, pxtcp_pcb_accept_confirm);
CALLBACK_MSG(msg_outbound, pxtcp_pcb_write_outbound);
CALLBACK_MSG(msg_inbound, pxtcp_pcb_write_inbound);
CALLBACK_MSG(msg_inpull, pxtcp_pcb_pull_inbound);
#undef CALLBACK_MSG
return pxtcp;
}
/**
* Exported to fwtcp to create pxtcp for incoming port-forwarded
* connections. Completed with pcb in pxtcp_pcb_connect().
*/
struct pxtcp *
pxtcp_create_forwarded(SOCKET sock)
{
struct pxtcp *pxtcp;
pxtcp = pxtcp_allocate();
if (pxtcp == NULL) {
return NULL;
}
pxtcp->sock = sock;
pxtcp->pmhdl.callback = pxtcp_pmgr_pump;
pxtcp->events = 0;
return pxtcp;
}
static void
pxtcp_pcb_associate(struct pxtcp *pxtcp, struct tcp_pcb *pcb)
{
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pcb != NULL);
pxtcp->pcb = pcb;
tcp_arg(pcb, pxtcp);
tcp_recv(pcb, pxtcp_pcb_recv);
tcp_sent(pcb, pxtcp_pcb_sent);
tcp_poll(pcb, NULL, 255);
tcp_err(pcb, pxtcp_pcb_err);
}
static void
pxtcp_free(struct pxtcp *pxtcp)
{
if (pxtcp->unsent != NULL) {
pbuf_free(pxtcp->unsent);
}
if (pxtcp->inbuf.buf != NULL) {
free(pxtcp->inbuf.buf);
}
free(pxtcp);
}
/**
* Counterpart to pxtcp_create_forwarded() to destruct pxtcp that
* fwtcp failed to register with poll manager to post to lwip thread
* for doing connect.
*/
void
pxtcp_cancel_forwarded(struct pxtcp *pxtcp)
{
LWIP_ASSERT1(pxtcp->pcb == NULL);
pxtcp_pcb_reset_pxtcp(pxtcp);
}
static void
pxtcp_pcb_dissociate(struct pxtcp *pxtcp)
{
if (pxtcp == NULL || pxtcp->pcb == NULL) {
return;
}
DPRINTF(("%s: pxtcp %p <-> pcb %p\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb));
/*
* We must have dissociated from a fully closed pcb immediately
* since lwip recycles them and we don't wan't to mess with what
* would be someone else's pcb that we happen to have a stale
* pointer to.
*/
LWIP_ASSERT1(pxtcp->pcb->callback_arg == pxtcp);
tcp_recv(pxtcp->pcb, NULL);
tcp_sent(pxtcp->pcb, NULL);
tcp_poll(pxtcp->pcb, NULL, 255);
tcp_err(pxtcp->pcb, NULL);
tcp_arg(pxtcp->pcb, NULL);
pxtcp->pcb = NULL;
}
/**
* Lwip thread callback invoked via pxtcp::msg_delete
*
* Since we use static messages to communicate to the lwip thread, we
* cannot delete pxtcp without making sure there are no unprocessed
* messages in the lwip thread mailbox.
*
* The easiest way to ensure that is to send this "delete" message as
* the last one and when it's processed we know there are no more and
* it's safe to delete pxtcp.
*
* Poll manager handlers should use pxtcp_schedule_delete()
* convenience function.
*/
static void
pxtcp_pcb_delete_pxtcp(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
DPRINTF(("%s: pxtcp %p, pcb %p, sock %d%s\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb, pxtcp->sock,
(pxtcp->deferred_delete && !pxtcp->inbound_pull
? " (was deferred)" : "")));
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->pmhdl.slot < 0);
LWIP_ASSERT1(pxtcp->outbound_close_done);
LWIP_ASSERT1(pxtcp->inbound_close); /* not necessarily done */
/*
* pxtcp is no longer registered with poll manager, so it's safe
* to close the socket.
*/
if (pxtcp->sock != INVALID_SOCKET) {
closesocket(pxtcp->sock);
pxtcp->sock = INVALID_SOCKET;
}
/*
* We might have already dissociated from a fully closed pcb, or
* guest might have sent us a reset while msg_delete was in
* transit. If there's no pcb, we are done.
*/
if (pxtcp->pcb == NULL) {
pollmgr_refptr_unref(pxtcp->rp);
pxtcp_free(pxtcp);
return;
}
/*
* Have we completely forwarded all inbound traffic to the guest?
*
* We may still be waiting for ACKs. We may have failed to send
* some of the data (tcp_write() failed with ERR_MEM). We may
* have failed to send the FIN (tcp_shutdown() failed with
* ERR_MEM).
*/
if (pxtcp_pcb_forward_inbound_done(pxtcp)) {
pxtcp_pcb_dissociate(pxtcp);
pollmgr_refptr_unref(pxtcp->rp);
pxtcp_free(pxtcp);
}
else {
DPRINTF2(("delete: pxtcp %p; pcb %p:"
" unacked %d, unsent %d, vacant %d, %s - DEFER!\n",
(void *)pxtcp, (void *)pxtcp->pcb,
(int)pxtcp->inbuf.unacked,
(int)pxtcp->inbuf.unsent,
(int)pxtcp->inbuf.vacant,
pxtcp->inbound_close_done ? "FIN sent" : "FIN is NOT sent"));
LWIP_ASSERT1(!pxtcp->deferred_delete);
pxtcp->deferred_delete = 1;
}
}
/**
* If we couldn't delete pxtcp right away in the msg_delete callback
* from the poll manager thread, we repeat the check at the end of
* relevant pcb callbacks.
*/
DECLINLINE(void)
pxtcp_pcb_maybe_deferred_delete(struct pxtcp *pxtcp)
{
if (pxtcp->deferred_delete && pxtcp_pcb_forward_inbound_done(pxtcp)) {
pxtcp_pcb_delete_pxtcp(pxtcp);
}
}
/**
* Poll manager callbacks should use this convenience wrapper to
* schedule pxtcp deletion on the lwip thread and to deregister from
* the poll manager.
*/
static int
pxtcp_schedule_delete(struct pxtcp *pxtcp)
{
/*
* If pollmgr_refptr_get() is called by any channel before
* scheduled deletion happens, let them know we are gone.
*/
pxtcp->pmhdl.slot = -1;
/*
* Schedule deletion. Since poll manager thread may be pre-empted
* right after we send the message, the deletion may actually
* happen on the lwip thread before we return from this function,
* so it's not safe to refer to pxtcp after this call.
*/
proxy_lwip_post(&pxtcp->msg_delete);
/* tell poll manager to deregister us */
return -1;
}
/**
* Lwip thread callback invoked via pxtcp::msg_reset
*
* Like pxtcp_pcb_delete(), but sends RST to the guest before
* deleting this pxtcp.
*/
static void
pxtcp_pcb_reset_pxtcp(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
LWIP_ASSERT1(pxtcp != NULL);
DPRINTF0(("%s: pxtcp %p, pcb %p, sock %d\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb, pxtcp->sock));
if (pxtcp->sock != INVALID_SOCKET) {
proxy_reset_socket(pxtcp->sock);
pxtcp->sock = INVALID_SOCKET;
}
if (pxtcp->pcb != NULL) {
struct tcp_pcb *pcb = pxtcp->pcb;
pxtcp_pcb_dissociate(pxtcp);
tcp_abort(pcb);
}
pollmgr_refptr_unref(pxtcp->rp);
pxtcp_free(pxtcp);
}
/**
* Poll manager callbacks should use this convenience wrapper to
* schedule pxtcp reset and deletion on the lwip thread and to
* deregister from the poll manager.
*
* See pxtcp_schedule_delete() for additional comments.
*/
static int
pxtcp_schedule_reset(struct pxtcp *pxtcp)
{
pxtcp->pmhdl.slot = -1;
proxy_lwip_post(&pxtcp->msg_reset);
return -1;
}
/**
* Reject proxy connection attempt. Depending on the cause (sockerr)
* we may just drop the pcb silently, generate an ICMP datagram or
* send TCP reset.
*/
static void
pxtcp_pcb_reject(struct netif *netif, struct tcp_pcb *pcb,
struct pbuf *p, int sockerr)
{
struct netif *oif;
int reset = 0;
oif = ip_current_netif();
ip_current_netif() = netif;
if (sockerr == ECONNREFUSED) {
reset = 1;
}
else if (PCB_ISIPV6(pcb)) {
if (sockerr == EHOSTDOWN) {
icmp6_dest_unreach(p, ICMP6_DUR_ADDRESS); /* XXX: ??? */
}
else if (sockerr == EHOSTUNREACH
|| sockerr == ENETDOWN
|| sockerr == ENETUNREACH)
{
icmp6_dest_unreach(p, ICMP6_DUR_NO_ROUTE);
}
}
else {
if (sockerr == EHOSTDOWN
|| sockerr == EHOSTUNREACH
|| sockerr == ENETDOWN
|| sockerr == ENETUNREACH)
{
icmp_dest_unreach(p, ICMP_DUR_HOST);
}
}
ip_current_netif() = oif;
tcp_abandon(pcb, reset);
}
/**
* Called from poll manager thread via pxtcp::msg_accept when proxy
* failed to connect to the destination. Also called when we failed
* to register pxtcp with poll manager.
*
* This is like pxtcp_pcb_reset_pxtcp() but is more discriminate in
* how this unestablished connection is terminated.
*/
static void
pxtcp_pcb_accept_refuse(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
DPRINTF0(("%s: pxtcp %p, pcb %p, sock %d: %R[sockerr]\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb,
pxtcp->sock, pxtcp->sockerr));
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->sock == INVALID_SOCKET);
if (pxtcp->pcb != NULL) {
struct tcp_pcb *pcb = pxtcp->pcb;
pxtcp_pcb_dissociate(pxtcp);
pxtcp_pcb_reject(pxtcp->netif, pcb, pxtcp->unsent, pxtcp->sockerr);
}
pollmgr_refptr_unref(pxtcp->rp);
pxtcp_free(pxtcp);
}
/**
* Convenience wrapper for poll manager connect callback to reject
* connection attempt.
*
* Like pxtcp_schedule_reset(), but the callback is more discriminate
* in how this unestablished connection is terminated.
*/
static int
pxtcp_schedule_reject(struct pxtcp *pxtcp)
{
pxtcp->msg_accept.msg.cb.function = pxtcp_pcb_accept_refuse;
pxtcp->pmhdl.slot = -1;
proxy_lwip_post(&pxtcp->msg_accept);
return -1;
}
/**
* Global tcp_proxy_accept() callback for proxied outgoing TCP
* connections from guest(s).
*/
static err_t
pxtcp_pcb_heard(void *arg, struct tcp_pcb *newpcb, err_t error)
{
struct pbuf *p = (struct pbuf *)arg;
struct pxtcp *pxtcp;
ipX_addr_t dst_addr;
int sdom;
SOCKET sock;
ssize_t nsent;
int sockerr = 0;
LWIP_UNUSED_ARG(error); /* always ERR_OK */
/*
* TCP first calls accept callback when it receives the first SYN
* and "tentatively accepts" new proxied connection attempt. When
* proxy "confirms" the SYN and sends SYN|ACK and the guest
* replies with ACK the accept callback is called again, this time
* with the established connection.
*/
LWIP_ASSERT1(newpcb->state == SYN_RCVD_0);
tcp_accept(newpcb, pxtcp_pcb_accept);
tcp_arg(newpcb, NULL);
tcp_setprio(newpcb, TCP_PRIO_MAX);
pxremap_outbound_ipX(PCB_ISIPV6(newpcb), &dst_addr, &newpcb->local_ip);
sdom = PCB_ISIPV6(newpcb) ? PF_INET6 : PF_INET;
sock = proxy_connected_socket(sdom, SOCK_STREAM,
&dst_addr, newpcb->local_port);
if (sock == INVALID_SOCKET) {
sockerr = SOCKERRNO();
goto abort;
}
pxtcp = pxtcp_allocate();
if (pxtcp == NULL) {
proxy_reset_socket(sock);
goto abort;
}
/* save initial datagram in case we need to reply with ICMP */
pbuf_ref(p);
pxtcp->unsent = p;
pxtcp->netif = ip_current_netif();
pxtcp_pcb_associate(pxtcp, newpcb);
pxtcp->sock = sock;
pxtcp->pmhdl.callback = pxtcp_pmgr_connect;
pxtcp->events = POLLOUT;
nsent = pxtcp_chan_send(POLLMGR_CHAN_PXTCP_ADD, pxtcp);
if (nsent < 0) {
pxtcp->sock = INVALID_SOCKET;
proxy_reset_socket(sock);
pxtcp_pcb_accept_refuse(pxtcp);
return ERR_ABRT;
}
return ERR_OK;
abort:
DPRINTF0(("%s: pcb %p, sock %d: %R[sockerr]\n",
__func__, (void *)newpcb, sock, sockerr));
pxtcp_pcb_reject(ip_current_netif(), newpcb, p, sockerr);
return ERR_ABRT;
}
/**
* tcp_proxy_accept() callback for accepted proxied outgoing TCP
* connections from guest(s). This is "real" accept with three-way
* handshake completed.
*/
static err_t
pxtcp_pcb_accept(void *arg, struct tcp_pcb *pcb, err_t error)
{
struct pxtcp *pxtcp = (struct pxtcp *)arg;
LWIP_UNUSED_ARG(pcb); /* used only in asserts */
LWIP_UNUSED_ARG(error); /* always ERR_OK */
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->pcb = pcb);
LWIP_ASSERT1(pcb->callback_arg == pxtcp);
/* send any inbound data that are already queued */
pxtcp_pcb_forward_inbound(pxtcp);
return ERR_OK;
}
/**
* Initial poll manager callback for proxied outgoing TCP connections.
* pxtcp_pcb_accept() sets pxtcp::pmhdl::callback to this.
*
* Waits for connect(2) to the destination to complete. On success
* replaces itself with pxtcp_pmgr_pump() callback common to all
* established TCP connections.
*/
static int
pxtcp_pmgr_connect(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
pxtcp = (struct pxtcp *)handler->data;
LWIP_ASSERT1(handler == &pxtcp->pmhdl);
LWIP_ASSERT1(fd == pxtcp->sock);
if (revents & (POLLNVAL | POLLHUP | POLLERR)) {
if (revents & POLLNVAL) {
pxtcp->sock = INVALID_SOCKET;
pxtcp->sockerr = ETIMEDOUT;
}
else {
socklen_t optlen = (socklen_t)sizeof(pxtcp->sockerr);
int status;
SOCKET s;
status = getsockopt(pxtcp->sock, SOL_SOCKET, SO_ERROR,
(char *)&pxtcp->sockerr, &optlen);
if (status < 0) { /* should not happen */
DPRINTF(("%s: sock %d: SO_ERROR failed: %R[sockerr]\n",
__func__, fd, SOCKERRNO()));
}
else {
DPRINTF(("%s: sock %d: connect: %R[sockerr]\n",
__func__, fd, pxtcp->sockerr));
}
s = pxtcp->sock;
pxtcp->sock = INVALID_SOCKET;
closesocket(s);
}
return pxtcp_schedule_reject(pxtcp);
}
if (revents & POLLOUT) { /* connect is successful */
/* confirm accept to the guest */
proxy_lwip_post(&pxtcp->msg_accept);
/*
* Switch to common callback used for all established proxied
* connections.
*/
pxtcp->pmhdl.callback = pxtcp_pmgr_pump;
/*
* Initially we poll for incoming traffic only. Outgoing
* traffic is fast-forwarded by pxtcp_pcb_recv(); if it fails
* it will ask us to poll for POLLOUT too.
*/
pxtcp->events = POLLIN;
return pxtcp->events;
}
/* should never get here */
DPRINTF0(("%s: pxtcp %p, sock %d: unexpected revents 0x%x\n",
__func__, (void *)pxtcp, fd, revents));
return pxtcp_schedule_reset(pxtcp);
}
/**
* Called from poll manager thread via pxtcp::msg_accept when proxy
* connected to the destination. Finalize accept by sending SYN|ACK
* to the guest.
*/
static void
pxtcp_pcb_accept_confirm(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
err_t error;
LWIP_ASSERT1(pxtcp != NULL);
if (pxtcp->pcb == NULL) {
return;
}
/* we are not going to reply with ICMP, so we can drop initial pbuf */
LWIP_ASSERT1(pxtcp->unsent != NULL);
pbuf_free(pxtcp->unsent);
pxtcp->unsent = NULL;
error = tcp_proxy_accept_confirm(pxtcp->pcb);
/*
* If lwIP failed to enqueue SYN|ACK because it's out of pbufs it
* abandons the pcb. Retrying that is not very easy, since it
* would require keeping "fractional state". From guest's point
* of view there is no reply to its SYN so it will either resend
* the SYN (effetively triggering full connection retry for us),
* or it will eventually time out.
*/
if (error == ERR_ABRT) {
pxtcp->pcb = NULL; /* pcb is gone */
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_RESET, pxtcp);
}
/*
* else if (error != ERR_OK): even if tcp_output() failed with
* ERR_MEM - don't give up, that SYN|ACK is enqueued and will be
* retransmitted eventually.
*/
}
/**
* Entry point for port-forwarding.
*
* fwtcp accepts new incoming connection, creates pxtcp for the socket
* (with no pcb yet) and adds it to the poll manager (polling for
* errors only). Then it calls this function to construct the pcb and
* perform connection to the guest.
*/
void
pxtcp_pcb_connect(struct pxtcp *pxtcp, const struct fwspec *fwspec)
{
struct sockaddr_storage ss;
socklen_t sslen;
struct tcp_pcb *pcb;
ipX_addr_t src_addr, dst_addr;
u16_t src_port, dst_port;
int status;
err_t error;
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->pcb == NULL);
LWIP_ASSERT1(fwspec->stype == SOCK_STREAM);
pcb = tcp_new();
if (pcb == NULL) {
goto reset;
}
tcp_setprio(pcb, TCP_PRIO_MAX);
pxtcp_pcb_associate(pxtcp, pcb);
sslen = sizeof(ss);
status = getpeername(pxtcp->sock, (struct sockaddr *)&ss, &sslen);
if (status == SOCKET_ERROR) {
goto reset;
}
/* nit: comapres PF and AF, but they are the same everywhere */
LWIP_ASSERT1(ss.ss_family == fwspec->sdom);
status = fwany_ipX_addr_set_src(&src_addr, (const struct sockaddr *)&ss);
if (status == PXREMAP_FAILED) {
goto reset;
}
if (ss.ss_family == PF_INET) {
const struct sockaddr_in *peer4 = (const struct sockaddr_in *)&ss;
src_port = peer4->sin_port;
memcpy(&dst_addr.ip4, &fwspec->dst.sin.sin_addr, sizeof(ip_addr_t));
dst_port = fwspec->dst.sin.sin_port;
}
else { /* PF_INET6 */
const struct sockaddr_in6 *peer6 = (const struct sockaddr_in6 *)&ss;
ip_set_v6(pcb, 1);
src_port = peer6->sin6_port;
memcpy(&dst_addr.ip6, &fwspec->dst.sin6.sin6_addr, sizeof(ip6_addr_t));
dst_port = fwspec->dst.sin6.sin6_port;
}
/* lwip port arguments are in host order */
src_port = ntohs(src_port);
dst_port = ntohs(dst_port);
error = tcp_proxy_bind(pcb, ipX_2_ip(&src_addr), src_port);
if (error != ERR_OK) {
goto reset;
}
error = tcp_connect(pcb, ipX_2_ip(&dst_addr), dst_port,
/* callback: */ pxtcp_pcb_connected);
if (error != ERR_OK) {
goto reset;
}
return;
reset:
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_RESET, pxtcp);
}
/**
* Port-forwarded connection to guest is successful, pump data.
*/
static err_t
pxtcp_pcb_connected(void *arg, struct tcp_pcb *pcb, err_t error)
{
struct pxtcp *pxtcp = (struct pxtcp *)arg;
LWIP_ASSERT1(error == ERR_OK); /* always called with ERR_OK */
LWIP_UNUSED_ARG(error);
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->pcb == pcb);
LWIP_ASSERT1(pcb->callback_arg == pxtcp);
LWIP_UNUSED_ARG(pcb);
DPRINTF0(("%s: new pxtcp %p; pcb %p; sock %d\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb, pxtcp->sock));
/* ACK on connection is like ACK on data in pxtcp_pcb_sent() */
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_POLLIN, pxtcp);
return ERR_OK;
}
/**
* tcp_recv() callback.
*/
static err_t
pxtcp_pcb_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t error)
{
struct pxtcp *pxtcp = (struct pxtcp *)arg;
LWIP_ASSERT1(error == ERR_OK); /* always called with ERR_OK */
LWIP_UNUSED_ARG(error);
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->pcb == pcb);
LWIP_ASSERT1(pcb->callback_arg == pxtcp);
LWIP_UNUSED_ARG(pcb);
/*
* Have we done sending previous batch?
*/
if (pxtcp->unsent != NULL) {
if (p != NULL) {
/*
* Return an error to tell TCP to hold onto that pbuf.
* It will be presented to us later from tcp_fasttmr().
*/
return ERR_WOULDBLOCK;
}
else {
/*
* Unlike data, p == NULL indicating orderly shutdown is
* NOT presented to us again
*/
pxtcp->outbound_close = 1;
return ERR_OK;
}
}
/*
* Guest closed?
*/
if (p == NULL) {
pxtcp->outbound_close = 1;
pxtcp_pcb_forward_outbound_close(pxtcp);
return ERR_OK;
}
/*
* Got data, send what we can without blocking.
*/
return pxtcp_pcb_forward_outbound(pxtcp, p);
}
/**
* Guest half-closed its TX side of the connection.
*
* Called either immediately from pxtcp_pcb_recv() when it gets NULL,
* or from pxtcp_pcb_forward_outbound() when it finishes forwarding
* previously unsent data and sees pxtcp::outbound_close flag saved by
* pxtcp_pcb_recv().
*/
static void
pxtcp_pcb_forward_outbound_close(struct pxtcp *pxtcp)
{
struct tcp_pcb *pcb;
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->outbound_close);
LWIP_ASSERT1(!pxtcp->outbound_close_done);
pcb = pxtcp->pcb;
LWIP_ASSERT1(pcb != NULL);
DPRINTF(("outbound_close: pxtcp %p; pcb %p %s\n",
(void *)pxtcp, (void *)pcb, tcp_debug_state_str(pcb->state)));
/* set the flag first, since shutdown() may trigger POLLHUP */
pxtcp->outbound_close_done = 1;
shutdown(pxtcp->sock, SHUT_WR); /* half-close the socket */
#if !(HAVE_TCP_POLLHUP & POLLOUT)
/*
* We need to nudge poll manager manually, since OS will not
* report POLLHUP.
*/
if (pxtcp->inbound_close) {
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_DEL, pxtcp);
}
#endif
/* no more outbound data coming to us */
tcp_recv(pcb, NULL);
/*
* If we have already done inbound close previously (active close
* on the pcb), then we must not hold onto a pcb in TIME_WAIT
* state since those will be recycled by lwip when it runs out of
* free pcbs in the pool.
*
* The test is true also for a pcb in CLOSING state that waits
* just for the ACK of its FIN (to transition to TIME_WAIT).
*/
if (pxtcp_pcb_forward_inbound_done(pxtcp)) {
pxtcp_pcb_dissociate(pxtcp);
}
}
/**
* Forward outbound data from pcb to socket.
*
* Called by pxtcp_pcb_recv() to forward new data and by callout
* triggered by POLLOUT on the socket to send previously unsent data.
*
* (Re)scehdules one-time callout if not all data are sent.
*/
static err_t
pxtcp_pcb_forward_outbound(struct pxtcp *pxtcp, struct pbuf *p)
{
struct pbuf *qs, *q;
size_t qoff;
size_t forwarded;
int sockerr;
LWIP_ASSERT1(pxtcp->unsent == NULL || pxtcp->unsent == p);
forwarded = 0;
sockerr = 0;
q = NULL;
qoff = 0;
qs = p;
while (qs != NULL) {
IOVEC iov[8];
const size_t iovsize = sizeof(iov)/sizeof(iov[0]);
size_t fwd1;
ssize_t nsent;
size_t i;
fwd1 = 0;
for (i = 0, q = qs; i < iovsize && q != NULL; ++i, q = q->next) {
LWIP_ASSERT1(q->len > 0);
IOVEC_SET_BASE(iov[i], q->payload);
IOVEC_SET_LEN(iov[i], q->len);
fwd1 += q->len;
}
/*
* TODO: This is where application-level proxy can hook into
* to process outbound traffic.
*/
nsent = pxtcp_sock_send(pxtcp, iov, i);
if (nsent == (ssize_t)fwd1) {
/* successfully sent this chain fragment completely */
forwarded += nsent;
qs = q;
}
else if (nsent >= 0) {
/* successfully sent only some data */
forwarded += nsent;
/* find the first pbuf that was not completely forwarded */
qoff = nsent;
for (i = 0, q = qs; i < iovsize && q != NULL; ++i, q = q->next) {
if (qoff < q->len) {
break;
}
qoff -= q->len;
}
LWIP_ASSERT1(q != NULL);
LWIP_ASSERT1(qoff < q->len);
break;
}
else {
sockerr = -nsent;
/*
* Some errors are really not errors - if we get them,
* it's not different from getting nsent == 0, so filter
* them out here.
*/
if (proxy_error_is_transient(sockerr)) {
sockerr = 0;
}
q = qs;
qoff = 0;
break;
}
}
if (forwarded > 0) {
tcp_recved(pxtcp->pcb, (u16_t)forwarded);
}
if (q == NULL) { /* everything is forwarded? */
LWIP_ASSERT1(sockerr == 0);
LWIP_ASSERT1(forwarded == p->tot_len);
pxtcp->unsent = NULL;
pbuf_free(p);
if (pxtcp->outbound_close) {
pxtcp_pcb_forward_outbound_close(pxtcp);
}
}
else {
if (q != p) {
/* free forwarded pbufs at the beginning of the chain */
pbuf_ref(q);
pbuf_free(p);
}
if (qoff > 0) {
/* advance payload pointer past the forwarded part */
pbuf_header(q, -(s16_t)qoff);
}
pxtcp->unsent = q;
/*
* Have sendmsg() failed?
*
* Connection reset will be detected by poll and
* pxtcp_schedule_reset() will be called.
*
* Otherwise something *really* unexpected must have happened,
* so we'd better abort.
*/
if (sockerr != 0 && sockerr != ECONNRESET) {
struct tcp_pcb *pcb = pxtcp->pcb;
pxtcp_pcb_dissociate(pxtcp);
tcp_abort(pcb);
/* call error callback manually since we've already dissociated */
pxtcp_pcb_err((void *)pxtcp, ERR_ABRT);
return ERR_ABRT;
}
/* schedule one-shot POLLOUT on the socket */
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_POLLOUT, pxtcp);
}
return ERR_OK;
}
#if !defined(RT_OS_WINDOWS)
static ssize_t
pxtcp_sock_send(struct pxtcp *pxtcp, IOVEC *iov, size_t iovlen)
{
struct msghdr mh;
ssize_t nsent;
#ifdef MSG_NOSIGNAL
const int send_flags = MSG_NOSIGNAL;
#else
const int send_flags = 0;
#endif
memset(&mh, 0, sizeof(mh));
mh.msg_iov = iov;
mh.msg_iovlen = iovlen;
nsent = sendmsg(pxtcp->sock, &mh, send_flags);
if (nsent < 0) {
nsent = -SOCKERRNO();
}
return nsent;
}
#else /* RT_OS_WINDOWS */
static ssize_t
pxtcp_sock_send(struct pxtcp *pxtcp, IOVEC *iov, size_t iovlen)
{
DWORD nsent;
int status;
status = WSASend(pxtcp->sock, iov, (DWORD)iovlen, &nsent,
0, NULL, NULL);
if (status == SOCKET_ERROR) {
return -SOCKERRNO();
}
return nsent;
}
#endif /* RT_OS_WINDOWS */
/**
* Callback from poll manager (on POLLOUT) to send data from
* pxtcp::unsent pbuf to socket.
*/
static void
pxtcp_pcb_write_outbound(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
LWIP_ASSERT1(pxtcp != NULL);
if (pxtcp->pcb == NULL) {
return;
}
pxtcp_pcb_forward_outbound(pxtcp, pxtcp->unsent);
}
/**
* Common poll manager callback used by both outgoing and incoming
* (port-forwarded) connections that has connected socket.
*/
static int
pxtcp_pmgr_pump(struct pollmgr_handler *handler, SOCKET fd, int revents)
{
struct pxtcp *pxtcp;
int status;
int sockerr;
pxtcp = (struct pxtcp *)handler->data;
LWIP_ASSERT1(handler == &pxtcp->pmhdl);
LWIP_ASSERT1(fd == pxtcp->sock);
if (revents & POLLNVAL) {
pxtcp->sock = INVALID_SOCKET;
return pxtcp_schedule_reset(pxtcp);
}
if (revents & POLLERR) {
socklen_t optlen = (socklen_t)sizeof(sockerr);
status = getsockopt(pxtcp->sock, SOL_SOCKET, SO_ERROR,
(char *)&sockerr, &optlen);
if (status < 0) { /* should not happen */
DPRINTF(("sock %d: SO_ERROR failed: %R[sockerr]\n",
fd, SOCKERRNO()));
}
else {
DPRINTF0(("sock %d: %R[sockerr]\n", fd, sockerr));
}
return pxtcp_schedule_reset(pxtcp);
}
if (revents & POLLOUT) {
pxtcp->events &= ~POLLOUT;
proxy_lwip_post(&pxtcp->msg_outbound);
}
if (revents & POLLIN) {
ssize_t nread;
int stop_pollin;
nread = pxtcp_sock_read(pxtcp, &stop_pollin);
if (nread < 0) {
sockerr = -(int)nread;
DPRINTF0(("sock %d: %R[sockerr]\n", fd, sockerr));
return pxtcp_schedule_reset(pxtcp);
}
if (stop_pollin) {
pxtcp->events &= ~POLLIN;
}
if (nread > 0) {
proxy_lwip_post(&pxtcp->msg_inbound);
#if !HAVE_TCP_POLLHUP
/*
* If host does not report POLLHUP for closed sockets
* (e.g. NetBSD) we should check for full close manually.
*/
if (pxtcp->inbound_close && pxtcp->outbound_close_done) {
LWIP_ASSERT1((revents & POLLHUP) == 0);
return pxtcp_schedule_delete(pxtcp);
}
#endif
}
}
#if !HAVE_TCP_POLLHUP
LWIP_ASSERT1((revents & POLLHUP) == 0);
#else
if (revents & POLLHUP) {
#if HAVE_TCP_POLLHUP == POLLIN
/*
* Remote closed inbound.
*/
if (!pxtcp->outbound_close_done) {
/*
* We might still need to poll for POLLOUT, but we can not
* poll for POLLIN anymore (even if not all data are read)
* because we will be spammed by POLLHUP.
*/
pxtcp->events &= ~POLLIN;
if (!pxtcp->inbound_close) {
/* the rest of the input has to be pulled */
proxy_lwip_post(&pxtcp->msg_inpull);
}
}
else
#endif
/*
* Both directions are closed.
*/
{
DPRINTF(("sock %d: HUP\n", fd));
LWIP_ASSERT1(pxtcp->outbound_close_done);
if (pxtcp->inbound_close) {
/* there's no unread data, we are done */
return pxtcp_schedule_delete(pxtcp);
}
else {
/* pull the rest of the input first (deferred_delete) */
pxtcp->pmhdl.slot = -1;
proxy_lwip_post(&pxtcp->msg_inpull);
return -1;
}
/* NOTREACHED */
}
}
#endif /* HAVE_TCP_POLLHUP */
return pxtcp->events;
}
/**
* Read data from socket to ringbuf. This may be used both on lwip
* and poll manager threads.
*
* Flag pointed to by pstop is set when further reading is impossible,
* either temporary when buffer is full, or permanently when EOF is
* received.
*
* Returns number of bytes read. NB: EOF is reported as 1!
*
* Returns zero if nothing was read, either because buffer is full, or
* if no data is available (EWOULDBLOCK, EINTR &c).
*
* Returns -errno on real socket errors.
*/
static ssize_t
pxtcp_sock_read(struct pxtcp *pxtcp, int *pstop)
{
IOVEC iov[2];
size_t iovlen;
ssize_t nread;
const size_t sz = pxtcp->inbuf.bufsize;
size_t beg, lim, wrnew;
*pstop = 0;
beg = pxtcp->inbuf.vacant;
IOVEC_SET_BASE(iov[0], &pxtcp->inbuf.buf[beg]);
/* lim is the index we can NOT write to */
lim = pxtcp->inbuf.unacked;
if (lim == 0) {
lim = sz - 1; /* empty slot at the end */
}
else if (lim == 1) {
lim = sz; /* empty slot at the beginning */
}
else {
--lim;
}
if (beg == lim) {
/*
* Buffer is full, stop polling for POLLIN.
*
* pxtcp_pcb_sent() will re-enable POLLIN when guest ACKs
* data, freeing space in the ring buffer.
*/
*pstop = 1;
return 0;
}
if (beg < lim) {
/* free space in one chunk */
iovlen = 1;
IOVEC_SET_LEN(iov[0], lim - beg);
}
else {
/* free space in two chunks */
iovlen = 2;
IOVEC_SET_LEN(iov[0], sz - beg);
IOVEC_SET_BASE(iov[1], &pxtcp->inbuf.buf[0]);
IOVEC_SET_LEN(iov[1], lim);
}
/*
* TODO: This is where application-level proxy can hook into to
* process inbound traffic.
*/
nread = pxtcp_sock_recv(pxtcp, iov, iovlen);
if (nread > 0) {
wrnew = beg + nread;
if (wrnew >= sz) {
wrnew -= sz;
}
pxtcp->inbuf.vacant = wrnew;
DPRINTF2(("pxtcp %p: sock %d read %d bytes\n",
(void *)pxtcp, pxtcp->sock, (int)nread));
return nread;
}
else if (nread == 0) {
*pstop = 1;
pxtcp->inbound_close = 1;
DPRINTF2(("pxtcp %p: sock %d read EOF\n",
(void *)pxtcp, pxtcp->sock));
return 1;
}
else {
int sockerr = -nread;
if (proxy_error_is_transient(sockerr)) {
/* haven't read anything, just return */
DPRINTF2(("pxtcp %p: sock %d read cancelled\n",
(void *)pxtcp, pxtcp->sock));
return 0;
}
else {
/* socket error! */
DPRINTF0(("pxtcp %p: sock %d read: %R[sockerr]\n",
(void *)pxtcp, pxtcp->sock, sockerr));
return -sockerr;
}
}
}
#if !defined(RT_OS_WINDOWS)
static ssize_t
pxtcp_sock_recv(struct pxtcp *pxtcp, IOVEC *iov, size_t iovlen)
{
struct msghdr mh;
ssize_t nread;
memset(&mh, 0, sizeof(mh));
mh.msg_iov = iov;
mh.msg_iovlen = iovlen;
nread = recvmsg(pxtcp->sock, &mh, 0);
if (nread < 0) {
nread = -SOCKERRNO();
}
return nread;
}
#else /* RT_OS_WINDOWS */
static ssize_t
pxtcp_sock_recv(struct pxtcp *pxtcp, IOVEC *iov, size_t iovlen)
{
DWORD flags;
DWORD nread;
int status;
flags = 0;
status = WSARecv(pxtcp->sock, iov, (DWORD)iovlen, &nread,
&flags, NULL, NULL);
if (status == SOCKET_ERROR) {
return -SOCKERRNO();
}
return (ssize_t)nread;
}
#endif /* RT_OS_WINDOWS */
/**
* Callback from poll manager (pxtcp::msg_inbound) to trigger output
* from ringbuf to guest.
*/
static void
pxtcp_pcb_write_inbound(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
LWIP_ASSERT1(pxtcp != NULL);
if (pxtcp->pcb == NULL) {
return;
}
pxtcp_pcb_forward_inbound(pxtcp);
}
/**
* tcp_poll() callback
*
* We swtich it on when tcp_write() or tcp_shutdown() fail with
* ERR_MEM to prevent connection from stalling. If there are ACKs or
* more inbound data then pxtcp_pcb_forward_inbound() will be
* triggered again, but if neither happens, tcp_poll() comes to the
* rescue.
*/
static err_t
pxtcp_pcb_poll(void *arg, struct tcp_pcb *pcb)
{
struct pxtcp *pxtcp = (struct pxtcp *)arg;
LWIP_UNUSED_ARG(pcb);
DPRINTF2(("%s: pxtcp %p; pcb %p\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb));
pxtcp_pcb_forward_inbound(pxtcp);
/*
* If the last thing holding up deletion of the pxtcp was failed
* tcp_shutdown() and it succeeded, we may be the last callback.
*/
pxtcp_pcb_maybe_deferred_delete(pxtcp);
return ERR_OK;
}
static void
pxtcp_pcb_schedule_poll(struct pxtcp *pxtcp)
{
tcp_poll(pxtcp->pcb, pxtcp_pcb_poll, 0);
}
static void
pxtcp_pcb_cancel_poll(struct pxtcp *pxtcp)
{
tcp_poll(pxtcp->pcb, NULL, 255);
}
/**
* Forward inbound data from ring buffer to the guest.
*
* Scheduled by poll manager thread after it receives more data into
* the ring buffer (we have more data to send).
* Also called from tcp_sent() callback when guest ACKs some data,
* increasing pcb->snd_buf (we are permitted to send more data).
*
* Also called from tcp_poll() callback if previous attempt to forward
* inbound data failed with ERR_MEM (we need to try again).
*/
static void
pxtcp_pcb_forward_inbound(struct pxtcp *pxtcp)
{
struct tcp_pcb *pcb;
size_t sndbuf;
size_t beg, lim, sndlim;
size_t toeob, tolim;
size_t nsent;
err_t error;
LWIP_ASSERT1(pxtcp != NULL);
pcb = pxtcp->pcb;
if (pcb == NULL) {
return;
}
if (/* __predict_false */ pcb->state < ESTABLISHED) {
/*
* If we have just confirmed accept of this connection, the
* pcb is in SYN_RCVD state and we still haven't received the
* ACK of our SYN. It's only in SYN_RCVD -> ESTABLISHED
* transition that lwip decrements pcb->acked so that that ACK
* is not reported to pxtcp_pcb_sent(). If we send something
* now and immediately close (think "daytime", e.g.) while
* still in SYN_RCVD state, we will move directly to
* FIN_WAIT_1 and when our confirming SYN is ACK'ed lwip will
* report it to pxtcp_pcb_sent().
*/
DPRINTF2(("forward_inbound: pxtcp %p; pcb %p %s - later...\n",
(void *)pxtcp, (void *)pcb, tcp_debug_state_str(pcb->state)));
return;
}
beg = pxtcp->inbuf.unsent; /* private to lwip thread */
lim = pxtcp->inbuf.vacant;
if (beg == lim) {
if (pxtcp->inbound_close && !pxtcp->inbound_close_done) {
pxtcp_pcb_forward_inbound_close(pxtcp);
tcp_output(pcb);
return;
}
/*
* Else, there's no data to send.
*
* If there is free space in the buffer, producer will
* reschedule us as it receives more data and vacant (lim)
* advances.
*
* If buffer is full when all data have been passed to
* tcp_write() but not yet acknowledged, we will advance
* unacked on ACK, freeing some space for producer to write to
* (then see above).
*/
return;
}
sndbuf = tcp_sndbuf(pcb);
if (sndbuf == 0) {
/*
* Can't send anything now. As guest ACKs some data, TCP will
* call pxtcp_pcb_sent() callback and we will come here again.
*/
return;
}
nsent = 0;
/*
* We have three limits to consider:
* - how much data we have in the ringbuf
* - how much data we are allowed to send
* - ringbuf size
*/
toeob = pxtcp->inbuf.bufsize - beg;
if (lim < beg) { /* lim wrapped */
if (sndbuf < toeob) { /* but we are limited by sndbuf */
/* so beg is not going to wrap, treat sndbuf as lim */
lim = beg + sndbuf; /* ... and proceed to the simple case */
}
else { /* we are limited by the end of the buffer, beg will wrap */
u8_t maybemore;
if (toeob == sndbuf || lim == 0) {
maybemore = 0;
}
else {
maybemore = TCP_WRITE_FLAG_MORE;
}
error = tcp_write(pcb, &pxtcp->inbuf.buf[beg], toeob, maybemore);
if (error != ERR_OK) {
goto writeerr;
}
nsent += toeob;
pxtcp->inbuf.unsent = 0; /* wrap */
if (maybemore) {
beg = 0;
sndbuf -= toeob;
}
else {
/* we are done sending, but ... */
goto check_inbound_close;
}
}
}
LWIP_ASSERT1(beg < lim);
sndlim = beg + sndbuf;
if (lim > sndlim) {
lim = sndlim;
}
tolim = lim - beg;
if (tolim > 0) {
error = tcp_write(pcb, &pxtcp->inbuf.buf[beg], (u16_t)tolim, 0);
if (error != ERR_OK) {
goto writeerr;
}
nsent += tolim;
pxtcp->inbuf.unsent = lim;
}
check_inbound_close:
if (pxtcp->inbound_close && pxtcp->inbuf.unsent == pxtcp->inbuf.vacant) {
pxtcp_pcb_forward_inbound_close(pxtcp);
}
DPRINTF2(("forward_inbound: pxtcp %p, pcb %p: sent %d bytes\n",
(void *)pxtcp, (void *)pcb, (int)nsent));
tcp_output(pcb);
pxtcp_pcb_cancel_poll(pxtcp);
return;
writeerr:
if (error == ERR_MEM) {
if (nsent > 0) { /* first write succeeded, second failed */
DPRINTF2(("forward_inbound: pxtcp %p, pcb %p: sent %d bytes only\n",
(void *)pxtcp, (void *)pcb, (int)nsent));
tcp_output(pcb);
}
DPRINTF(("forward_inbound: pxtcp %p, pcb %p: ERR_MEM\n",
(void *)pxtcp, (void *)pcb));
pxtcp_pcb_schedule_poll(pxtcp);
}
else {
DPRINTF(("forward_inbound: pxtcp %p, pcb %p: %s\n",
(void *)pxtcp, (void *)pcb, proxy_lwip_strerr(error)));
/* XXX: We shouldn't get ERR_ARG. Check ERR_CONN conditions early? */
LWIP_ASSERT1(error == ERR_MEM);
}
}
static void
pxtcp_pcb_forward_inbound_close(struct pxtcp *pxtcp)
{
struct tcp_pcb *pcb;
err_t error;
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->inbound_close);
LWIP_ASSERT1(!pxtcp->inbound_close_done);
LWIP_ASSERT1(pxtcp->inbuf.unsent == pxtcp->inbuf.vacant);
pcb = pxtcp->pcb;
LWIP_ASSERT1(pcb != NULL);
DPRINTF(("inbound_close: pxtcp %p; pcb %p: %s\n",
(void *)pxtcp, (void *)pcb, tcp_debug_state_str(pcb->state)));
error = tcp_shutdown(pcb, /*RX*/ 0, /*TX*/ 1);
if (error != ERR_OK) {
DPRINTF(("inbound_close: pxtcp %p; pcb %p:"
" tcp_shutdown: error=%s\n",
(void *)pxtcp, (void *)pcb, proxy_lwip_strerr(error)));
pxtcp_pcb_schedule_poll(pxtcp);
return;
}
pxtcp_pcb_cancel_poll(pxtcp);
pxtcp->inbound_close_done = 1;
/*
* If we have already done outbound close previously (passive
* close on the pcb), then we must not hold onto a pcb in LAST_ACK
* state since those will be deleted by lwip when that last ack
* comes from the guest.
*
* NB: We do NOT check for deferred delete here, even though we
* have just set one of its conditions, inbound_close_done. We
* let pcb callbacks that called us do that. It's simpler and
* cleaner that way.
*/
if (pxtcp->outbound_close_done && pxtcp_pcb_forward_inbound_done(pxtcp)) {
pxtcp_pcb_dissociate(pxtcp);
}
}
/**
* Check that all forwarded inbound data is sent and acked, and that
* inbound close is scheduled (we aren't called back when it's acked).
*/
DECLINLINE(int)
pxtcp_pcb_forward_inbound_done(const struct pxtcp *pxtcp)
{
return (pxtcp->inbound_close_done /* also implies that all data forwarded */
&& pxtcp->inbuf.unacked == pxtcp->inbuf.unsent);
}
/**
* tcp_sent() callback - guest acknowledged len bytes.
*
* We can advance inbuf::unacked index, making more free space in the
* ringbuf and wake up producer on poll manager thread.
*
* We can also try to send more data if we have any since pcb->snd_buf
* was increased and we are now permitted to send more.
*/
static err_t
pxtcp_pcb_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
{
struct pxtcp *pxtcp = (struct pxtcp *)arg;
size_t unacked;
LWIP_ASSERT1(pxtcp != NULL);
LWIP_ASSERT1(pxtcp->pcb == pcb);
LWIP_ASSERT1(pcb->callback_arg == pxtcp);
LWIP_UNUSED_ARG(pcb); /* only in assert */
DPRINTF2(("%s: pxtcp %p; pcb %p: +%d ACKed:"
" unacked %d, unsent %d, vacant %d\n",
__func__, (void *)pxtcp, (void *)pcb, (int)len,
(int)pxtcp->inbuf.unacked,
(int)pxtcp->inbuf.unsent,
(int)pxtcp->inbuf.vacant));
if (/* __predict_false */ len == 0) {
/* we are notified to start pulling */
LWIP_ASSERT1(!pxtcp->inbound_close);
LWIP_ASSERT1(pxtcp->inbound_pull);
unacked = pxtcp->inbuf.unacked;
}
else {
/*
* Advance unacked index. Guest acknowledged the data, so it
* won't be needed again for potential retransmits.
*/
unacked = pxtcp->inbuf.unacked + len;
if (unacked > pxtcp->inbuf.bufsize) {
unacked -= pxtcp->inbuf.bufsize;
}
pxtcp->inbuf.unacked = unacked;
}
/* arrange for more inbound data */
if (!pxtcp->inbound_close) {
if (!pxtcp->inbound_pull) {
/* wake up producer, in case it has stopped polling for POLLIN */
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_POLLIN, pxtcp);
#ifdef RT_OS_WINDOWS
/**
* We have't got enought room in ring buffer to read atm,
* but we don't want to lose notification from WSAW4ME when
* space would be available, so we reset event with empty recv
*/
recv(pxtcp->sock, NULL, 0, 0);
#endif
}
else {
ssize_t nread;
int stop_pollin; /* ignored */
nread = pxtcp_sock_read(pxtcp, &stop_pollin);
if (nread < 0) {
int sockerr = -(int)nread;
LWIP_UNUSED_ARG(sockerr);
DPRINTF0(("%s: sock %d: %R[sockerr]\n",
__func__, pxtcp->sock, sockerr));
/*
* Since we are pulling, pxtcp is no longer registered
* with poll manager so we can kill it directly.
*/
pxtcp_pcb_reset_pxtcp(pxtcp);
return ERR_ABRT;
}
}
}
/* forward more data if we can */
if (!pxtcp->inbound_close_done) {
pxtcp_pcb_forward_inbound(pxtcp);
/*
* NB: we might have dissociated from a pcb that transitioned
* to LAST_ACK state, so don't refer to pcb below.
*/
}
/* have we got all the acks? */
if (pxtcp->inbound_close /* no more new data */
&& pxtcp->inbuf.unsent == pxtcp->inbuf.vacant /* all data is sent */
&& unacked == pxtcp->inbuf.unsent) /* ... and is acked */
{
char *buf;
DPRINTF(("%s: pxtcp %p; pcb %p; all data ACKed\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb));
/* no more retransmits, so buf is not needed */
buf = pxtcp->inbuf.buf;
pxtcp->inbuf.buf = NULL;
free(buf);
/* no more acks, so no more callbacks */
if (pxtcp->pcb != NULL) {
tcp_sent(pxtcp->pcb, NULL);
}
/*
* We may be the last callback for this pcb if we have also
* successfully forwarded inbound_close.
*/
pxtcp_pcb_maybe_deferred_delete(pxtcp);
}
return ERR_OK;
}
/**
* Callback from poll manager (pxtcp::msg_inpull) to switch
* pxtcp_pcb_sent() to actively pull the last bits of input. See
* POLLHUP comment in pxtcp_pmgr_pump().
*
* pxtcp::sock is deregistered from poll manager after this callback
* is scheduled.
*/
static void
pxtcp_pcb_pull_inbound(void *ctx)
{
struct pxtcp *pxtcp = (struct pxtcp *)ctx;
LWIP_ASSERT1(pxtcp != NULL);
if (pxtcp->pcb == NULL) {
DPRINTF(("%s: pxtcp %p: PCB IS GONE\n", __func__, (void *)pxtcp));
pxtcp_pcb_reset_pxtcp(pxtcp);
return;
}
pxtcp->inbound_pull = 1;
if (pxtcp->outbound_close_done) {
DPRINTF(("%s: pxtcp %p: pcb %p (deferred delete)\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb));
pxtcp->deferred_delete = 1;
}
else {
DPRINTF(("%s: pxtcp %p: pcb %p\n",
__func__, (void *)pxtcp, (void *)pxtcp->pcb));
}
pxtcp_pcb_sent(pxtcp, pxtcp->pcb, 0);
}
/**
* tcp_err() callback.
*
* pcb is not passed to this callback since it may be already
* deallocated by the stack, but we can't do anything useful with it
* anyway since connection is gone.
*/
static void
pxtcp_pcb_err(void *arg, err_t error)
{
struct pxtcp *pxtcp = (struct pxtcp *)arg;
LWIP_ASSERT1(pxtcp != NULL);
/*
* ERR_CLSD is special - it is reported here when:
*
* . guest has already half-closed
* . we send FIN to guest when external half-closes
* . guest acks that FIN
*
* Since connection is closed but receive has been already closed
* lwip can only report this via tcp_err. At this point the pcb
* is still alive, so we can peek at it if need be.
*
* The interesting twist is when the ACK from guest that akcs our
* FIN also acks some data. In this scenario lwip will NOT call
* tcp_sent() callback with the ACK for that last bit of data but
* instead will call tcp_err with ERR_CLSD right away. Since that
* ACK also acknowledges all the data, we should run some of
* pxtcp_pcb_sent() logic here.
*/
if (error == ERR_CLSD) {
struct tcp_pcb *pcb = pxtcp->pcb; /* still alive */
DPRINTF2(("ERR_CLSD: pxtcp %p; pcb %p:"
" pcb->acked %d;"
" unacked %d, unsent %d, vacant %d\n",
(void *)pxtcp, (void *)pcb,
pcb->acked,
(int)pxtcp->inbuf.unacked,
(int)pxtcp->inbuf.unsent,
(int)pxtcp->inbuf.vacant));
LWIP_ASSERT1(pxtcp->pcb == pcb);
LWIP_ASSERT1(pcb->callback_arg == pxtcp);
if (pcb->acked > 0) {
pxtcp_pcb_sent(pxtcp, pcb, pcb->acked);
}
return;
}
DPRINTF0(("tcp_err: pxtcp=%p, error=%s\n",
(void *)pxtcp, proxy_lwip_strerr(error)));
pxtcp->pcb = NULL; /* pcb is gone */
if (pxtcp->deferred_delete) {
pxtcp_pcb_reset_pxtcp(pxtcp);
}
else {
pxtcp_chan_send_weak(POLLMGR_CHAN_PXTCP_RESET, pxtcp);
}
}