/* fills in addr with current port associated with the mysocket descriptor. * like the regular getsockname(), this does not fill in the local IP * address unless it's known. */ int mygetsockname(mysocket_t sd, struct sockaddr *addr, socklen_t *addrlen) { mysock_context_t *ctx = _mysock_get_context(sd); assert(addr && addrlen); MYSOCK_CHECK(ctx != NULL, EBADF); MYSOCK_CHECK(addr != NULL && addrlen != NULL, EFAULT); *addr = ctx->network_state.local_addr; assert(!addr->sa_family || addr->sa_family == AF_INET); addr->sa_family = AF_INET; ((struct sockaddr_in *) addr)->sin_port = _network_get_port(&ctx->network_state); if (ctx->network_state.peer_addr_valid) { /* XXX: if local address has been bound, and local_addr is set, * we probably shouldn't override it here... although this * shouldn't really affect anything... */ ((struct sockaddr_in *) addr)->sin_addr.s_addr = _network_get_local_addr(&ctx->network_state); } return 0; }
/* initiate a new STCP connection; called by myconnect() and myaccept() */ void _mysock_transport_init(mysocket_t sd, bool_t is_active) { mysock_context_t *connection_context = _mysock_get_context(sd); assert(!connection_context->listening); connection_context->is_active = is_active; /* start a new network thread; this handles incoming data, passing it * up to the transport layer. (the network input is threaded so we can * keep track of timeouts/when data arrives, in a portable manner * independent of the underlying network I/O functionality). */ if (_network_start_recv_thread(connection_context) < 0) { assert(0); abort(); } /* start a new transport layer thread */ connection_context->transport_thread = _mysock_create_thread( transport_thread_func, connection_context, FALSE); connection_context->transport_thread_started = TRUE; }
/* in this implementation, mylisten() is assumed to follow mybind() */ int mylisten(mysocket_t sd, int backlog) { mysock_context_t *ctx = _mysock_get_context(sd); assert(ctx->bound); MYSOCK_CHECK(ctx != NULL, EBADF); MYSOCK_CHECK(ctx->bound, EINVAL); /* set up the socket for demultiplexing */ ctx->listening = TRUE; _mysock_set_backlog(ctx, backlog); if (_network_listen(&ctx->network_state, backlog) < 0) return -1; /* since we don't spawn an STCP worker thread for passive sockets * (there's no transport layer related work to do, so * _mysock_transport_init() is never called for such sockets), we * begin receiving network packets here... */ if (_network_start_recv_thread(ctx) < 0) { assert(0); return -1; } return 0; }
void stcp_fin_received(mysocket_t sd) { mysock_context_t *ctx = _mysock_get_context(sd); assert(ctx); DEBUG_LOG(("stcp_fin_received(%d): setting eof flag\n", sd)); _mysock_enqueue_buffer(ctx, &ctx->app_send_queue, NULL, 0); }
/* pass data up to the application for consumption by myread() */ void stcp_app_send(mysocket_t sd, const void *src, size_t src_len) { mysock_context_t *ctx = _mysock_get_context(sd); assert(ctx && src); if (src_len > 0) { DEBUG_LOG(("stcp_app_send(%d): sending %u bytes up to app\n", sd, src_len)); _mysock_enqueue_buffer(ctx, &ctx->app_send_queue, src, src_len); } }
/* receive data from the application (sent to us using mywrite()). * the call blocks until data is available. */ size_t stcp_app_recv(mysocket_t sd, void *dst, size_t max_len) { mysock_context_t *ctx = _mysock_get_context(sd); assert(ctx && dst); /* app may have passed in data of arbitrary length; all of it must be * passed down to the transport layer. if it doesn't fit in the specified * buffer, any left over is kept for the next call to app_recv(). */ return _mysock_dequeue_buffer(ctx, &ctx->app_recv_queue, dst, max_len, TRUE); }
int mywrite(mysocket_t sd, const void *buf, size_t buf_len) { mysock_context_t *ctx = _mysock_get_context(sd); MYSOCK_CHECK(ctx != NULL, EBADF); MYSOCK_CHECK(!ctx->listening, EINVAL); assert(!ctx->close_requested); _mysock_enqueue_buffer(ctx, &ctx->app_recv_queue, buf, buf_len); /* XXX: all bytes are queued, irrespective of current sender window */ return buf_len; }
int mygetpeername(mysocket_t sd, struct sockaddr *name, socklen_t *namelen) { mysock_context_t *ctx = _mysock_get_context(sd); assert(name && namelen); MYSOCK_CHECK(name != NULL && namelen != NULL, EFAULT); memcpy(name, &ctx->network_state.peer_addr, MIN(*namelen, (socklen_t)ctx->network_state.peer_addr_len)); MYSOCK_CHECK((*namelen = ctx->network_state.peer_addr_len) > 0, ENOTCONN); return 0; }
/* simply a wrapper around bind() */ int mybind(mysocket_t sd, struct sockaddr *addr, int addrlen) { mysock_context_t *ctx = _mysock_get_context(sd); assert(addr); MYSOCK_CHECK(ctx != NULL, EBADF); MYSOCK_CHECK(addr->sa_family == AF_INET, EADDRNOTAVAIL); /* mybind() must precede mylisten() */ assert(!ctx->listening); ctx->bound = TRUE; ctx->network_state.local_addr = *addr; return _network_bind(&ctx->network_state, addr, addrlen); }
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)); }
/* stcp_network_send() * * Send data (unreliably) to the peer. * * sd Mysocket descriptor * src A pointer to the data to send * src_len The length in bytes of the buffer * * This function takes data in multiple segments and sends them as a single * UDP datagram. The buffer and length parameters may be repeated an arbitrary * number of times; a NULL pointer signifies the end of buffer/length pairs. * For example: * * stcp_network_send(mysd, buf1, len1, buf2, len2, NULL); * * Unreliability is handled by a helper function (_network_send()); if we're * operating in unreliable mode, we decide in there whether to drop the * datagram or send it later. * * Returns the number of bytes transferred on success, or -1 on failure. * */ ssize_t stcp_network_send(mysocket_t sd, const void *src, size_t src_len, ...) { mysock_context_t *ctx = _mysock_get_context(sd); char packet[MAX_IP_PAYLOAD_LEN]; size_t packet_len; const void *next_buf; va_list argptr; struct tcphdr *header; assert(ctx && src); assert(src_len <= sizeof(packet)); memcpy(packet, src, src_len); packet_len = src_len; va_start(argptr, src_len); while ((next_buf = va_arg(argptr, const void *))) { size_t next_len = va_arg(argptr, size_t); assert(packet_len + next_len <= sizeof(packet)); memcpy(packet + packet_len, next_buf, next_len); packet_len += next_len; } va_end(argptr); /* fill in fields in the TCP header that aren't handled by students */ assert(packet_len >= sizeof(struct tcphdr)); header = (struct tcphdr *) packet; header->th_sport = _network_get_port(&ctx->network_state); /* N.B. assert(header->th_sport > 0) fires in the UDP SYN-ACK case */ assert(ctx->network_state.peer_addr.sa_family == AF_INET); header->th_dport = ((struct sockaddr_in *) &ctx->network_state.peer_addr)->sin_port; assert(header->th_dport > 0); header->th_sum = 0; /* set below */ header->th_urp = 0; /* ignored */ _mysock_set_checksum(ctx, packet, packet_len); return _network_send(sd, packet, packet_len); }
/* 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); } }
/* connect to the address specified in name on the mysocket sd */ int myconnect(mysocket_t sd, struct sockaddr *name, int namelen) { mysock_context_t *ctx = _mysock_get_context(sd); MYSOCK_CHECK(ctx != NULL, EINVAL); MYSOCK_CHECK((ctx->network_state.peer_addr_len == 0), EISCONN); #ifdef DEBUG struct sockaddr_in *sin = (struct sockaddr_in *) name; fprintf(stderr, "\n####Initiating a new connection to %s:%u#### (sd=%d)\n", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), sd); fflush(stderr); #endif /*DEBUG*/ ctx->network_state.peer_addr = *name; ctx->network_state.peer_addr_len = namelen; ctx->network_state.peer_addr_valid = TRUE; /* record connection setup for demultiplexing */ if (!ctx->bound) { int rc; /* we need to find the local port number to set up demultiplexing * before we send the SYN. (this is really only required in the VNS * case--we have to demultiplex only on listening sockets for the * UDP/TCP network layer--but it doesn't do any harm here in * general). */ if ((rc = _mysock_bind_ephemeral(ctx)) < 0) return rc; } /* time for kick off */ _mysock_transport_init(sd, TRUE); /* block until connection is established, or we hit an error */ return _mysock_wait_for_connection(ctx); }
int myread(mysocket_t sd, void *buf, size_t buf_len) { mysock_context_t *ctx = _mysock_get_context(sd); int len; MYSOCK_CHECK(ctx != NULL, EBADF); MYSOCK_CHECK(!ctx->listening, EINVAL); assert(!ctx->close_requested); if (ctx->eof) return 0; if ((len = _mysock_dequeue_buffer(ctx, &ctx->app_send_queue, buf, buf_len, TRUE)) == 0) { /* make sure repeated calls to myread() return 0 on EOF */ ctx->eof = TRUE; } return len; }
mysocket_t myaccept(mysocket_t sd, struct sockaddr *addr, int *addrlen) { mysock_context_t *accept_ctx = _mysock_get_context(sd); mysock_context_t *ctx; MYSOCK_CHECK(accept_ctx != NULL, EBADF); MYSOCK_CHECK(accept_ctx->listening, EINVAL); #ifdef DEBUG fprintf(stderr, "\n####Accepting a new connection at port# %hu#### " "(sd=%d)\n", ntohs(_network_get_port(&accept_ctx->network_state)), sd); fflush(stderr); #endif /*DEBUG*/ /* the new socket is created on an incoming SYN. block here until we * establish a connection, or STCP indicates an error condition. */ _mysock_dequeue_connection(accept_ctx, &ctx); assert(ctx); if (!ctx->stcp_errno) { /* fill in addr, addrlen with address of peer */ assert(ctx->network_state.peer_addr_len > 0); if (addr && addrlen) { *addr = ctx->network_state.peer_addr; *addrlen = ctx->network_state.peer_addr_len; } } assert(ctx->listen_sd == sd); DEBUG_LOG(("***myaccept(%d) returning new sd %d***\n", sd, ctx->my_sd)); return (errno = ctx->stcp_errno) ? -1 : ctx->my_sd; }
/* 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; }
/* allow STCP implementation to establish a context for a given mysocket * descriptor. this context should contain any information that needs to be * tracked for the given mysocket, e.g. sequence numbers, retransmission * timers, etc. it would typically be malloc()ed in transport_init() (or even * just allocated on the stack), and freed on connection close. */ void stcp_set_context(mysocket_t sd, const void *stcp_state) { mysock_context_t *ctx = _mysock_get_context(sd); ctx->stcp_state = (void *) stcp_state; }
/* 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 }
void *stcp_get_context(mysocket_t sd) { mysock_context_t *ctx = _mysock_get_context(sd); return ctx->stcp_state; }