/* Ensures that Close frames received from peer conform to RFC 6455, section 7. */ static void nn_sws_validate_close_handshake (struct nn_sws *self) { uint16_t close_code; /* TODO: As per RFC 6455 7.1.6, the Close Reason following the Close Code must be well-formed UTF-8. Can we be liberal (as per Postel principle) and not check the validity of the UTF-8 here? */ close_code = nn_gets (self->inmsg_current_chunk_buf); if (close_code == NN_SWS_CLOSE_NORMAL || close_code == NN_SWS_CLOSE_GOING_AWAY || close_code == NN_SWS_CLOSE_ERR_PROTO || close_code == NN_SWS_CLOSE_ERR_WUT || close_code == NN_SWS_CLOSE_ERR_INVALID_FRAME || close_code == NN_SWS_CLOSE_ERR_POLICY || close_code == NN_SWS_CLOSE_ERR_TOOBIG || close_code == NN_SWS_CLOSE_ERR_EXTENSION || close_code == NN_SWS_CLOSE_ERR_SERVER) { /* RFC 6455 7.4.1 */ self->instate = NN_SWS_INSTATE_RECVD_CONTROL; nn_pipebase_received (&self->pipebase); } else if (close_code >= 3000 && close_code <= 3999) { /* RFC 6455 7.4.2 */ self->instate = NN_SWS_INSTATE_RECVD_CONTROL; nn_pipebase_received (&self->pipebase); } else if (close_code >= 4000 && close_code <= 4999) { /* RFC 6455 7.4.2 */ self->instate = NN_SWS_INSTATE_RECVD_CONTROL; nn_pipebase_received (&self->pipebase); } else { nn_sws_fail_conn (self, NN_SWS_CLOSE_ERR_PROTO, "Unrecognized close code."); } }
static void nn_stream_hdr_received (const struct nn_cp_sink **self, struct nn_usock *usock) { struct nn_stream *stream; int protocol; stream = nn_cont (self, struct nn_stream, sink); stream->sink = &nn_stream_state_active; nn_timer_stop (&stream->hdr_timeout); /* TODO: If it does not conform, drop the connection. */ protocol = nn_gets (stream->protohdr + 4); if (!nn_pipebase_ispeer (&stream->pipebase, protocol)) nn_assert (0); /* Connection is ready for sending. Make outpipe available to the SP socket. */ nn_pipebase_activate (&stream->pipebase); /* Start waiting for incoming messages. First, read the 8-byte size. */ stream->instate = NN_STREAM_INSTATE_HDR; nn_usock_recv (stream->usock, stream->inhdr, 8); }
static void nn_sws_handler (struct nn_fsm *self, int src, int type, NN_UNUSED void *srcptr) { struct nn_sws *sws; int rc; sws = nn_cont (self, struct nn_sws, fsm); switch (sws->state) { /******************************************************************************/ /* IDLE state. */ /******************************************************************************/ case NN_SWS_STATE_IDLE: switch (src) { case NN_FSM_ACTION: switch (type) { case NN_FSM_START: nn_wshdr_start (&sws->wshdr, sws->usock, &sws->pipebase, sws->mode, sws->remote_host); sws->state = NN_SWS_STATE_HANDSHAKE; return; default: nn_fsm_bad_action (sws->state, src, type); } default: nn_fsm_bad_source (sws->state, src, type); } /******************************************************************************/ /* HANDSHAKE state. */ /******************************************************************************/ case NN_SWS_STATE_HANDSHAKE: switch (src) { case NN_SWS_SRC_HANDSHAKE: switch (type) { case NN_WSHDR_OK: /* Before moving to the active state stop the handshake state machine. */ nn_wshdr_stop (&sws->wshdr); sws->state = NN_SWS_STATE_STOPPING_HANDSHAKE; return; case NN_WSHDR_ERROR: /* Raise the error and move directly to the DONE state. wshdr object will be stopped later on. */ sws->state = NN_SWS_STATE_DONE; nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_CLOSE_HANDSHAKE); return; default: nn_fsm_bad_action (sws->state, src, type); } default: nn_fsm_bad_source (sws->state, src, type); } /******************************************************************************/ /* STOPPING_HANDSHAKE state. */ /******************************************************************************/ case NN_SWS_STATE_STOPPING_HANDSHAKE: switch (src) { case NN_SWS_SRC_HANDSHAKE: switch (type) { case NN_WSHDR_STOPPED: /* Start the pipe. */ rc = nn_pipebase_start (&sws->pipebase); if (nn_slow (rc < 0)) { sws->state = NN_SWS_STATE_DONE; nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR); return; } /* Start receiving a message in asynchronous manner. */ nn_sws_recv_hdr (sws); /* Mark the pipe as available for sending. */ sws->outstate = NN_SWS_OUTSTATE_IDLE; sws->state = NN_SWS_STATE_ACTIVE; return; default: nn_fsm_bad_action (sws->state, src, type); } default: nn_fsm_bad_source (sws->state, src, type); } /******************************************************************************/ /* ACTIVE state. */ /******************************************************************************/ case NN_SWS_STATE_ACTIVE: switch (src) { case NN_SWS_SRC_USOCK: switch (type) { case NN_USOCK_SENT: /* The message is now fully sent. */ nn_assert (sws->outstate == NN_SWS_OUTSTATE_SENDING); sws->outstate = NN_SWS_OUTSTATE_IDLE; nn_msg_term (&sws->outmsg); nn_msg_init (&sws->outmsg, 0); nn_pipebase_sent (&sws->pipebase); return; case NN_USOCK_RECEIVED: switch (sws->instate) { case NN_SWS_INSTATE_RECV_HDR: /* Require RSV1, RSV2, and RSV3 bits to be unset for as per RFC 6455 section 5.2. */ if (sws->inhdr [0] & NN_SWS_FRAME_BITMASK_RSV1 || sws->inhdr [0] & NN_SWS_FRAME_BITMASK_RSV2 || sws->inhdr [0] & NN_SWS_FRAME_BITMASK_RSV3) { nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "RSV1, RSV2, and RSV3 must be unset."); return; } sws->is_final_frame = sws->inhdr [0] & NN_SWS_FRAME_BITMASK_FIN; /* Communication from client to server must be masked. Communication from server to client must be unmasked. */ if (sws->mode == NN_WS_SERVER) { nn_assert (sws->inhdr [1] & NN_SWS_FRAME_BITMASK_MASKED); sws->ext_hdr_len = 4; } else { nn_assert (!(sws->inhdr [1] & NN_SWS_FRAME_BITMASK_MASKED)); sws->ext_hdr_len = 0; } sws->opcode = sws->inhdr [0] & NN_SWS_FRAME_BITMASK_OPCODE; sws->payload_ctl = sws->inhdr [1] & NN_SWS_FRAME_BITMASK_LENGTH; /* Prevent unexpected continuation frame. */ if (!sws->continuing && sws->opcode == NN_WS_OPCODE_FRAGMENT) { nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "No message to continue."); return; } /* Preserve initial message opcode and RSV bits in case this is a fragmented message. */ if (!sws->continuing) sws->inmsg_hdr = sws->inhdr [0] | NN_SWS_FRAME_BITMASK_FIN; if (sws->payload_ctl <= 0x7d) { sws->ext_hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_0; } else if (sws->payload_ctl <= 0xffff) { sws->ext_hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_16; } else { sws->ext_hdr_len += NN_SWS_FRAME_SIZE_PAYLOAD_63; } switch (sws->opcode) { case NN_WS_OPCODE_BINARY: sws->is_control_frame = 0; if (sws->continuing) { nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "Expected continuation frame opcode."); return; } if (!sws->is_final_frame) sws->continuing = 1; if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) { /* Only a remote server could send a 2-byte msg; sanity-check that this endpoint is a client. */ nn_assert (sws->mode == NN_WS_CLIENT); sws->inmsg_current_chunk_len = 0; if (sws->continuing) { /* This frame was empty, but continue next frame in fragmented sequence. */ nn_sws_recv_hdr (sws); return; } else { /* Special case when there is no payload, mask, or additional frames. */ sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED; nn_pipebase_received (&sws->pipebase); return; } } /* Continue to receive extended header+payload. */ break; case NN_WS_OPCODE_FRAGMENT: sws->is_control_frame = 0; sws->continuing = !sws->is_final_frame; if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) { /* Only a remote server could send a 2-byte msg; sanity-check that this endpoint is a client. */ nn_assert (sws->mode == NN_WS_CLIENT); sws->inmsg_current_chunk_len = 0; if (sws->continuing) { /* This frame was empty, but continue next frame in fragmented sequence. */ nn_sws_recv_hdr (sws); return; } else { /* Special case when there is no payload, mask, or additional frames. */ sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED; nn_pipebase_received (&sws->pipebase); return; } } /* Continue to receive extended header+payload. */ break; case NN_WS_OPCODE_CLOSE: /* RFC 6455 section 5.5.1. */ sws->is_control_frame = 1; if (!sws->is_final_frame) { /* As per RFC 6455 section 5.4, fragmentation of control frames is not allowed; on receipt the endpoint MUST close connection immediately. */ nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "Cannot fragment control message (FIN=0)."); return; } if (sws->payload_ctl > NN_SWS_MAX_SMALL_PAYLOAD) { /* As per RFC 6455 section 5.4, large payloads on control frames is not allowed, and on receipt the endpoint MUST close connection immediately. */ nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "Control frame payload exceeds allowable length."); return; } if (sws->payload_ctl == 1) { /* As per RFC 6455 section 5.5.1, if a payload is to accompany a close frame, the first two bytes MUST be the close code. */ nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "Expected 2byte close code."); return; } if (sws->ext_hdr_len == 0 && sws->payload_ctl == 0) { /* Special case when there is no payload, mask, or additional frames. */ sws->inmsg_current_chunk_len = 0; sws->instate = NN_SWS_INSTATE_RECVD_CONTROL; nn_pipebase_received (&sws->pipebase); return; } /* Continue to receive extended header+payload. */ break; default: /* Client sent an invalid opcode; as per RFC 6455 section 10.7, close connection with code. */ nn_sws_fail_conn (sws, NN_SWS_CLOSE_ERR_PROTO, "Invalid opcode."); return; } if (sws->ext_hdr_len == 0) { /* Only a remote server could send a 2-byte msg; sanity-check that this endpoint is a client. */ nn_assert (sws->mode == NN_WS_CLIENT); /* In the case of no additional header, the payload is known to not exceed this threshold. */ nn_assert (sws->payload_ctl <= 0x7d); /* In the case of no additional header, the payload is known to not exceed this threshold. */ nn_assert (sws->payload_ctl > 0); sws->instate = NN_SWS_INSTATE_RECV_PAYLOAD; sws->inmsg_current_chunk_len = sws->payload_ctl; /* Use scatter/gather array for application messages, and a fixed-width buffer for control messages. This is convenient since control messages can be interspersed between chunked application msgs. */ if (sws->is_control_frame) { sws->inmsg_current_chunk_buf = sws->inmsg_control; } else { sws->inmsg_chunks++; sws->inmsg_total_size += sws->inmsg_current_chunk_len; sws->inmsg_current_chunk_buf = nn_msg_chunk_new (sws->inmsg_current_chunk_len, &sws->inmsg_array); } nn_usock_recv (sws->usock, sws->inmsg_current_chunk_buf, sws->inmsg_current_chunk_len, NULL); return; } else { /* Continue receiving the rest of the header frame. */ sws->instate = NN_SWS_INSTATE_RECV_HDREXT; nn_usock_recv (sws->usock, sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL, sws->ext_hdr_len, NULL); return; } case NN_SWS_INSTATE_RECV_HDREXT: nn_assert (sws->ext_hdr_len > 0); if (sws->payload_ctl <= 0x7d) { sws->inmsg_current_chunk_len = sws->payload_ctl; if (sws->mode == NN_WS_SERVER) { memcpy (sws->mask, sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL, 4); } } else if (sws->payload_ctl == 0xffff) { sws->inmsg_current_chunk_len = nn_gets (sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL); if (sws->mode == NN_WS_SERVER) { memcpy (sws->mask, sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL + NN_SWS_FRAME_SIZE_PAYLOAD_16, 4); } } else { sws->inmsg_current_chunk_len = (size_t) nn_getll (sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL); if (sws->mode == NN_WS_SERVER) { memcpy (sws->mask, sws->inhdr + NN_SWS_FRAME_SIZE_INITIAL + NN_SWS_FRAME_SIZE_PAYLOAD_63, 4); } } /* Handle zero-length message bodies. */ if (sws->inmsg_current_chunk_len == 0) { if (sws->is_final_frame) { sws->instate = (sws->is_control_frame ? NN_SWS_INSTATE_RECVD_CONTROL : NN_SWS_INSTATE_RECVD_CHUNKED); nn_pipebase_received (&sws->pipebase); return; } else { nn_sws_recv_hdr (sws); return; } } nn_assert (sws->inmsg_current_chunk_len > 0); /* Use scatter/gather array for application messages, and a fixed-width buffer for control messages. This is convenient since control messages can be interspersed between chunked application msgs. */ if (sws->is_control_frame) { sws->inmsg_current_chunk_buf = sws->inmsg_control; } else { sws->inmsg_chunks++; sws->inmsg_total_size += sws->inmsg_current_chunk_len; sws->inmsg_current_chunk_buf = nn_msg_chunk_new (sws->inmsg_current_chunk_len, &sws->inmsg_array); } sws->instate = NN_SWS_INSTATE_RECV_PAYLOAD; nn_usock_recv (sws->usock, sws->inmsg_current_chunk_buf, sws->inmsg_current_chunk_len, NULL); return; case NN_SWS_INSTATE_RECV_PAYLOAD: /* Unmask if necessary. */ if (sws->mode == NN_WS_SERVER) { nn_sws_mask_payload (sws->inmsg_current_chunk_buf, sws->inmsg_current_chunk_len, sws->mask, NULL); } switch (sws->opcode) { case NN_WS_OPCODE_BINARY: case NN_WS_OPCODE_FRAGMENT: if (sws->is_final_frame) { sws->instate = NN_SWS_INSTATE_RECVD_CHUNKED; nn_pipebase_received (&sws->pipebase); } else { nn_sws_recv_hdr (sws); } return; case NN_WS_OPCODE_CLOSE: /* If the payload is not even long enough for the required 2-octet Close Code, the connection should have been failed upstream. */ nn_assert (sws->inmsg_current_chunk_len >= 2); nn_sws_validate_close_handshake (sws); return; default: /* This should have been prevented upstream. */ nn_assert (0); return; } default: nn_fsm_error ("Unexpected socket instate", sws->state, src, type); } case NN_USOCK_SHUTDOWN: nn_pipebase_stop (&sws->pipebase); sws->state = NN_SWS_STATE_BROKEN_CONNECTION; return; case NN_USOCK_ERROR: nn_pipebase_stop (&sws->pipebase); sws->state = NN_SWS_STATE_DONE; nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR); return; default: nn_fsm_bad_action (sws->state, src, type); } break; default: nn_fsm_bad_source (sws->state, src, type); } /******************************************************************************/ /* CLOSING_CONNECTION state. */ /* Wait for acknowledgement closing handshake was successfully sent. */ /******************************************************************************/ case NN_SWS_STATE_CLOSING_CONNECTION: switch (src) { case NN_SWS_SRC_USOCK: switch (type) { case NN_USOCK_SENT: /* Wait for acknowledgement closing handshake was sent to peer. */ nn_assert (sws->outstate == NN_SWS_OUTSTATE_SENDING); sws->outstate = NN_SWS_OUTSTATE_IDLE; sws->state = NN_SWS_STATE_DONE; nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_CLOSE_HANDSHAKE); return; case NN_USOCK_SHUTDOWN: return; case NN_USOCK_ERROR: sws->state = NN_SWS_STATE_DONE; nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR); return; default: nn_fsm_bad_action (sws->state, src, type); } default: nn_fsm_bad_source (sws->state, src, type); } /******************************************************************************/ /* SHUTTING_DOWN state. */ /* The underlying connection is closed. We are just waiting that underlying */ /* usock being closed */ /******************************************************************************/ case NN_SWS_STATE_BROKEN_CONNECTION: switch (src) { case NN_SWS_SRC_USOCK: switch (type) { case NN_USOCK_ERROR: sws->state = NN_SWS_STATE_DONE; nn_fsm_raise (&sws->fsm, &sws->done, NN_SWS_RETURN_ERROR); return; default: nn_fsm_bad_action (sws->state, src, type); } default: nn_fsm_bad_source (sws->state, src, type); } /******************************************************************************/ /* DONE state. */ /* The underlying connection is closed. There's nothing that can be done in */ /* this state except stopping the object. */ /******************************************************************************/ case NN_SWS_STATE_DONE: nn_fsm_bad_source (sws->state, src, type); /******************************************************************************/ /* Invalid state. */ /******************************************************************************/ default: nn_fsm_bad_state (sws->state, src, type); } }
static void nn_streamhdr_handler (struct nn_fsm *self, int src, int type, void *srcptr) { struct nn_streamhdr *streamhdr; struct nn_iovec iovec; int protocol; streamhdr = nn_cont (self, struct nn_streamhdr, fsm); /******************************************************************************/ /* STOP procedure. */ /******************************************************************************/ if (nn_slow (src == NN_FSM_ACTION && type == NN_FSM_STOP)) { nn_timer_stop (&streamhdr->timer); streamhdr->state = NN_STREAMHDR_STATE_STOPPING; } if (nn_slow (streamhdr->state == NN_STREAMHDR_STATE_STOPPING)) { if (!nn_timer_isidle (&streamhdr->timer)) return; streamhdr->state = NN_STREAMHDR_STATE_IDLE; nn_fsm_stopped (&streamhdr->fsm, NN_STREAMHDR_STOPPED); return; } switch (streamhdr->state) { /******************************************************************************/ /* IDLE state. */ /******************************************************************************/ case NN_STREAMHDR_STATE_IDLE: switch (src) { case NN_FSM_ACTION: switch (type) { case NN_FSM_START: nn_timer_start (&streamhdr->timer, 1000); iovec.iov_base = streamhdr->protohdr; iovec.iov_len = sizeof (streamhdr->protohdr); nn_usock_send (streamhdr->usock, &iovec, 1); streamhdr->state = NN_STREAMHDR_STATE_SENDING; return; default: nn_fsm_bad_action (streamhdr->state, src, type); } default: nn_fsm_bad_source (streamhdr->state, src, type); } /******************************************************************************/ /* SENDING state. */ /******************************************************************************/ case NN_STREAMHDR_STATE_SENDING: switch (src) { case NN_STREAMHDR_SRC_USOCK: switch (type) { case NN_USOCK_SENT: nn_usock_recv (streamhdr->usock, streamhdr->protohdr, sizeof (streamhdr->protohdr)); streamhdr->state = NN_STREAMHDR_STATE_RECEIVING; return; case NN_USOCK_ERROR: nn_timer_stop (&streamhdr->timer); streamhdr->state = NN_STREAMHDR_STATE_STOPPING_TIMER_ERROR; return; default: nn_fsm_bad_action (streamhdr->state, src, type); } case NN_STREAMHDR_SRC_TIMER: switch (type) { case NN_TIMER_TIMEOUT: nn_timer_stop (&streamhdr->timer); streamhdr->state = NN_STREAMHDR_STATE_STOPPING_TIMER_ERROR; return; default: nn_fsm_bad_action (streamhdr->state, src, type); } default: nn_fsm_bad_source (streamhdr->state, src, type); } /******************************************************************************/ /* RECEIVING state. */ /******************************************************************************/ case NN_STREAMHDR_STATE_RECEIVING: switch (src) { case NN_STREAMHDR_SRC_USOCK: switch (type) { case NN_USOCK_RECEIVED: /* Here we are checking whether the peer speaks the same protocol as this socket. */ if (memcmp (streamhdr->protohdr, "\0SP\0", 4) != 0) goto invalidhdr; protocol = nn_gets (streamhdr->protohdr + 4); if (!nn_pipebase_ispeer (streamhdr->pipebase, protocol)) goto invalidhdr; nn_timer_stop (&streamhdr->timer); streamhdr->state = NN_STREAMHDR_STATE_STOPPING_TIMER_DONE; return; case NN_USOCK_ERROR: invalidhdr: nn_timer_stop (&streamhdr->timer); streamhdr->state = NN_STREAMHDR_STATE_STOPPING_TIMER_ERROR; return; default: nn_assert (0); } case NN_STREAMHDR_SRC_TIMER: switch (type) { case NN_TIMER_TIMEOUT: nn_timer_stop (&streamhdr->timer); streamhdr->state = NN_STREAMHDR_STATE_STOPPING_TIMER_ERROR; return; default: nn_fsm_bad_action (streamhdr->state, src, type); } default: nn_fsm_bad_source (streamhdr->state, src, type); } /******************************************************************************/ /* STOPPING_TIMER_ERROR state. */ /******************************************************************************/ case NN_STREAMHDR_STATE_STOPPING_TIMER_ERROR: switch (src) { case NN_STREAMHDR_SRC_TIMER: switch (type) { case NN_TIMER_STOPPED: nn_usock_swap_owner (streamhdr->usock, &streamhdr->usock_owner); streamhdr->usock = NULL; streamhdr->usock_owner.src = -1; streamhdr->usock_owner.fsm = NULL; streamhdr->state = NN_STREAMHDR_STATE_DONE; nn_fsm_raise (&streamhdr->fsm, &streamhdr->done, NN_STREAMHDR_ERROR); return; default: nn_fsm_bad_action (streamhdr->state, src, type); } default: nn_fsm_bad_source (streamhdr->state, src, type); } /******************************************************************************/ /* STOPPING_TIMER_DONE state. */ /******************************************************************************/ case NN_STREAMHDR_STATE_STOPPING_TIMER_DONE: switch (src) { case NN_STREAMHDR_SRC_TIMER: switch (type) { case NN_TIMER_STOPPED: nn_usock_swap_owner (streamhdr->usock, &streamhdr->usock_owner); streamhdr->usock = NULL; streamhdr->usock_owner.src = -1; streamhdr->usock_owner.fsm = NULL; streamhdr->state = NN_STREAMHDR_STATE_DONE; nn_fsm_raise (&streamhdr->fsm, &streamhdr->done, NN_STREAMHDR_OK); return; default: nn_fsm_bad_action (streamhdr->state, src, type); } default: nn_fsm_bad_source (streamhdr->state, src, type); } /******************************************************************************/ /* DONE state. */ /* The header exchange was either done successfully of failed. There's */ /* nothing that can be done in this state except stopping the object. */ /******************************************************************************/ case NN_STREAMHDR_STATE_DONE: nn_fsm_bad_source (streamhdr->state, src, type); /******************************************************************************/ /* Invalid state. */ /******************************************************************************/ default: nn_fsm_bad_state (streamhdr->state, src, type); } }
/* Main body of the daemon. */ static void nn_tcpmuxd_routine (void *arg) { int rc; struct nn_tcpmuxd_ctx *ctx; struct pollfd pfd [2]; int conn; int pos; char service [256]; struct nn_tcpmuxd_conn *tc; size_t sz; ssize_t ssz; int i; struct nn_list_item *it; unsigned char buf [2]; struct timeval tv; ctx = (struct nn_tcpmuxd_ctx*) arg; pfd [0].fd = ctx->tcp_listener; pfd [0].events = POLLIN; pfd [1].fd = ctx->ipc_listener; pfd [1].events = POLLIN; while (1) { /* Wait for events. */ rc = poll (pfd, 2, -1); errno_assert (rc >= 0); nn_assert (rc != 0); /* There's an incoming TCP connection. */ if (pfd [0].revents & POLLIN) { /* Accept the connection. */ conn = accept (ctx->tcp_listener, NULL, NULL); if (conn < 0 && errno == ECONNABORTED) continue; errno_assert (conn >= 0); tv.tv_sec = 0; tv.tv_usec = 100000; rc = setsockopt (conn, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)); errno_assert (rc == 0); rc = setsockopt (conn, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (tv)); errno_assert (rc == 0); /* Read TCPMUX header. */ pos = 0; while (1) { nn_assert (pos < sizeof (service)); ssz = recv (conn, &service [pos], 1, 0); if (ssz < 0 && errno == EAGAIN) { close (conn); continue; } errno_assert (ssz >= 0); nn_assert (ssz == 1); service [pos] = tolower (service [pos]); if (pos > 0 && service [pos - 1] == 0x0d && service [pos] == 0x0a) break; ++pos; } service [pos - 1] = 0; /* Check whether specified service is listening. */ for (it = nn_list_begin (&ctx->conns); it != nn_list_end (&ctx->conns); it = nn_list_next (&ctx->conns, it)) { tc = nn_cont (it, struct nn_tcpmuxd_conn, item); if (strcmp (service, tc->service) == 0) break; } /* If no one is listening, tear down the connection. */ if (it == nn_list_end (&ctx->conns)) { ssz = send (conn, "-\x0d\x0a", 3, 0); if (ssz < 0 && errno == EAGAIN) { close (conn); continue; } errno_assert (ssz >= 0); nn_assert (ssz == 3); close (conn); continue; } /* Send TCPMUX reply. */ ssz = send (conn, "+\x0d\x0a", 3, 0); if (ssz < 0 && errno == EAGAIN) { close (conn); continue; } errno_assert (ssz >= 0); nn_assert (ssz == 3); /* Pass the file descriptor to the listening process. */ rc = send_fd (tc->fd, conn); errno_assert (rc == 0); } /* There's an incoming IPC connection. */ if (pfd [1].revents & POLLIN) { /* Accept the connection. */ conn = accept (ctx->ipc_listener, NULL, NULL); if (conn < 0 && errno == ECONNABORTED) continue; errno_assert (conn >= 0); /* Create new connection entry. */ tc = nn_alloc (sizeof (struct nn_tcpmuxd_conn), "tcpmuxd_conn"); nn_assert (tc); tc->fd = conn; nn_list_item_init (&tc->item); /* Read the connection header. */ ssz = recv (conn, buf, 2, 0); errno_assert (ssz >= 0); nn_assert (ssz == 2); sz = nn_gets (buf); tc->service = nn_alloc (sz + 1, "tcpmuxd_conn.service"); nn_assert (tc->service); ssz = recv (conn, tc->service, sz, 0); errno_assert (ssz >= 0); nn_assert (ssz == sz); for (i = 0; i != sz; ++i) tc->service [sz] = tolower (tc->service [sz]); tc->service [sz] = 0; /* Add the entry to the IPC connections list. */ nn_list_insert (&ctx->conns, &tc->item, nn_list_end (&ctx->conns)); } }
/* Main body of the daemon. */ static void nn_tcpmuxd_routine (void *arg) { int rc; struct nn_tcpmuxd_ctx *ctx; int conn; int pos; char service [256]; struct nn_tcpmuxd_conn *tc = 0; size_t sz; ssize_t ssz; int i; struct nn_list_item *it; unsigned char buf [2]; struct timeval tv; ctx = (struct nn_tcpmuxd_ctx*) arg; while (1) { /* Wait for events. */ rc = (int32_t)poll (ctx->pfd, (int32_t)ctx->pfd_size, -1); errno_assert (rc >= 0); nn_assert (rc != 0); /* There's an incoming TCP connection. */ if (ctx->pfd [0].revents & POLLIN) { /* Accept the connection. */ conn = accept (ctx->tcp_listener, NULL, NULL); if (conn < 0 && errno == ECONNABORTED) continue; errno_assert (conn >= 0); /* Set timeouts to prevent malevolent client blocking the service. Note that these options are not supported on Solaris. */ tv.tv_sec = 0; tv.tv_usec = 100000; rc = setsockopt (conn, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)); errno_assert (rc == 0 || (rc < 0 && errno == ENOPROTOOPT)); rc = setsockopt (conn, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (tv)); errno_assert (rc == 0 || (rc < 0 && errno == ENOPROTOOPT)); /* Read TCPMUX header. */ pos = 0; while (1) { nn_assert (pos < sizeof (service)); ssz = recv (conn, &service [pos], 1, 0); if (ssz < 0 && errno == EAGAIN) { close (conn); continue; } errno_assert (ssz >= 0); nn_assert (ssz == 1); service [pos] = tolower ((uint32_t)service [pos]); if (pos > 0 && service [pos - 1] == 0x0d && service [pos] == 0x0a) break; ++pos; } service [pos - 1] = 0; /* Check whether specified service is listening. */ for (it = nn_list_begin (&ctx->conns); it != nn_list_end (&ctx->conns); it = nn_list_next (&ctx->conns, it)) { tc = nn_cont (it, struct nn_tcpmuxd_conn, item); if (strcmp (service, tc->service) == 0) break; } /* If no one is listening, tear down the connection. */ if (it == nn_list_end (&ctx->conns)) { ssz = send (conn, "-\x0d\x0a", 3, 0); if (ssz < 0 && errno == EAGAIN) { close (conn); continue; } errno_assert (ssz >= 0); nn_assert (ssz == 3); close (conn); continue; } /* Send TCPMUX reply. */ ssz = send (conn, "+\x0d\x0a", 3, 0); if (ssz < 0 && errno == EAGAIN) { close (conn); continue; } errno_assert (ssz >= 0); nn_assert (ssz == 3); nn_assert (tc != 0); /* Pass the file descriptor to the listening process. */ rc = nn_tcpmuxd_send_fd (tc->fd, conn); errno_assert (rc == 0); } /* There's an incoming IPC connection. */ if (ctx->pfd [1].revents & POLLIN) { /* Accept the connection. */ conn = accept (ctx->ipc_listener, NULL, NULL); if (conn < 0 && errno == ECONNABORTED) continue; errno_assert (conn >= 0); /* Create new connection entry. */ tc = nn_alloc (sizeof (struct nn_tcpmuxd_conn), "tcpmuxd_conn"); nn_assert (tc); tc->fd = conn; nn_list_item_init (&tc->item); /* Adjust the pollset. We will poll for errors only. */ ctx->pfd_size++; if (ctx->pfd_size > ctx->pfd_capacity) { ctx->pfd_capacity *= 2; ctx->pfd = nn_realloc (ctx->pfd, sizeof (struct pollfd) * ctx->pfd_capacity); alloc_assert (ctx->pfd); } ctx->pfd [ctx->pfd_size - 1].fd = conn; ctx->pfd [ctx->pfd_size - 1].events = 0; ctx->pfd [ctx->pfd_size - 1].revents = 0; /* Read the connection header. */ ssz = recv (conn, buf, 2, 0); errno_assert (ssz >= 0); nn_assert (ssz == 2); sz = nn_gets (buf); tc->service = nn_alloc (sz + 1, "tcpmuxd_conn.service"); nn_assert (tc->service); ssz = recv (conn, tc->service, sz, 0); errno_assert (ssz >= 0); nn_assert (ssz == sz); for (i = 0; i != sz; ++i) tc->service [i] = tolower ((uint32_t)tc->service [i]); tc->service [sz] = 0; /* Add the entry to the IPC connections list. */ nn_list_insert (&ctx->conns, &tc->item, nn_list_end (&ctx->conns)); } for (i = 2; i < ctx->pfd_size; ++i) { if (ctx->pfd [i].revents & POLLERR || ctx->pfd [i].revents & POLLHUP) { nn_tcpmuxd_disconnect (ctx, i); i--; } } }