/* destroy a connection context previously created with allocate_context(). * this is invoked only if and when the network and transport threads are * done. */ void _mysock_free_context(mysock_context_t *ctx) { int sd; assert(ctx); PTHREAD_CALL(pthread_cond_destroy(&ctx->blocking_cond)); PTHREAD_CALL(pthread_mutex_destroy(&ctx->blocking_lock)); PTHREAD_CALL(pthread_cond_destroy(&ctx->data_ready_cond)); PTHREAD_CALL(pthread_mutex_destroy(&ctx->data_ready_lock)); /* free any last buffers that might be lying around (e.g. retransmitted * packets from the peer). normally, the application from/to queues * should be empty by this point; the network receive queue may * legitimately have retransmitted packets, so silently discard these. */ (void) _mysock_free_queue(ctx, &ctx->network_recv_queue); (void) _mysock_free_queue(ctx, &ctx->app_recv_queue); (void) _mysock_free_queue(ctx, &ctx->app_send_queue); _network_close(&ctx->network_state); /* clear mysocket descriptor table entry */ sd = ctx->my_sd; if (global_ctx[sd] == ctx) global_ctx[sd] = 0; memset(ctx, 0, sizeof(*ctx)); free(ctx); }
static int _tcp_connect(network_context_t *ctx) { network_context_socket_tcp_t *tcp_io_ctx; assert(ctx); tcp_io_ctx = (network_context_socket_tcp_t *) ctx->impl_data; assert(tcp_io_ctx); PTHREAD_CALL(pthread_mutex_lock(&tcp_io_ctx->connect_lock)); if (!tcp_io_ctx->connected) { assert(ctx->peer_addr_valid); assert(ctx->peer_addr.sa_family == AF_INET); assert(((struct sockaddr_in *) &ctx->peer_addr)->sin_port > 0); DEBUG_LOG(("_tcp_connect (my_sd=%d): connecting on socket %d...\n", tcp_io_ctx->sock_ctx->my_sd, (int)GET_SOCKET(ctx))); if ((connect(GET_SOCKET(ctx), &ctx->peer_addr, sizeof(ctx->peer_addr))) < 0) { perror("connect (_tcp_connect)"); fprintf(stderr, "(errno=%d)\n", errno); return -1; } tcp_io_ctx->connected = TRUE; } PTHREAD_CALL(pthread_mutex_unlock(&tcp_io_ctx->connect_lock)); return 0; }
/* remove one packet from the head of the waiting packet queue, copying the * packet's payload into the specified buffer. returns the number of bytes * copied. if remove_partial is true, and there is insufficient room in the * destination buffer for the packet at the head of the queue, it is only * partially dequeued, and the remaining contents remain at the queue's head * for a subsequent call to dequeue_buffer(). */ size_t _mysock_dequeue_buffer(mysock_context_t *ctx, packet_queue_t *pq, void *dst, size_t max_len, bool_t remove_partial) { packet_queue_node_t *node; size_t packet_len; assert(ctx && pq && dst); /* block until queue is non-empty */ PTHREAD_CALL(pthread_mutex_lock(&ctx->data_ready_lock)); while (!pq->head) { PTHREAD_CALL(pthread_cond_wait(&ctx->data_ready_cond, &ctx->data_ready_lock)); } node = pq->head; assert(node && node->data); if (node->data_len > max_len && remove_partial) { /* remove only a portion of the packet at the head of the queue, * leaving the rest around for the next call to dequeue_buffer(). */ PTHREAD_CALL(pthread_mutex_unlock(&ctx->data_ready_lock)); memcpy(dst, node->data, max_len); memmove(node->data, node->data + max_len, node->data_len - max_len); node->data_len -= max_len; packet_len = max_len; } else { /* dequeue the entire packet at the head of the queue */ if (!(pq->head = pq->head->next)) { assert(pq->tail == node); pq->tail = NULL; } PTHREAD_CALL(pthread_mutex_unlock(&ctx->data_ready_lock)); memcpy(dst, node->data, MIN(max_len, node->data_len)); packet_len = node->data_len; free(node->data); memset(node, 0, sizeof(*node)); free(node); } return packet_len; }
/* create a detached thread */ pthread_t _mysock_create_thread(void *(*start)(void *args), void *args, bool_t create_detached) { pthread_t thread_id; assert(start); PTHREAD_CALL(pthread_create(&thread_id, NULL, start, args)); if (create_detached) PTHREAD_CALL(pthread_detach(thread_id)); return thread_id; }
/* transport layer thread; transport_init() should not return until the * transport layer finishes (i.e. the connection is over). */ static void *transport_thread_func(void *arg_ptr) { mysock_context_t *ctx = (mysock_context_t *) arg_ptr; char eof_packet; assert(ctx); ASSERT_VALID_MYSOCKET_DESCRIPTOR(ctx, ctx->my_sd); /* enter the STCP control loop. transport_init() doesn't return until the * connection's finished. that function should first signal establishment * of the connection after SYN/SYN-ACK (or an error condition if the * connection couldn't be established) to the application by using * stcp_unblock_application(); as the name suggests, this unblocks the * calling code. transport_init() then handles the connection, * returning only after the connection is closed. */ transport_init(ctx->my_sd, ctx->is_active); /* transport_init() has returned; both sides have closed the connection, * do some final cleanup here... */ PTHREAD_CALL(pthread_mutex_lock(&ctx->blocking_lock)); if (ctx->blocking) { /* if we're still blocked, STCP must not have indicated the * connection completed. pass the error up to the application. */ if (errno == 0 || errno == EINTR) { /* this is a bit of a kludge--this should really be set by STCP * itself, but it's a reasonable guess if for some reason (e.g. * oversight) the transport layer hasn't announced why it * bailed out... */ errno = (ctx->is_active) ? ECONNREFUSED : ECONNABORTED; } PTHREAD_CALL(pthread_mutex_unlock(&ctx->blocking_lock)); stcp_unblock_application(ctx->my_sd); } else { PTHREAD_CALL(pthread_mutex_unlock(&ctx->blocking_lock)); } /* force final myread() to return 0 bytes (this should have been done * by the transport layer already in response to the peer's FIN). */ _mysock_enqueue_buffer(ctx, &ctx->app_send_queue, &eof_packet, 0); return NULL; }
int _mysock_wait_for_connection(mysock_context_t *ctx) { assert(ctx); /* block until we either connect to the peer, or hit an error */ PTHREAD_CALL(pthread_mutex_lock(&ctx->blocking_lock)); while (ctx->blocking) { PTHREAD_CALL(pthread_cond_wait(&ctx->blocking_cond, &ctx->blocking_lock)); } PTHREAD_CALL(pthread_mutex_unlock(&ctx->blocking_lock)); return (errno = ctx->stcp_errno) ? -1 : 0; }
/* called by mylisten() to specify the number of pending connection * requests permitted for a listening socket. a backlog of zero * specifies at most one pending connection is permitted for the socket. */ void _mysock_set_backlog(mysock_context_t *ctx, unsigned int backlog) { unsigned int k, max_len = backlog + 1; uint16_t local_port; listen_queue_t *q; assert(ctx && ctx->listening && ctx->bound); local_port = ntohs(_network_get_port(&ctx->network_state)); assert(local_port > 0); PTHREAD_CALL(pthread_rwlock_wrlock(&listen_lock)); if ((q = _get_connection_queue(ctx)) == NULL) { /* first backlog specified for new listening socket */ DEBUG_LOG(("allocating connection queue for local port %hu\n", local_port)); q = (listen_queue_t *) calloc(1, sizeof(listen_queue_t)); assert(q); q->local_port = local_port; PTHREAD_CALL(pthread_cond_init(&q->connection_cond, NULL)); PTHREAD_CALL(pthread_mutex_init(&q->connection_lock, NULL)); HASH_INSERT(listen_table, ctx->my_sd, q); } assert(q); assert(q->local_port == local_port); if (max_len > q->max_len) { q->connection_queue = (connect_request_t *) realloc(q->connection_queue, max_len * sizeof(connect_request_t)); assert(q->connection_queue); memset(q->connection_queue + q->max_len, 0, (max_len - q->max_len) * sizeof(connect_request_t)); } for (k = q->max_len; k < max_len; ++k) q->connection_queue[k].sd = -1; q->max_len = max_len; PTHREAD_CALL(pthread_rwlock_unlock(&listen_lock)); }
void _mysock_passive_connection_complete(mysock_context_t *ctx) { listen_queue_t *q; assert(ctx); PTHREAD_CALL(pthread_rwlock_rdlock(&listen_lock)); assert(ctx->listen_sd >= 0); if ((q = _get_connection_queue(_mysock_get_context(ctx->listen_sd)))) { completed_connect_t *tail, *new_entry; connect_request_t *connection_req = NULL; unsigned int k; /* find this connection in the incomplete connection queue */ for (k = 0; k < q->max_len && !connection_req; ++k) { if (q->connection_queue[k].sd == ctx->my_sd) connection_req = &q->connection_queue[k]; } assert(connection_req); new_entry = (completed_connect_t *)malloc(sizeof(completed_connect_t)); assert(new_entry); new_entry->request = connection_req; new_entry->next = NULL; PTHREAD_CALL(pthread_mutex_lock(&q->connection_lock)); /* add established connection to tail of completed connection queue */ for (tail = q->completed_queue; tail && tail->next; tail = tail->next) ; if (tail) tail->next = new_entry; else q->completed_queue = new_entry; PTHREAD_CALL(pthread_mutex_unlock(&q->connection_lock)); PTHREAD_CALL(pthread_cond_signal(&q->connection_cond)); } PTHREAD_CALL(pthread_rwlock_unlock(&listen_lock)); }
/* called by myaccept() to grab the first completed connection off the * given mysocket's connection queue, or block until one completes. */ void _mysock_dequeue_connection(mysock_context_t *accept_ctx, mysock_context_t **new_ctx) { listen_queue_t *q; completed_connect_t *r; assert(accept_ctx && new_ctx); assert(accept_ctx->listening && accept_ctx->bound); DEBUG_LOG(("waiting for new connection...\n")); PTHREAD_CALL(pthread_rwlock_rdlock(&listen_lock)); q = _get_connection_queue(accept_ctx); assert(q); PTHREAD_CALL(pthread_mutex_lock(&q->connection_lock)); while (!q->completed_queue) { PTHREAD_CALL(pthread_cond_wait(&q->connection_cond, &q->connection_lock)); } r = q->completed_queue; q->completed_queue = q->completed_queue->next; DEBUG_LOG(("dequeueing established connection from %s:%hu\n", inet_ntoa(((struct sockaddr_in *) &r->request->peer_addr)->sin_addr), ntohs(((struct sockaddr_in *) &r->request->peer_addr)->sin_port))); assert(r->request); *new_ctx = _mysock_get_context(r->request->sd); assert(*new_ctx); /* free up this entry from the listen queue */ INVALIDATE_CONNECT_REQUEST(r->request); memset(r, 0, sizeof(*r)); free(r); assert(q->cur_len > 0); --q->cur_len; PTHREAD_CALL(pthread_mutex_unlock(&q->connection_lock)); PTHREAD_CALL(pthread_rwlock_unlock(&listen_lock)); }
/* TODO: pass in errno as argument. several libc calls on Solaris set this * even on successful operation. */ void stcp_unblock_application(mysocket_t sd) { mysock_context_t *ctx = _mysock_get_context(sd); /* pthread_mutex_lock sometimes sets errno even on successful operation */ int stcp_errno = errno; PTHREAD_CALL(pthread_mutex_lock(&ctx->blocking_lock)); assert(ctx->blocking); ctx->blocking = FALSE; if ((ctx->stcp_errno = stcp_errno) == EINTR) ctx->stcp_errno = 0; PTHREAD_CALL(pthread_mutex_unlock(&ctx->blocking_lock)); PTHREAD_CALL(pthread_cond_signal(&ctx->blocking_cond)); if (!ctx->is_active) { /* move from incomplete to completed connection queue */ _mysock_passive_connection_complete(ctx); } }
/* called by myclose() on a passive socket */ void _mysock_close_passive_socket(mysock_context_t *ctx) { listen_queue_t *q; assert(ctx && ctx->listening && ctx->bound); PTHREAD_CALL(pthread_rwlock_wrlock(&listen_lock)); if ((q = _get_connection_queue(ctx)) != NULL) { /* close any queued connections that haven't been passed up to the * user via myaccept()... */ unsigned int k; completed_connect_t *connect_iter; for (k = 0; k < q->max_len; ++k) { if (q->connection_queue[k].sd != -1) myclose(q->connection_queue[k].sd); } free(q->connection_queue); for (connect_iter = q->completed_queue; connect_iter; ) { /* note: socket was closed by the preceding loop */ completed_connect_t *next = connect_iter->next; free(connect_iter); connect_iter = next; } PTHREAD_CALL(pthread_cond_destroy(&q->connection_cond)); PTHREAD_CALL(pthread_mutex_destroy(&q->connection_lock)); HASH_DELETE(listen_table, ctx->my_sd); memset(q, 0, sizeof(*q)); free(q); } PTHREAD_CALL(pthread_rwlock_unlock(&listen_lock)); }
/* allocate a new connection context. this keeps track of the working state * between the transport and network layers for a particular connection. the * context is subsequently freed on the network layer's exit. */ static mysock_context_t *_mysock_allocate_context(void) { mysock_context_t *ctx = 0; ctx = (mysock_context_t *) calloc(1, sizeof(mysock_context_t)); assert(ctx); /* by default, sockets are active */ ctx->listen_sd = -1; /* initialise connection condition variable. this is signaled when the * connection is established, i.e. myconnect() or myaccept() should * unblock and return to the calling application. */ PTHREAD_CALL(pthread_cond_init(&ctx->blocking_cond, NULL)); PTHREAD_CALL(pthread_mutex_init(&ctx->blocking_lock, NULL)); /* initialise data ready condition variable. this is signaled when * data is ready from the application or the network. */ PTHREAD_CALL(pthread_cond_init(&ctx->data_ready_cond, NULL)); PTHREAD_CALL(pthread_mutex_init(&ctx->data_ready_lock, NULL)); ctx->blocking = TRUE; /* we unblock once we're connected */ /* initialise underlying network state. this includes creating the actual * socket used for communication to the peer--this is analogous to the * underlying raw IP socket used by a real TCP implementation. */ if (_network_init(ctx, &ctx->network_state) < 0) { _mysock_free_context(ctx); return NULL; } return ctx; }
/* close the given mysocket. note that the semantics of myclose() differ * slightly from a regular close(); STCP doesn't implement WAIT_TIME, so * myclose() simply discards all knowledge of the connection once the * connection is terminated. */ int myclose(mysocket_t sd) { mysock_context_t *ctx = _mysock_get_context(sd); DEBUG_LOG(("***myclose(%d)***\n", sd)); MYSOCK_CHECK(ctx != NULL, EBADF); /* stcp_wait_for_event() needs to wake up on a socket close request */ PTHREAD_CALL(pthread_mutex_lock(&ctx->data_ready_lock)); ctx->close_requested = TRUE; PTHREAD_CALL(pthread_mutex_unlock(&ctx->data_ready_lock)); PTHREAD_CALL(pthread_cond_broadcast(&ctx->data_ready_cond)); /* block until STCP thread exits */ if (ctx->transport_thread_started) { assert(!ctx->listening); assert(ctx->is_active || ctx->listen_sd != -1); PTHREAD_CALL(pthread_join(ctx->transport_thread, NULL)); ctx->transport_thread_started = FALSE; } _network_stop_recv_thread(ctx); if (ctx->listening) { /* remove entry from SYN demultiplexing table */ _mysock_close_passive_socket(ctx); } /* free all resources associated with this mysocket */ _mysock_free_context(ctx); DEBUG_LOG(("myclose(%d) returning...\n", sd)); return 0; }
/* add an incoming buffer (packet) to a queue for this connection; it will be * dequeued by stcp_network_recv() or myread() when the transport layer or * application is ready to use it, depending on the queue to which * the buffer (or packet) is added. * * in the interest of simplicity (and since we aren't writing a high * performance TCP stack), this just copies the specified buffer for its own * use, so the calling code can do whatever it wants with the packet * afterwards. the copied buffer is freed later by dequeue_buffer(). */ void _mysock_enqueue_buffer(mysock_context_t *ctx, packet_queue_t *pq, const void *packet, size_t packet_len) { packet_queue_node_t *node; assert(ctx && pq && (packet || !packet_len)); node = (packet_queue_node_t *) calloc(1, sizeof(packet_queue_node_t)); assert(node); node->data = (char *) malloc(packet_len * sizeof(char)); assert(node->data); if (packet_len > 0) memcpy(node->data, packet, packet_len); node->data_len = packet_len; PTHREAD_CALL(pthread_mutex_lock(&ctx->data_ready_lock)); if (!pq->head) { assert(!pq->tail); pq->head = pq->tail = node; } else { assert(pq->tail); assert(!pq->tail->next); assert(!node->next); pq->tail->next = node; pq->tail = node; } PTHREAD_CALL(pthread_mutex_unlock(&ctx->data_ready_lock)); PTHREAD_CALL(pthread_cond_broadcast(&ctx->data_ready_cond)); }
void _network_close(network_context_t *ctx) { network_context_socket_tcp_t *tcp_io_ctx; assert(ctx); tcp_io_ctx = (network_context_socket_tcp_t *) ctx->impl_data; assert(tcp_io_ctx); if (tcp_io_ctx->new_socket != -1) { DEBUG_LOG(("closing TCP network layer socket %d...\n", (int) tcp_io_ctx->new_socket)); closesocket(tcp_io_ctx->new_socket); } PTHREAD_CALL(pthread_mutex_destroy(&tcp_io_ctx->connect_lock)); _network_close_socket(ctx); }
/* initialise the network subsystem. this function should be called before * making use of any of the other network layer functions. */ int _network_init(mysock_context_t *sock_ctx, network_context_t *net_ctx) { network_context_socket_tcp_t *tcp_io_ctx; int rc; assert(sock_ctx && net_ctx); if ((rc = _network_init_socket(sock_ctx, net_ctx, SOCK_STREAM, sizeof(network_context_socket_tcp_t))) < 0) return rc; tcp_io_ctx = (network_context_socket_tcp_t *) net_ctx->impl_data; assert(tcp_io_ctx); tcp_io_ctx->sock_ctx = sock_ctx; tcp_io_ctx->new_socket = -1; tcp_io_ctx->connected = FALSE; PTHREAD_CALL(pthread_mutex_init(&tcp_io_ctx->connect_lock, NULL)); return 0; }
/* called by the transport layer to wait for new data, either from the network * or from the application, or for the application to request that the * mysocket be closed, depending on the value of flags. abstime is the * absolute time at which the function should quit waiting; if NULL, it blocks * indefinitely until data arrives. * * sd is the mysocket descriptor for the connection of interest. * * returns bit vector corresponding to application/network data being ready, * of the same format as the flags passed (see the enum in stcp_api.h). */ unsigned int stcp_wait_for_event(mysocket_t sd, unsigned int flags, const struct timespec *abstime) { unsigned int rc = 0; mysock_context_t *ctx = _mysock_get_context(sd); PTHREAD_CALL(pthread_mutex_lock(&ctx->data_ready_lock)); for (;;) { if ((flags & APP_DATA) && (ctx->app_recv_queue.head != NULL)) rc |= APP_DATA; if ((flags & NETWORK_DATA) && (ctx->network_recv_queue.head != NULL)) rc |= NETWORK_DATA; if (/*(flags & APP_CLOSE_REQUESTED) &&*/ ctx->close_requested && (ctx->app_recv_queue.head == NULL)) { /* we should only wake up on this event once. also, we don't * pass the close event down to STCP until we've already passed * it all outstanding data from the app. */ ctx->close_requested = FALSE; rc |= APP_CLOSE_REQUESTED; } if (rc) break; if (abstime) { /* wait with timeout */ switch (pthread_cond_timedwait(&ctx->data_ready_cond, &ctx->data_ready_lock, abstime)) { case 0: /* some data might be available */ case EINTR: break; case ETIMEDOUT: /* no data arrived in the specified time */ goto done; case EINVAL: assert(0); break; default: assert(0); break; } } else { /* block indefinitely */ PTHREAD_CALL(pthread_cond_wait(&ctx->data_ready_cond, &ctx->data_ready_lock)); } } done: PTHREAD_CALL(pthread_mutex_unlock(&ctx->data_ready_lock)); return rc; }
/* new connection requests for the given mysocket are queued to the * corresponding listen queue if one exists and there's sufficient * space, or dropped otherwise. ctx is the context associated with * a mysocket for which myaccept() will be called (i.e., a listening * socket). * * returns TRUE if the new connection has been queued, FALSE otherwise. */ bool_t _mysock_enqueue_connection(mysock_context_t *ctx, const void *packet, size_t packet_len, const struct sockaddr *peer_addr, int peer_addr_len, void *user_data) { listen_queue_t *q; connect_request_t *queue_entry = NULL; unsigned int k; assert(ctx && ctx->listening && ctx->bound); assert(packet && peer_addr); assert(peer_addr_len > 0); #define DEBUG_CONNECTION_MSG(msg, reason) \ _debug_print_connection(msg, reason, ctx, peer_addr) PTHREAD_CALL(pthread_rwlock_rdlock(&listen_lock)); if (packet_len < sizeof(struct tcphdr) || !(((struct tcphdr *) packet)->th_flags & TH_SYN)) { DEBUG_CONNECTION_MSG("received non-SYN packet", "(ignoring)"); goto done; /* not a connection setup request */ } if (!(q = _get_connection_queue(ctx))) { DEBUG_CONNECTION_MSG("dropping SYN packet", "(socket not listening)"); goto done; /* the socket was closed or not listening */ } /* see if this is a retransmission of an existing request */ for (k = 0; k < q->max_len; ++k) { connect_request_t *r = &q->connection_queue[k]; assert(r->sd == -1 || peer_addr_len == r->peer_addr_len); /* both are sockaddr_in */ if (!memcmp(&r->peer_addr, peer_addr, peer_addr_len)) { DEBUG_CONNECTION_MSG("dropping SYN packet", "(retransmission of queued request)"); goto done; /* retransmission */ } } /* if it's not a retransmission, find an empty slot in the incomplete * connection table */ if (q->cur_len < q->max_len) { for (k = 0; k < q->max_len && !queue_entry; ++k) { if (q->connection_queue[k].sd < 0) queue_entry = &q->connection_queue[k]; } assert(queue_entry); ++q->cur_len; } if (queue_entry) { mysock_context_t *new_ctx; /* establish the connection */ assert(queue_entry->sd == -1); if ((queue_entry->sd = _mysock_new_mysocket(ctx->network_state.is_reliable)) < 0) { DEBUG_CONNECTION_MSG("dropping SYN packet", "(couldn't allocate new mysocket)"); INVALIDATE_CONNECT_REQUEST(queue_entry); --q->cur_len; queue_entry = NULL; goto done; } new_ctx = _mysock_get_context(queue_entry->sd); new_ctx->listen_sd = ctx->my_sd; new_ctx->network_state.peer_addr = *peer_addr; new_ctx->network_state.peer_addr_len = peer_addr_len; new_ctx->network_state.peer_addr_valid = TRUE; queue_entry->peer_addr = *peer_addr; queue_entry->peer_addr_len = peer_addr_len; queue_entry->user_data = (void *) user_data; DEBUG_CONNECTION_MSG("establishing connection", ""); /* update any additional network layer state based on the initial * packet, e.g. remapped sequence numbers, etc. */ _network_update_passive_state(&new_ctx->network_state, &ctx->network_state, user_data, packet, packet_len); _mysock_transport_init(queue_entry->sd, FALSE); /* pass the SYN packet on to the main STCP code */ _mysock_enqueue_buffer(new_ctx, &new_ctx->network_recv_queue, packet, packet_len); } else { /* the packet is dropped (maximum backlog reached) */ DEBUG_CONNECTION_MSG("dropping SYN packet", "(queue full)"); } done: PTHREAD_CALL(pthread_rwlock_unlock(&listen_lock)); return (queue_entry != NULL); #undef DEBUG_CONNECTION_MSG }