diff options
Diffstat (limited to 'tcp_splice.c')
| -rw-r--r-- | tcp_splice.c | 446 |
1 files changed, 235 insertions, 211 deletions
diff --git a/tcp_splice.c b/tcp_splice.c index f048a82..4b01f1a 100644 --- a/tcp_splice.c +++ b/tcp_splice.c @@ -44,7 +44,6 @@ #include <net/ethernet.h> #include <netinet/in.h> #include <netinet/tcp.h> -#include <sys/epoll.h> #include <sys/types.h> #include <sys/socket.h> @@ -56,6 +55,7 @@ #include "siphash.h" #include "inany.h" #include "flow.h" +#include "epoll_ctl.h" #include "flow_table.h" @@ -95,7 +95,7 @@ static int tcp_conn_sock_ns(const struct ctx *c, sa_family_t af); * conn_at_sidx() - Get spliced TCP connection specific flow at given sidx * @sidx: Flow and side to retrieve * - * Return: Spliced TCP connection at @sidx, or NULL of @sidx is invalid. + * Return: spliced TCP connection at @sidx, or NULL of @sidx is invalid. * Asserts if the flow at @sidx is not FLOW_TCP_SPLICE. */ static struct tcp_splice_conn *conn_at_sidx(flow_sidx_t sidx) @@ -105,7 +105,7 @@ static struct tcp_splice_conn *conn_at_sidx(flow_sidx_t sidx) if (!flow) return NULL; - ASSERT(flow->f.type == FLOW_TCP_SPLICE); + assert(flow->f.type == FLOW_TCP_SPLICE); return &flow->tcp_splice; } @@ -114,68 +114,56 @@ static struct tcp_splice_conn *conn_at_sidx(flow_sidx_t sidx) * @events: Connection event flags * @ev: Events to fill in, 0 is accepted socket, 1 is connecting socket */ -static void tcp_splice_conn_epoll_events(uint16_t events, - struct epoll_event ev[]) +static uint32_t tcp_splice_conn_epoll_events(uint16_t events, unsigned sidei) { - unsigned sidei; - - flow_foreach_sidei(sidei) - ev[sidei].events = 0; + uint32_t e = 0; if (events & SPLICE_ESTABLISHED) { - flow_foreach_sidei(sidei) { - if (!(events & FIN_SENT(!sidei))) - ev[sidei].events = EPOLLIN | EPOLLRDHUP; - } - } else if (events & SPLICE_CONNECT) { - ev[1].events = EPOLLOUT; + if (!(events & FIN_SENT(!sidei))) + e = EPOLLIN | EPOLLRDHUP; + } else if (sidei == 1 && events & SPLICE_CONNECT) { + e = EPOLLOUT; } - flow_foreach_sidei(sidei) - ev[sidei].events |= (events & OUT_WAIT(sidei)) ? EPOLLOUT : 0; + if (events & OUT_WAIT(sidei)) + e |= EPOLLOUT; + if (events & OUT_WAIT(!sidei)) + e &= ~EPOLLIN; + + return e; } /** * tcp_splice_epoll_ctl() - Add/modify/delete epoll state from connection events - * @c: Execution context * @conn: Connection pointer + * @now: Current timestamp * * Return: 0 on success, negative error code on failure (not on deletion) */ -static int tcp_splice_epoll_ctl(const struct ctx *c, - struct tcp_splice_conn *conn) +static int tcp_splice_epoll_ctl(struct tcp_splice_conn *conn, + const struct timespec *now) { - int m = conn->in_epoll ? EPOLL_CTL_MOD : EPOLL_CTL_ADD; - const union epoll_ref ref[SIDES] = { - { .type = EPOLL_TYPE_TCP_SPLICE, .fd = conn->s[0], - .flowside = FLOW_SIDX(conn, 0) }, - { .type = EPOLL_TYPE_TCP_SPLICE, .fd = conn->s[1], - .flowside = FLOW_SIDX(conn, 1) } - }; - struct epoll_event ev[SIDES] = { { .data.u64 = ref[0].u64 }, - { .data.u64 = ref[1].u64 } }; - - tcp_splice_conn_epoll_events(conn->events, ev); - - if (epoll_ctl(c->epollfd, m, conn->s[0], &ev[0]) || - epoll_ctl(c->epollfd, m, conn->s[1], &ev[1])) { + uint32_t events[2]; + + events[0] = tcp_splice_conn_epoll_events(conn->events, 0); + events[1] = tcp_splice_conn_epoll_events(conn->events, 1); + + if (flow_epoll_set(&conn->f, EPOLL_CTL_MOD, events[0], conn->s[0], 0) || + flow_epoll_set(&conn->f, EPOLL_CTL_MOD, events[1], conn->s[1], 1)) { int ret = -errno; - flow_err(conn, "ERROR on epoll_ctl(): %s", strerror_(errno)); + flow_perror_ratelimit(conn, now, "ERROR on epoll_ctl()"); return ret; } - conn->in_epoll = true; - return 0; } /** * conn_flag_do() - Set/unset given flag, log, update epoll on CLOSING flag - * @c: Execution context * @conn: Connection pointer * @flag: Flag to set, or ~flag to unset */ -static void conn_flag_do(const struct ctx *c, struct tcp_splice_conn *conn, +static void conn_flag_do(struct tcp_splice_conn *conn, unsigned long flag) { if (flag & (flag - 1)) { @@ -200,25 +188,25 @@ static void conn_flag_do(const struct ctx *c, struct tcp_splice_conn *conn, } if (flag == CLOSING) { - epoll_del(c, conn->s[0]); - epoll_del(c, conn->s[1]); + epoll_del(flow_epollfd(&conn->f), conn->s[0]); + epoll_del(flow_epollfd(&conn->f), conn->s[1]); } } -#define conn_flag(c, conn, flag) \ +#define conn_flag(conn, flag) \ do { \ flow_trace(conn, "flag at %s:%i", __func__, __LINE__); \ - conn_flag_do(c, conn, flag); \ + conn_flag_do(conn, flag); \ } while (0) /** * conn_event_do() - Set and log connection events, update epoll state - * @c: Execution context * @conn: Connection pointer * @event: Connection event + * @now: Current timestamp */ -static void conn_event_do(const struct ctx *c, struct tcp_splice_conn *conn, - unsigned long event) +static void conn_event_do(struct tcp_splice_conn *conn, unsigned long event, + const struct timespec *now) { if (event & (event - 1)) { int flag_index = fls(~event); @@ -240,16 +228,33 @@ static void conn_event_do(const struct ctx *c, struct tcp_splice_conn *conn, flow_dbg(conn, "%s", tcp_splice_event_str[flag_index]); } - if (tcp_splice_epoll_ctl(c, conn)) - conn_flag(c, conn, CLOSING); + if (tcp_splice_epoll_ctl(conn, now)) + conn_flag(conn, CLOSING); } -#define conn_event(c, conn, event) \ +#define conn_event(conn, event, now) \ do { \ flow_trace(conn, "event at %s:%i",__func__, __LINE__); \ - conn_event_do(c, conn, event); \ + conn_event_do(conn, event, now); \ } while (0) +/** + * tcp_splice_rst() - Close spliced connection forcing RST on each side + * @conn: Connection pointer + */ +static void tcp_splice_rst(struct tcp_splice_conn *conn) +{ + unsigned sidei; + + if (conn->flags & CLOSING) + return; /* Nothing to do */ + + /* Force RST on sockets to inform the peer */ + flow_foreach_sidei(sidei) + tcp_linger0(conn, conn->s[sidei]); + + conn_flag(conn, CLOSING); +} /** * tcp_splice_flow_defer() - Deferred per-flow handling (clean up closed) @@ -277,7 +282,7 @@ bool tcp_splice_flow_defer(struct tcp_splice_conn *conn) conn->s[sidei] = -1; } - conn->read[sidei] = conn->written[sidei] = 0; + conn->pending[sidei] = 0; } conn->events = SPLICE_CLOSED; @@ -291,11 +296,13 @@ bool tcp_splice_flow_defer(struct tcp_splice_conn *conn) * tcp_splice_connect_finish() - Completion of connect() or call on success * @c: Execution context * @conn: Connection pointer + * @now: Current timestamp * * Return: 0 on success, -EIO on failure */ static int tcp_splice_connect_finish(const struct ctx *c, - struct tcp_splice_conn *conn) + struct tcp_splice_conn *conn, + const struct timespec *now) { unsigned sidei; int i = 0; @@ -313,9 +320,11 @@ static int tcp_splice_connect_finish(const struct ctx *c, if (conn->pipe[sidei][0] < 0) { if (pipe2(conn->pipe[sidei], O_NONBLOCK | O_CLOEXEC)) { - flow_err(conn, "cannot create %d->%d pipe: %s", - sidei, !sidei, strerror_(errno)); - conn_flag(c, conn, CLOSING); + flow_perror_ratelimit( + conn, now, + "cannot create %d->%d pipe", + sidei, !sidei); + tcp_splice_rst(conn); return -EIO; } @@ -329,7 +338,7 @@ static int tcp_splice_connect_finish(const struct ctx *c, } if (!(conn->events & SPLICE_ESTABLISHED)) - conn_event(c, conn, SPLICE_ESTABLISHED); + conn_event(conn, SPLICE_ESTABLISHED, now); return 0; } @@ -338,24 +347,25 @@ static int tcp_splice_connect_finish(const struct ctx *c, * tcp_splice_connect() - Create and connect socket for new spliced connection * @c: Execution context * @conn: Connection pointer + * @now: Current timestamp * * Return: 0 for connect() succeeded or in progress, negative value on error */ -static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn) +static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn, + const struct timespec *now) { const struct flowside *tgt = &conn->f.side[TGTSIDE]; sa_family_t af = inany_v4(&tgt->eaddr) ? AF_INET : AF_INET6; uint8_t tgtpif = conn->f.pif[TGTSIDE]; union sockaddr_inany sa; - socklen_t sl; int one = 1; if (tgtpif == PIF_HOST) - conn->s[1] = tcp_conn_sock(c, af); + conn->s[1] = tcp_conn_sock(af); else if (tgtpif == PIF_SPLICE) conn->s[1] = tcp_conn_sock_ns(c, af); else - ASSERT(0); + assert(0); if (conn->s[1] < 0) return -1; @@ -375,19 +385,27 @@ static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn) conn->s[1]); } - pif_sockaddr(c, &sa, &sl, tgtpif, &tgt->eaddr, tgt->eport); + pif_sockaddr(c, &sa, tgtpif, &tgt->eaddr, tgt->eport); + + flow_epollid_set(&conn->f, EPOLLFD_ID_DEFAULT); + if (flow_epoll_set(&conn->f, EPOLL_CTL_ADD, 0, conn->s[0], 0) || + flow_epoll_set(&conn->f, EPOLL_CTL_ADD, 0, conn->s[1], 1)) { + int ret = -errno; + flow_perror_ratelimit(conn, now, "Cannot register to epollfd"); + return ret; + } + + conn_event(conn, SPLICE_CONNECT, now); - if (connect(conn->s[1], &sa.sa, sl)) { + if (connect(conn->s[1], &sa.sa, socklen_inany(&sa))) { if (errno != EINPROGRESS) { flow_trace(conn, "Couldn't connect socket for splice: %s", strerror_(errno)); return -errno; } - - conn_event(c, conn, SPLICE_CONNECT); } else { - conn_event(c, conn, SPLICE_ESTABLISHED); - return tcp_splice_connect_finish(c, conn); + conn_event(conn, SPLICE_ESTABLISHED, now); + return tcp_splice_connect_finish(c, conn, now); } return 0; @@ -398,7 +416,7 @@ static int tcp_splice_connect(const struct ctx *c, struct tcp_splice_conn *conn) * @c: Execution context * @af: Address family (AF_INET or AF_INET6) * - * Return: Socket fd in the namespace on success, -errno on failure + * Return: socket fd in the namespace on success, -errno on failure */ static int tcp_conn_sock_ns(const struct ctx *c, sa_family_t af) { @@ -427,15 +445,17 @@ static int tcp_conn_sock_ns(const struct ctx *c, sa_family_t af) * @flow: flow to initialise * @s0: Accepted (side 0) socket * @sa: Peer address of connection + * @now: Current timestamp * * #syscalls:pasta setsockopt */ -void tcp_splice_conn_from_sock(const struct ctx *c, union flow *flow, int s0) +void tcp_splice_conn_from_sock(const struct ctx *c, union flow *flow, int s0, + const struct timespec *now) { struct tcp_splice_conn *conn = FLOW_SET_TYPE(flow, FLOW_TCP_SPLICE, tcp_splice); - ASSERT(c->mode == MODE_PASTA); + assert(c->mode == MODE_PASTA); conn->s[0] = s0; conn->s[1] = -1; @@ -445,189 +465,194 @@ void tcp_splice_conn_from_sock(const struct ctx *c, union flow *flow, int s0) if (setsockopt(s0, SOL_TCP, TCP_QUICKACK, &((int){ 1 }), sizeof(int))) flow_trace(conn, "failed to set TCP_QUICKACK on %i", s0); - if (tcp_splice_connect(c, conn)) - conn_flag(c, conn, CLOSING); + if (tcp_splice_connect(c, conn, now)) + tcp_splice_rst(conn); FLOW_ACTIVATE(conn); } /** - * tcp_splice_sock_handler() - Handler for socket mapped to spliced connection + * tcp_splice_forward() - Forward data in one direction using splice() * @c: Execution context - * @ref: epoll reference - * @events: epoll events bitmap + * @conn: Connection to forward data for + * @fromsidei: Side to forward data from + * @now: Current timestamp + * + * Return: 0 on success, -1 on error (connection should be reset) * * #syscalls:pasta splice */ -void tcp_splice_sock_handler(struct ctx *c, union epoll_ref ref, - uint32_t events) +static int tcp_splice_forward(struct ctx *c, + struct tcp_splice_conn *conn, unsigned fromsidei, + const struct timespec *now) { - struct tcp_splice_conn *conn = conn_at_sidx(ref.flowside); - unsigned evsidei = ref.flowside.sidei, fromsidei; - uint8_t lowat_set_flag, lowat_act_flag; - int eof, never_read; - - ASSERT(conn->f.type == FLOW_TCP_SPLICE); - - if (conn->events == SPLICE_CLOSED) - return; - - if (events & EPOLLERR) { - int err, rc; - socklen_t sl = sizeof(err); - - rc = getsockopt(ref.fd, SOL_SOCKET, SO_ERROR, &err, &sl); - if (rc) - flow_err(conn, "Error retrieving SO_ERROR: %s", - strerror_(errno)); - else - flow_trace(conn, "Error event on socket: %s", - strerror_(err)); - - goto close; - } - - if (conn->events == SPLICE_CONNECT) { - if (!(events & EPOLLOUT)) - goto close; - if (tcp_splice_connect_finish(c, conn)) - goto close; - } - - if (events & EPOLLOUT) { - fromsidei = !evsidei; - conn_event(c, conn, ~OUT_WAIT(evsidei)); - } else { - fromsidei = evsidei; - } - - if (events & EPOLLRDHUP) - /* For side 0 this is fake, but implied */ - conn_event(c, conn, FIN_RCVD(evsidei)); - -swap: - eof = 0; - never_read = 1; - - lowat_set_flag = RCVLOWAT_SET(fromsidei); - lowat_act_flag = RCVLOWAT_ACT(fromsidei); + uint8_t lowat_set_flag = RCVLOWAT_SET(fromsidei); + uint8_t lowat_act_flag = RCVLOWAT_ACT(fromsidei); while (1) { - ssize_t readlen, written, pending; + ssize_t readlen, written; int more = 0; -retry: - readlen = splice(conn->s[fromsidei], NULL, - conn->pipe[fromsidei][1], NULL, - c->tcp.pipe_size, - SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + do + readlen = splice(conn->s[fromsidei], NULL, + conn->pipe[fromsidei][1], NULL, + c->tcp.pipe_size, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK); + while (readlen < 0 && errno == EINTR); + + if (readlen < 0 && errno != EAGAIN) { + flow_perror_ratelimit( + conn, now, "Splicing from %s socket", + pif_name(conn->f.pif[fromsidei])); + return -1; + } + flow_trace(conn, "%zi from read-side call", readlen); - if (readlen < 0) { - if (errno == EINTR) - goto retry; - - if (errno != EAGAIN) - goto close; - } else if (!readlen) { - eof = 1; + + if (readlen <= 0) { + if (!readlen) /* EOF */ + conn_event(conn, FIN_RCVD(fromsidei), now); + + /* We're either blocked or at EOF on the read side, and + * there's nothing in the pipe so there's nothing to do + * write side either. + */ + if (!conn->pending[fromsidei]) + break; } else { - never_read = 0; + conn->pending[fromsidei] += readlen; if (readlen >= (long)c->tcp.pipe_size * 90 / 100) more = SPLICE_F_MORE; if (conn->flags & lowat_set_flag) - conn_flag(c, conn, lowat_act_flag); + conn_flag(conn, lowat_act_flag); + } + + do + written = splice(conn->pipe[fromsidei][0], NULL, + conn->s[!fromsidei], NULL, + c->tcp.pipe_size, + SPLICE_F_MOVE | more | SPLICE_F_NONBLOCK); + while (written < 0 && errno == EINTR); + + if (written < 0 && errno != EAGAIN) { + flow_perror_ratelimit( + conn, now, "Splicing to %s socket", + pif_name(conn->f.pif[!fromsidei])); + return -1; } -eintr: - written = splice(conn->pipe[fromsidei][0], NULL, - conn->s[!fromsidei], NULL, c->tcp.pipe_size, - SPLICE_F_MOVE | more | SPLICE_F_NONBLOCK); flow_trace(conn, "%zi from write-side call (passed %zi)", written, c->tcp.pipe_size); - /* Most common case: skip updating counters. */ - if (readlen > 0 && readlen == written) { - if (readlen >= (long)c->tcp.pipe_size * 10 / 100) - continue; - - if (conn->flags & lowat_set_flag && - readlen > (long)c->tcp.pipe_size / 10) { - int lowat = c->tcp.pipe_size / 4; - - if (setsockopt(conn->s[fromsidei], SOL_SOCKET, - SO_RCVLOWAT, - &lowat, sizeof(lowat))) { - flow_trace(conn, - "Setting SO_RCVLOWAT %i: %s", - lowat, strerror_(errno)); - } else { - conn_flag(c, conn, lowat_set_flag); - conn_flag(c, conn, lowat_act_flag); - } - } + if (written < 0) + break; + conn->pending[fromsidei] -= written; + + if (!conn->pending[fromsidei] && readlen <= 0) { + /* Read side is EOF or EAGAIN, and we emptied the pipe. + * No more we can do for now. + */ break; } + } - conn->read[fromsidei] += readlen > 0 ? readlen : 0; - conn->written[fromsidei] += written > 0 ? written : 0; - - if (written < 0) { - if (errno == EINTR) - goto eintr; + /* We need write-side wakeups if and only if we have data in the pipe to + * drain. + */ + if (conn->pending[fromsidei]) + conn_event(conn, OUT_WAIT(!fromsidei), now); + else + conn_event(conn, ~OUT_WAIT(!fromsidei), now); + + if ((conn->events & FIN_RCVD(fromsidei)) && + !(conn->events & FIN_SENT(!fromsidei)) && + !conn->pending[fromsidei]) { + if (shutdown(conn->s[!fromsidei], SHUT_WR) < 0) { + flow_perror_ratelimit(conn, now, "shutdown() on %s", + pif_name(conn->f.pif[!fromsidei])); + return -1; + } + conn_event(conn, FIN_SENT(!fromsidei), now); + } - if (errno != EAGAIN) - goto close; + return 0; +} - if (conn->read[fromsidei] == conn->written[fromsidei]) - break; +/** + * tcp_splice_sock_handler() - Handler for socket mapped to spliced connection + * @c: Execution context + * @ref: epoll reference + * @events: epoll events bitmap + * @now: Current timestamp + */ +void tcp_splice_sock_handler(struct ctx *c, union epoll_ref ref, + uint32_t events, const struct timespec *now) +{ + struct tcp_splice_conn *conn = conn_at_sidx(ref.flowside); + unsigned evsidei = ref.flowside.sidei; - conn_event(c, conn, OUT_WAIT(!fromsidei)); - break; - } + assert(conn->f.type == FLOW_TCP_SPLICE); - if (never_read && written == (long)(c->tcp.pipe_size)) - goto retry; + if (conn->events == SPLICE_CLOSED) + return; - pending = conn->read[fromsidei] - conn->written[fromsidei]; - if (!never_read && written > 0 && written < pending) - goto retry; + if (events & EPOLLERR) { + int err, rc; + socklen_t sl = sizeof(err); - if (eof) - break; + rc = getsockopt(ref.fd, SOL_SOCKET, SO_ERROR, &err, &sl); + if (rc) + flow_perror_ratelimit(conn, now, + "Error retrieving SO_ERROR"); + else + flow_dbg_ratelimit(conn, now, + "Error event on %s socket: %s", + pif_name(conn->f.pif[evsidei]), + strerror_(err)); + goto reset; } - if (conn->read[fromsidei] == conn->written[fromsidei] && eof) { - unsigned sidei; - - flow_foreach_sidei(sidei) { - if ((conn->events & FIN_RCVD(sidei)) && - !(conn->events & FIN_SENT(!sidei))) { - shutdown(conn->s[!sidei], SHUT_WR); - conn_event(c, conn, FIN_SENT(!sidei)); - } + if (conn->events == SPLICE_CONNECT) { + if (!(events & EPOLLOUT)) { + flow_err_ratelimit( + conn, now, + "Unexpected events 0x%x during connect", + events); + goto reset; } + if (tcp_splice_connect_finish(c, conn, now)) + goto reset; } - if (CONN_HAS(conn, FIN_SENT(0) | FIN_SENT(1))) - goto close; + if (events & EPOLLOUT) { + if (tcp_splice_forward(c, conn, !evsidei, now)) + goto reset; + } - if ((events & (EPOLLIN | EPOLLOUT)) == (EPOLLIN | EPOLLOUT)) { - events = EPOLLIN; + if (events & (EPOLLIN | EPOLLRDHUP)) { + if (tcp_splice_forward(c, conn, evsidei, now)) + goto reset; + } - fromsidei = !fromsidei; - goto swap; + if (CONN_HAS(conn, FIN_SENT(0) | FIN_SENT(1))) { + /* Clean close, no reset */ + conn_flag(conn, CLOSING); + return; } - if (events & EPOLLHUP) - goto close; + if (events & EPOLLHUP) { + flow_dbg_ratelimit(conn, now, "Hangup from %s socket", + pif_name(conn->f.pif[evsidei])); + goto reset; + } return; -close: - conn_flag(c, conn, CLOSING); +reset: + tcp_splice_rst(conn); } /** @@ -703,13 +728,13 @@ static int tcp_sock_refill_ns(void *arg) ns_enter(c); if (c->ifi4) { - int rc = tcp_sock_refill_pool(c, ns_sock_pool4, AF_INET); + int rc = tcp_sock_refill_pool(ns_sock_pool4, AF_INET); if (rc < 0) warn("TCP: Error refilling IPv4 ns socket pool: %s", strerror_(-rc)); } if (c->ifi6) { - int rc = tcp_sock_refill_pool(c, ns_sock_pool6, AF_INET6); + int rc = tcp_sock_refill_pool(ns_sock_pool6, AF_INET6); if (rc < 0) warn("TCP: Error refilling IPv6 ns socket pool: %s", strerror_(-rc)); @@ -747,14 +772,13 @@ void tcp_splice_init(struct ctx *c) /** * tcp_splice_timer() - Timer for spliced connections - * @c: Execution context * @conn: Connection to handle */ -void tcp_splice_timer(const struct ctx *c, struct tcp_splice_conn *conn) +void tcp_splice_timer(struct tcp_splice_conn *conn) { unsigned sidei; - ASSERT(!(conn->flags & CLOSING)); + assert(!(conn->flags & CLOSING)); flow_foreach_sidei(sidei) { if ((conn->flags & RCVLOWAT_SET(sidei)) && @@ -764,10 +788,10 @@ void tcp_splice_timer(const struct ctx *c, struct tcp_splice_conn *conn) flow_trace(conn, "can't set SO_RCVLOWAT on %d", conn->s[sidei]); } - conn_flag(c, conn, ~RCVLOWAT_SET(sidei)); + conn_flag(conn, ~RCVLOWAT_SET(sidei)); } } flow_foreach_sidei(sidei) - conn_flag(c, conn, ~RCVLOWAT_ACT(sidei)); + conn_flag(conn, ~RCVLOWAT_ACT(sidei)); } |
