int SPDYF_openssl_new_session(struct SPDY_Session *session) { int ret; if(NULL == (session->io_context = SSL_new(session->daemon->io_context))) { SPDYF_DEBUG("Couldn't create ssl structure"); return SPDY_NO; } if(1 != (ret = SSL_set_fd(session->io_context, session->socket_fd))) { SPDYF_DEBUG("SSL_set_fd %i",ret); SSL_free(session->io_context); session->io_context = NULL; return SPDY_NO; } //for non-blocking I/O SSL_accept may return -1 //and this function won't work if(1 != (ret = SSL_accept(session->io_context))) { SPDYF_DEBUG("SSL_accept %i",ret); SSL_free(session->io_context); session->io_context = NULL; return SPDY_NO; } /* alternatively SSL_set_accept_state(session->io_context); * may be called and then the negotiation will be done on reading */ return SPDY_YES; }
int SPDYF_openssl_init(struct SPDY_Daemon *daemon) { int options; //create ssl context. TLSv1 used if(NULL == (daemon->io_context = SSL_CTX_new(TLSv1_server_method()))) { SPDYF_DEBUG("Couldn't create ssl context"); return SPDY_NO; } //set options for tls //TODO DH is not enabled for easier debugging //SSL_CTX_set_options(daemon->io_context, SSL_OP_SINGLE_DH_USE); //TODO here session tickets are disabled for easier debuging with //wireshark when using Chrome // SSL_OP_NO_COMPRESSION disables TLS compression to avoid CRIME attack options = SSL_OP_NO_TICKET; #ifdef SSL_OP_NO_COMPRESSION options |= SSL_OP_NO_COMPRESSION; #elif OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */ sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); #endif SSL_CTX_set_options(daemon->io_context, options); if(1 != SSL_CTX_use_certificate_file(daemon->io_context, daemon->certfile , SSL_FILETYPE_PEM)) { SPDYF_DEBUG("Couldn't load the cert file"); SSL_CTX_free(daemon->io_context); return SPDY_NO; } if(1 != SSL_CTX_use_PrivateKey_file(daemon->io_context, daemon->keyfile, SSL_FILETYPE_PEM)) { SPDYF_DEBUG("Couldn't load the name file"); SSL_CTX_free(daemon->io_context); return SPDY_NO; } SSL_CTX_set_next_protos_advertised_cb(daemon->io_context, &spdyf_next_protos_advertised_cb, NULL); //TODO only RC4-SHA is used to make it easy to debug with wireshark if (1 != SSL_CTX_set_cipher_list(daemon->io_context, "RC4-SHA")) { SPDYF_DEBUG("Couldn't set the desired cipher list"); SSL_CTX_free(daemon->io_context); return SPDY_NO; } return SPDY_YES; }
void SPDY_destroy_request (struct SPDY_Request *request) { if(NULL == request) { SPDYF_DEBUG("request is NULL"); return; } //strings into request struct are just references to strings in //headers, so no need to free them twice SPDY_name_value_destroy(request->headers); free(request); }
int SPDYF_raw_new_session(struct SPDY_Session *session) { int fd_flags; int val = 1; int ret; //setting the socket to be non-blocking fd_flags = fcntl (session->socket_fd, F_GETFL); if ( -1 == fd_flags || 0 != fcntl (session->socket_fd, F_SETFL, fd_flags | O_NONBLOCK)) SPDYF_DEBUG("WARNING: Couldn't set the new connection to be non-blocking"); if(SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags) { ret = setsockopt(session->socket_fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); if(-1 == ret) SPDYF_DEBUG("WARNING: Couldn't set the new connection to TCP_NODELAY"); } return SPDY_YES; }
int SPDYF_raw_after_write(struct SPDY_Session *session, int was_written) { #if HAVE_DECL_TCP_CORK if(SPDY_YES == was_written && 0 == (SPDY_DAEMON_FLAG_NO_DELAY & session->daemon->flags)) { int val = 0; int ret; ret = setsockopt(session->socket_fd, IPPROTO_TCP, TCP_CORK, &val, (socklen_t)sizeof(val)); if(-1 == ret) SPDYF_DEBUG("WARNING: Couldn't unset the new connection to TCP_CORK"); } #endif return was_written; }
/** * Handler for reading the full SYN_STREAM frame after we know that * the frame is such. * The function waits for the full frame and then changes status * of the session. New stream is created. * * @param session SPDY_Session whose read buffer is used. */ static void spdyf_handler_read_syn_stream (struct SPDY_Session *session) { size_t name_value_strm_size = 0; unsigned int compressed_data_size; int ret; void *name_value_strm = NULL; struct SPDYF_Control_Frame *frame; struct SPDY_NameValue *headers; SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status, "the function is called wrong"); frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; //handle subheaders if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status) { if(0 == frame->length) { //protocol error: incomplete frame //we just ignore it since there is no stream id for which to //send RST_STREAM //TODO maybe GOAWAY and closing session is appropriate SPDYF_DEBUG("zero long SYN_STREAM received"); session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(frame); return; } if(SPDY_YES != SPDYF_stream_new(session)) { /* waiting for some more fields to create new stream or something went wrong, SPDYF_stream_new has handled the situation */ return; } session->current_stream_id = session->streams_head->stream_id; if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE) { //TODO no need to create stream if this happens session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; return; } else session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY; } //handle body //start reading the compressed name/value pairs (http headers) compressed_data_size = frame->length //everything after length field - 10;//4B stream id, 4B assoc strem id, 2B priority, unused and slot if(session->read_buffer_offset - session->read_buffer_beginning < compressed_data_size) { // the full frame is not yet here, try later return; } if(compressed_data_size > 0 && SPDY_YES != SPDYF_zlib_inflate(&session->zlib_recv_stream, session->read_buffer + session->read_buffer_beginning, compressed_data_size, &name_value_strm, &name_value_strm_size)) { /* something went wrong on inflating, * the state of the stream for decompression is unknown * and we may not be able to read anything more received on * this session, * so it is better to close the session */ free(name_value_strm); free(frame); /* mark the session for closing and close it, when * everything on the output queue is already written */ session->status = SPDY_SESSION_STATUS_FLUSHING; SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_INTERNAL_ERROR, false); return; } if(0 == name_value_strm_size || 0 == compressed_data_size) { //Protocol error: send RST_STREAM if(SPDY_YES != SPDYF_prepare_rst_stream(session, session->streams_head, SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR)) { //no memory, try later to send RST return; } } else { ret = SPDYF_name_value_from_stream(name_value_strm, name_value_strm_size, &headers); if(SPDY_NO == ret) { //memory error, try later free(name_value_strm); return; } session->streams_head->headers = headers; //inform the application layer for the new stream received if(SPDY_YES != session->daemon->fnew_stream_cb(session->daemon->fcls, session->streams_head)) { //memory error, try later free(name_value_strm); return; } session->read_buffer_beginning += compressed_data_size; free(name_value_strm); } //SPDYF_DEBUG("syn_stream received: id %i", session->current_stream_id); //change state to wait for new frame session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(frame); }
/** * Handler for reading DATA frames. In requests they are used for POST * arguments. * * @param session SPDY_Session whose read buffer is used. */ static void spdyf_handler_read_data (struct SPDY_Session *session) { int ret; struct SPDYF_Data_Frame * frame; struct SPDYF_Stream * stream; SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status || SPDY_SESSION_STATUS_WAIT_FOR_BODY == session->status, "the function is called wrong"); //SPDYF_DEBUG("DATA frame received (POST?). Ignoring"); //SPDYF_SIGINT(""); frame = (struct SPDYF_Data_Frame *)session->frame_handler_cls; //handle subheaders if(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status) { if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE) { session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; return; } else session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY; } //handle body if(session->read_buffer_offset - session->read_buffer_beginning >= frame->length) { stream = SPDYF_stream_find(frame->stream_id, session); if(NULL == stream || stream->is_in_closed || NULL == session->daemon->received_data_cb) { if(NULL == session->daemon->received_data_cb) SPDYF_DEBUG("No callback for DATA frame set; Ignoring DATA frame!"); //TODO send error? //TODO for now ignore frame session->read_buffer_beginning += frame->length; session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(frame); return; } ret = session->daemon->freceived_data_cb(session->daemon->cls, stream, session->read_buffer + session->read_buffer_beginning, frame->length, 0 == (SPDY_DATA_FLAG_FIN & frame->flags)); session->read_buffer_beginning += frame->length; stream->window_size -= frame->length; //TODO close in and send rst maybe SPDYF_ASSERT(SPDY_YES == ret, "Cancel POST data is not yet implemented"); if(SPDY_DATA_FLAG_FIN & frame->flags) { stream->is_in_closed = true; } else if(stream->window_size < SPDYF_INITIAL_WINDOW_SIZE / 2) { //very simple implementation of flow control //when the window's size is under the half of the initial value, //increase it again up to the initial value //prepare WINDOW_UPDATE if(SPDY_YES == SPDYF_prepare_window_update(session, stream, SPDYF_INITIAL_WINDOW_SIZE - stream->window_size)) { stream->window_size = SPDYF_INITIAL_WINDOW_SIZE; } //else: do it later } //SPDYF_DEBUG("data received: id %i", frame->stream_id); session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(frame); } }
/** * Handler for reading RST_STREAM frames. After receiving the frame * the stream moves into closed state and status * of the session is changed. Frames, belonging to this stream, which * are still at the output queue, will be ignored later. * * @param session SPDY_Session whose read buffer is used. */ static void spdyf_handler_read_rst_stream (struct SPDY_Session *session) { struct SPDYF_Control_Frame *frame; uint32_t stream_id; int32_t status_int; //enum SPDY_RST_STREAM_STATUS status; //for debug struct SPDYF_Stream *stream; SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status, "the function is called wrong"); frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; if(0 != frame->flags || 8 != frame->length) { //this is a protocol error SPDYF_DEBUG("wrong RST_STREAM received"); //ignore as a large frame session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; return; } if((session->read_buffer_offset - session->read_buffer_beginning) < frame->length) { //not all fields are received //try later return; } memcpy(&stream_id, session->read_buffer + session->read_buffer_beginning, 4); stream_id = NTOH31(stream_id); session->read_buffer_beginning += 4; memcpy(&status_int, session->read_buffer + session->read_buffer_beginning, 4); //status = ntohl(status_int); //for debug session->read_buffer_beginning += 4; session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(frame); //mark the stream as closed stream = session->streams_head; while(NULL != stream) { if(stream_id == stream->stream_id) { stream->is_in_closed = true; stream->is_out_closed = true; break; } stream = stream->next; } //SPDYF_DEBUG("Received RST_STREAM; status=%i; id=%i",status,stream_id); //do something according to the status //TODO /*switch(status) { case SPDY_RST_STREAM_STATUS_PROTOCOL_ERROR: break; }*/ }
/** * Handler for reading the GOAWAY frame after we know that * the frame is such. * The function waits for the full frame and then changes status * of the session. * * @param session SPDY_Session whose read buffer is used. */ static void spdyf_handler_read_goaway (struct SPDY_Session *session) { struct SPDYF_Control_Frame *frame; uint32_t last_good_stream_id; uint32_t status_int; enum SPDY_GOAWAY_STATUS status; SPDYF_ASSERT(SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER == session->status, "the function is called wrong"); frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; if(frame->length > SPDY_MAX_SUPPORTED_FRAME_SIZE) { //this is a protocol error/attack session->status = SPDY_SESSION_STATUS_IGNORE_BYTES; return; } if(0 != frame->flags || 8 != frame->length) { //this is a protocol error SPDYF_DEBUG("wrong GOAWAY received"); //anyway, it will be handled } if((session->read_buffer_offset - session->read_buffer_beginning) < frame->length) { //not all fields are received //try later return; } //mark that the session is almost closed session->is_goaway_received = true; if(8 == frame->length) { memcpy(&last_good_stream_id, session->read_buffer + session->read_buffer_beginning, 4); last_good_stream_id = NTOH31(last_good_stream_id); session->read_buffer_beginning += 4; memcpy(&status_int, session->read_buffer + session->read_buffer_beginning, 4); status = ntohl(status_int); session->read_buffer_beginning += 4; //TODO do something with last_good //SPDYF_DEBUG("Received GOAWAY; status=%i; lastgood=%i",status,last_good_stream_id); //do something according to the status //TODO switch(status) { case SPDY_GOAWAY_STATUS_OK: break; case SPDY_GOAWAY_STATUS_PROTOCOL_ERROR: break; case SPDY_GOAWAY_STATUS_INTERNAL_ERROR: break; } //SPDYF_DEBUG("goaway received: status %i", status); } session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(frame); }
int SPDYF_session_idle (struct SPDY_Session *session) { size_t read_buffer_beginning; size_t frame_length; struct SPDYF_Control_Frame* control_frame; struct SPDYF_Data_Frame *data_frame; //prepare session for closing if timeout is used and already passed if(SPDY_SESSION_STATUS_CLOSING != session->status && session->daemon->session_timeout && (session->last_activity + session->daemon->session_timeout < SPDYF_monotonic_time())) { session->status = SPDY_SESSION_STATUS_CLOSING; //best effort for sending GOAWAY SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true); SPDYF_session_write(session,true); } switch(session->status) { //expect new frame to arrive case SPDY_SESSION_STATUS_WAIT_FOR_HEADER: session->current_stream_id = 0; //check if the whole frame header is already here //both frame types have the same length if(session->read_buffer_offset - session->read_buffer_beginning < sizeof(struct SPDYF_Control_Frame)) return SPDY_NO; /* check the first bit to see if it is data or control frame * and also if the version is supported */ if(0x80 == *(uint8_t *)(session->read_buffer + session->read_buffer_beginning) && SPDY_VERSION == *((uint8_t *)session->read_buffer + session->read_buffer_beginning + 1)) { //control frame if(NULL == (control_frame = malloc(sizeof(struct SPDYF_Control_Frame)))) { SPDYF_DEBUG("No memory"); return SPDY_NO; } //get frame headers memcpy(control_frame, session->read_buffer + session->read_buffer_beginning, sizeof(struct SPDYF_Control_Frame)); session->read_buffer_beginning += sizeof(struct SPDYF_Control_Frame); SPDYF_CONTROL_FRAME_NTOH(control_frame); session->status = SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER; //assign different frame handler according to frame type switch(control_frame->type){ case SPDY_CONTROL_FRAME_TYPES_SYN_STREAM: session->frame_handler = &spdyf_handler_read_syn_stream; break; case SPDY_CONTROL_FRAME_TYPES_GOAWAY: session->frame_handler = &spdyf_handler_read_goaway; break; case SPDY_CONTROL_FRAME_TYPES_RST_STREAM: session->frame_handler = &spdyf_handler_read_rst_stream; break; default: session->frame_handler = &SPDYF_handler_ignore_frame; } session->frame_handler_cls = control_frame; //DO NOT break the outer case } else if(0 == *(uint8_t *)(session->read_buffer + session->read_buffer_beginning)) { //needed for POST //data frame if(NULL == (data_frame = malloc(sizeof(struct SPDYF_Data_Frame)))) { SPDYF_DEBUG("No memory"); return SPDY_NO; } //get frame headers memcpy(data_frame, session->read_buffer + session->read_buffer_beginning, sizeof(struct SPDYF_Data_Frame)); session->read_buffer_beginning += sizeof(struct SPDYF_Data_Frame); SPDYF_DATA_FRAME_NTOH(data_frame); session->status = SPDY_SESSION_STATUS_WAIT_FOR_BODY; session->frame_handler = &spdyf_handler_read_data; session->frame_handler_cls = data_frame; //DO NOT brake the outer case } else { SPDYF_DEBUG("another protocol or version received!"); /* According to the draft the lib should send here * RST_STREAM with status UNSUPPORTED_VERSION. I don't * see any sense of keeping the session open since * we don't know how many bytes is the bogus "frame". * And the latter normally will be HTTP request. * */ //shutdown(session->socket_fd, SHUT_RD); session->status = SPDY_SESSION_STATUS_FLUSHING; SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_PROTOCOL_ERROR,false); //SPDYF_session_write(session,false); /* close connection since the client expects another protocol from us */ //SPDYF_session_close(session); return SPDY_YES; } //expect specific header fields after the standard header case SPDY_SESSION_STATUS_WAIT_FOR_SUBHEADER: if(NULL!=session->frame_handler) { read_buffer_beginning = session->read_buffer_beginning; //if everything is ok, the "body" will also be processed //by the handler session->frame_handler(session); if(SPDY_SESSION_STATUS_IGNORE_BYTES == session->status) { //check for larger than max supported frame if(session->frame_handler != &spdyf_handler_read_data) { frame_length = ((struct SPDYF_Control_Frame *)session->frame_handler_cls)->length; } else { frame_length = ((struct SPDYF_Data_Frame *)session->frame_handler_cls)->length; } //if(SPDY_MAX_SUPPORTED_FRAME_SIZE < frame_length) { SPDYF_DEBUG("received frame with unsupported size: %zu", frame_length); //the data being received must be ignored and //RST_STREAM sent //ignore bytes that will arive later session->read_ignore_bytes = frame_length + read_buffer_beginning - session->read_buffer_offset; //ignore what is already in read buffer session->read_buffer_beginning = session->read_buffer_offset; SPDYF_prepare_rst_stream(session, session->current_stream_id > 0 ? session->streams_head : NULL, //may be 0 here which is not good SPDY_RST_STREAM_STATUS_FRAME_TOO_LARGE); //actually the read buffer can be bigger than the //max supported size session->status = session->read_ignore_bytes ? SPDY_SESSION_STATUS_IGNORE_BYTES : SPDY_SESSION_STATUS_WAIT_FOR_HEADER; free(session->frame_handler_cls); } } } if(SPDY_SESSION_STATUS_IGNORE_BYTES != session->status) { break; } //ignoring data in read buffer case SPDY_SESSION_STATUS_IGNORE_BYTES: SPDYF_ASSERT(session->read_ignore_bytes > 0, "Session is in wrong state"); if(session->read_ignore_bytes > session->read_buffer_offset - session->read_buffer_beginning) { session->read_ignore_bytes -= session->read_buffer_offset - session->read_buffer_beginning; session->read_buffer_beginning = session->read_buffer_offset; } else { session->read_buffer_beginning += session->read_ignore_bytes; session->read_ignore_bytes = 0; session->status = SPDY_SESSION_STATUS_WAIT_FOR_HEADER; } break; //expect frame body (name/value pairs) case SPDY_SESSION_STATUS_WAIT_FOR_BODY: if(NULL!=session->frame_handler) session->frame_handler(session); break; case SPDY_SESSION_STATUS_FLUSHING: return SPDY_NO; //because of error the session needs to be closed case SPDY_SESSION_STATUS_CLOSING: //error should be already sent to the client SPDYF_session_close(session); return SPDY_YES; } return SPDY_YES; }
int SPDYF_stream_new (struct SPDY_Session *session) { uint32_t stream_id; uint32_t assoc_stream_id; uint8_t priority; uint8_t slot; size_t buffer_pos = session->read_buffer_beginning; struct SPDYF_Stream *stream; struct SPDYF_Control_Frame *frame; if((session->read_buffer_offset - session->read_buffer_beginning) < 10) { //not all fields are received to create new stream return SPDY_NO; } frame = (struct SPDYF_Control_Frame *)session->frame_handler_cls; //get stream id of the new stream memcpy(&stream_id, session->read_buffer + session->read_buffer_beginning, 4); stream_id = NTOH31(stream_id); session->read_buffer_beginning += 4; if(stream_id <= session->last_in_stream_id || 0==(stream_id % 2)) { //wrong stream id sent by client //GOAWAY with PROTOCOL_ERROR MUST be sent //TODO //ignore frame session->frame_handler = &SPDYF_handler_ignore_frame; return SPDY_NO; } else if(session->is_goaway_sent) { //the client is not allowed to create new streams anymore //we MUST ignore the frame session->frame_handler = &SPDYF_handler_ignore_frame; return SPDY_NO; } //set highest stream id for session session->last_in_stream_id = stream_id; //get assoc stream id of the new stream //this value is used with SPDY PUSH, thus nothing to do with it here memcpy(&assoc_stream_id, session->read_buffer + session->read_buffer_beginning, 4); assoc_stream_id = NTOH31(assoc_stream_id); session->read_buffer_beginning += 4; //get stream priority (3 bits) //after it there are 5 bits that are not used priority = *(uint8_t *)(session->read_buffer + session->read_buffer_beginning) >> 5; session->read_buffer_beginning++; //get slot (see SPDY draft) slot = *(uint8_t *)(session->read_buffer + session->read_buffer_beginning); session->read_buffer_beginning++; if(NULL == (stream = malloc(sizeof(struct SPDYF_Stream)))) { SPDYF_DEBUG("No memory"); //revert buffer state session->read_buffer_beginning = buffer_pos; return SPDY_NO; } memset(stream,0, sizeof(struct SPDYF_Stream)); stream->session = session; stream->stream_id = stream_id; stream->assoc_stream_id = assoc_stream_id; stream->priority = priority; stream->slot = slot; stream->is_in_closed = (frame->flags & SPDY_SYN_STREAM_FLAG_FIN) != 0; stream->flag_unidirectional = (frame->flags & SPDY_SYN_STREAM_FLAG_UNIDIRECTIONAL) != 0; stream->is_out_closed = stream->flag_unidirectional; stream->is_server_initiator = false; stream->window_size = SPDYF_INITIAL_WINDOW_SIZE; //put the stream to the list of streams for the session DLL_insert(session->streams_head, session->streams_tail, stream); return SPDY_YES; }