/** * \brief This function handles node commands, when payload packet was received. * * This function also detects packet loss of previous not received payload * packets. All node commands are unpacked from the buffer and added to incoming queue. * * \param[in] *C The verse context. * * \return This function returns RECEIVE_PACKET_DELAYED, when delayd packet * was received, otherwise it returns RECEIVE_PACKET_SUCCESS. */ static int handle_node_commands(struct vContext *C) { struct VSession *vsession = CTX_current_session(C); struct VDgramConn *vconn = CTX_current_dgram_conn(C); struct VPacket *r_packet = CTX_r_packet(C); struct Ack_Nak_Cmd ack_nak_cmd; /* Note: when ACK and NAK command are add to the AckNak history, * then this vector of commands is automatically compressed. */ /* Was any packet lost since last receiving of packet? */ if(r_packet->header.payload_id > vconn->last_r_pay+1) { v_print_log(VRS_PRINT_DEBUG_MSG, "Packet(s) lost: %d - %d\n", vconn->last_r_pay+1, r_packet->header.payload_id-1); /* Add NAK command to the list of ACK NAK commands */ ack_nak_cmd.id = CMD_NAK_ID; ack_nak_cmd.pay_id = vconn->last_r_pay+1; v_ack_nak_history_add_cmd(&vconn->ack_nak, &ack_nak_cmd); /* Was some delayed packet received? */ } else if(r_packet->header.payload_id < vconn->last_r_pay+1) { if(is_log_level(VRS_PRINT_WARNING)) v_print_log(VRS_PRINT_WARNING, "Received unordered packet: %d, expected: %d\n", r_packet->header.payload_id, vconn->last_r_pay+1); /* Drop this packet */ return RECEIVE_PACKET_UNORDERED; } /* ADD ACK command to the list of ACK NAK commands */ ack_nak_cmd.id = CMD_ACK_ID; ack_nak_cmd.pay_id = r_packet->header.payload_id; v_ack_nak_history_add_cmd(&vconn->ack_nak, &ack_nak_cmd); /* Check if there are really node commands */ if(r_packet->data!=NULL) { /* Unpack node commands and put them to the queue of incoming commands */ v_cmd_unpack((char*)r_packet->data, r_packet->data_size, vsession->in_queue); } return RECEIVE_PACKET_SUCCESS; }
/** * \brief This function set size of receive window (flow control), which is sent * to the peer. * * This function sets C->dgram_conn->rwin_host * * \param[in] *C Verse context * */ static void set_host_rwin(struct vContext *C) { struct VDgramConn *dgram_conn = CTX_current_dgram_conn(C); struct VSession *vsession = CTX_current_session(C); long int free_space; switch(dgram_conn->fc_meth) { case FC_NONE: dgram_conn->rwin_host = 0xFFFFFFFF; break; case FC_TCP_LIKE: /* Compute free space in incomming queue */ free_space = (long int)vsession->in_queue->max_size - (long int)vsession->in_queue->size; dgram_conn->rwin_host = (uint32)(free_space > 0) ? free_space : 0; /*printf(">>> max_size: %d, size: %d -> free_size: %ld -> window %d<<<\n", vsession->in_queue->max_size, vsession->in_queue->size, free_space, dgram_conn->rwin_host);*/ break; case FC_RESERVED: v_print_log(VRS_PRINT_WARNING, "FC_RESERVED should never be used\n"); dgram_conn->rwin_host = 0xFFFFFFFF; break; } }
/** * \brief This function is called, when server is in NEGOTIATE newhost state */ int vs_NEGOTIATE_newhost_loop(struct vContext *C) { struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message = CTX_r_message(C); int i, buffer_pos=0; /* Reset content of received message */ memset(r_message, 0, sizeof(struct VMessage)); /* Unpack Verse message header */ buffer_pos += v_unpack_message_header(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); /* Unpack all system commands */ buffer_pos += v_unpack_message_system_commands(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); v_print_receive_message(C); /* Process all system commands */ for(i=0; i<MAX_SYSTEM_COMMAND_COUNT && r_message->sys_cmd[i].cmd.id!=CMD_RESERVED_ID; i++) { switch(r_message->sys_cmd[i].cmd.id) { case CMD_CONFIRM_L_ID: if(r_message->sys_cmd[i].negotiate_cmd.feature==FTR_HOST_URL) { if(r_message->sys_cmd[i].negotiate_cmd.count == 1) { if(vsession->host_url!=NULL && strcmp(vsession->host_url, (char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str) == 0) { return 1; } } } break; } } return 0; }
static int vs_RESPOND_krb_auth_loop(struct vContext *C, const char *u_name) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message = CTX_r_message(C); struct VMessage *s_message = CTX_s_message(C); int i, cmd_rank = 0, client_name_proposed = 0, client_version_proposed = 0; unsigned short buffer_pos = 0; int user_id; /* Reset content of received message */ memset(r_message, 0, sizeof(struct VMessage)); /* Unpack Verse message header */ buffer_pos += v_unpack_message_header(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); /* Unpack all system commands */ buffer_pos += v_unpack_message_system_commands(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); v_print_receive_message(C); for(i=0; i<MAX_SYSTEM_COMMAND_COUNT && r_message->sys_cmd[i].cmd.id!=CMD_RESERVED_ID; i++) { switch(r_message->sys_cmd[i].cmd.id) { case CMD_CHANGE_L_ID: /* Client could propose client name and version */ if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_CLIENT_NAME) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { /* Only first proposed client name will be used */ vsession->client_name = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); client_name_proposed = 1; } } else if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_CLIENT_VERSION) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { /* Only first proposed client name will be used */ vsession->client_version = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); client_version_proposed = 1; } } break; default: v_print_log(VRS_PRINT_WARNING, "This command id: %d is not supported in this state\n", r_message->sys_cmd[i].cmd.id); break; } } buffer_pos = VERSE_MESSAGE_HEADER_SIZE; /* Send confirmation about client name */ if (client_name_proposed == 1) { v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_NAME, vsession->client_name, NULL); } /* Send confirmation about client version only in situation, when * client proposed client name too */ if (client_version_proposed == 1) { if (client_name_proposed == 1) { v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_VERSION, vsession->client_version, NULL); } else { /* Client version without client name is not allowed */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_VERSION, NULL); } } /* Do user authentication */ if ((user_id = vs_krb_make_user(C, u_name)) != -1) { long int avatar_id; pthread_mutex_lock(&vs_ctx->data.mutex); avatar_id = vs_create_avatar_node(vs_ctx, vsession, user_id); pthread_mutex_unlock(&vs_ctx->data.mutex); if(avatar_id == -1) { v_print_log(VRS_PRINT_ERROR, "Failed to create avatar node\n"); return 0; } /* Save user_id to the session and send it in * connect_accept command */ vsession->user_id = user_id; vsession->avatar_id = avatar_id; s_message->sys_cmd[cmd_rank].ua_succ.id = CMD_USER_AUTH_SUCCESS; s_message->sys_cmd[cmd_rank].ua_succ.user_id = user_id; s_message->sys_cmd[cmd_rank].ua_succ.avatar_id = avatar_id; cmd_rank++; /* Generate random string for coockie */ vsession->peer_cookie.str = (char*)calloc((COOKIE_SIZE+1), sizeof(char)); for(i=0; i<COOKIE_SIZE; i++) { /* Generate only printable characters (debug prints) */ vsession->peer_cookie.str[i] = 32 + (char)((float)rand()*94.0/RAND_MAX); } vsession->peer_cookie.str[COOKIE_SIZE] = '\0'; /* Set up negotiate command of the host cookie */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CHANGE_R_ID, FTR_COOKIE, vsession->peer_cookie.str, NULL); /* Load DED from configuration and save it to the session */ vsession->ded.str = strdup(vs_ctx->ded); v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CHANGE_L_ID, FTR_DED, vsession->ded.str, NULL); buffer_pos += v_pack_stream_system_commands(s_message, &io_ctx->buf[buffer_pos]); s_message->header.len = io_ctx->buf_size = buffer_pos; s_message->header.version = VRS_VERSION; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); v_print_send_message(C); return 1; } else { s_message->sys_cmd[0].ua_fail.id = CMD_USER_AUTH_FAILURE; s_message->sys_cmd[0].ua_fail.count = 0; s_message->sys_cmd[1].cmd.id = CMD_RESERVED_ID; buffer_pos += v_pack_stream_system_commands(s_message, &io_ctx->buf[buffer_pos]); s_message->header.len = io_ctx->buf_size = buffer_pos; s_message->header.version = VRS_VERSION; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); v_print_send_message(C); return 1; } return 0; }
/** * \brief This function process received message and send respond to the client * if client sent CMD_USER_AUTH_REQUEST with method type UA_METHOD_NONE. The * response contains list of supported authentication methods (only Password * method is supported now) * \param[in] *C The pointer at Verse context * \return The function returns 1, when response was sent to the client * and it returns 0, when all needs were not meet or error occurred. */ int vs_RESPOND_methods_loop(struct vContext *C) { struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message = CTX_r_message(C); struct VMessage *s_message = CTX_s_message(C); int i, auth_req = 0, client_name_proposed = 0, client_version_proposed = 0; unsigned short buffer_pos = 0; /* Reset content of received message */ memset(r_message, 0, sizeof(struct VMessage)); /* Unpack Verse message header */ buffer_pos += v_unpack_message_header(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); /* Unpack all system commands */ buffer_pos += v_unpack_message_system_commands(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); v_print_receive_message(C); for(i=0; i<MAX_SYSTEM_COMMAND_COUNT && r_message->sys_cmd[i].cmd.id!=CMD_RESERVED_ID; i++) { switch(r_message->sys_cmd[i].cmd.id) { case CMD_USER_AUTH_REQUEST: if(r_message->sys_cmd[i].ua_req.method_type == VRS_UA_METHOD_NONE) { auth_req = 1; } else { v_print_log(VRS_PRINT_WARNING, "This auth method id: %d is not supported in this state\n", r_message->sys_cmd[i].ua_req.method_type); } break; case CMD_CHANGE_L_ID: /* Client could propose client name and version */ if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_CLIENT_NAME) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { /* Only first proposed client name will be used */ vsession->client_name = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); client_name_proposed = 1; } } else if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_CLIENT_VERSION) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { /* Only first proposed client name will be used */ vsession->client_version = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); client_version_proposed = 1; } } break; default: v_print_log(VRS_PRINT_WARNING, "This command id: %d is not supported in this state\n", r_message->sys_cmd[i].cmd.id); break; } } if(auth_req == 1) { int cmd_rank = 0; /* VRS_UA_METHOD_NONE is not supported method. Send list of * supported methods. Current implementation supports * only PASSWORD method now. */ s_message->sys_cmd[cmd_rank].ua_fail.id = CMD_USER_AUTH_FAILURE; /* List of supported methods */ s_message->sys_cmd[cmd_rank].ua_fail.count = 1; s_message->sys_cmd[cmd_rank].ua_fail.method[0] = VRS_UA_METHOD_PASSWORD; cmd_rank++; /* Send confirmation about client name */ if(client_name_proposed == 1) { v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_NAME, vsession->client_name, NULL); } /* Send confirmation about client version only in situation, when * client proposed client name too */ if(client_version_proposed == 1) { if(client_name_proposed == 1) { v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_VERSION, vsession->client_version, NULL); } else { /* Client version without client name is not allowed */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_VERSION, NULL); } } buffer_pos = VERSE_MESSAGE_HEADER_SIZE; /* Pack system commands to the buffer */ buffer_pos += v_pack_stream_system_commands(s_message,&io_ctx->buf[buffer_pos]); s_message->header.len = io_ctx->buf_size = buffer_pos; s_message->header.version = VRS_VERSION; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); /* Debug print of command to be send */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { v_print_send_message(C); } return 1; } return 0; }
int vs_RESPOND_userauth_loop(struct vContext *C) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message = CTX_r_message(C); struct VMessage *s_message = CTX_s_message(C); int i, cmd_rank = 0; unsigned short buffer_pos = 0; /* Reset content of received message */ memset(r_message, 0, sizeof(struct VMessage)); /* Unpack Verse message header */ buffer_pos += v_unpack_message_header(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); /* Unpack all system commands */ buffer_pos += v_unpack_message_system_commands(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); v_print_receive_message(C); for(i=0; i<MAX_SYSTEM_COMMAND_COUNT && r_message->sys_cmd[i].cmd.id!=CMD_RESERVED_ID; i++) { if(r_message->sys_cmd[i].cmd.id == CMD_USER_AUTH_REQUEST) { if(r_message->sys_cmd[i].ua_req.method_type == VRS_UA_METHOD_PASSWORD) { int user_id; /* Do user authentication */ if((user_id = vs_user_auth(C, r_message->sys_cmd[i].ua_req.username, r_message->sys_cmd[i].ua_req.data)) != -1) { long int avatar_id; pthread_mutex_lock(&vs_ctx->data.mutex); avatar_id = vs_create_avatar_node(vs_ctx, vsession, user_id); pthread_mutex_unlock(&vs_ctx->data.mutex); if(avatar_id == -1) { v_print_log(VRS_PRINT_ERROR, "Failed to create avatar node\n"); return 0; } buffer_pos = VERSE_MESSAGE_HEADER_SIZE; /* Save user_id to the session and send it in * connect_accept command */ vsession->user_id = user_id; vsession->avatar_id = avatar_id; s_message->sys_cmd[cmd_rank].ua_succ.id = CMD_USER_AUTH_SUCCESS; s_message->sys_cmd[cmd_rank].ua_succ.user_id = user_id; s_message->sys_cmd[cmd_rank].ua_succ.avatar_id = avatar_id; cmd_rank++; /* Generate random string for coockie */ vsession->peer_cookie.str = (char*)calloc((COOKIE_SIZE+1), sizeof(char)); for(i=0; i<COOKIE_SIZE; i++) { /* Generate only printable characters (debug prints) */ vsession->peer_cookie.str[i] = 32 + (char)((float)rand()*94.0/RAND_MAX); } vsession->peer_cookie.str[COOKIE_SIZE] = '\0'; /* Set up negotiate command of the host cookie */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CHANGE_R_ID, FTR_COOKIE, vsession->peer_cookie.str, NULL); /* Load DED from configuration and save it to the session */ vsession->ded.str = strdup(vs_ctx->ded); v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CHANGE_L_ID, FTR_DED, vsession->ded.str, NULL); buffer_pos += v_pack_stream_system_commands(s_message, &io_ctx->buf[buffer_pos]); s_message->header.len = io_ctx->buf_size = buffer_pos; s_message->header.version = VRS_VERSION; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); v_print_send_message(C); return 1; } else { buffer_pos = VERSE_MESSAGE_HEADER_SIZE; s_message->sys_cmd[0].ua_fail.id = CMD_USER_AUTH_FAILURE; s_message->sys_cmd[0].ua_fail.count = 0; s_message->sys_cmd[1].cmd.id = CMD_RESERVED_ID; buffer_pos += v_pack_stream_system_commands(s_message, &io_ctx->buf[buffer_pos]); s_message->header.len = io_ctx->buf_size = buffer_pos; s_message->header.version = VRS_VERSION; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); v_print_send_message(C); return 1; } } } } return 0; }
/** * \brief WebSocket callback function for received message */ void vs_ws_recv_msg_callback(wslay_event_context_ptr wslay_ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { struct vContext *C = (struct vContext*)user_data; struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct VSession *session = CTX_current_session(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); int ret; if(!wslay_is_ctrl_frame(arg->opcode)) { /* Verse server uses only binary message for communication */ if(arg->opcode == WSLAY_BINARY_FRAME) { struct wslay_event_msg msgarg; #if DEBUG_WEB_SOCKET unsigned int i; v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback received binary message\n"); v_print_log(VRS_PRINT_DEBUG_MSG, "Binary dump\n"); /* Print dump of received data */ for(i=0; i<arg->msg_length; i++) { v_print_log_simple(VRS_PRINT_DEBUG_MSG, "%d,", arg->msg[i]); } v_print_log_simple(VRS_PRINT_DEBUG_MSG, "\n"); #endif /* Copy received data to IO context */ memcpy(io_ctx->buf, arg->msg, arg->msg_length); io_ctx->buf_size = arg->msg_length; if(session->stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { if(v_STREAM_handle_messages(C) != 0) { /* When some payload data were received, then poke data thread */ sem_post(vs_ctx->data.sem); } else { /* End connection */ session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* Try to close connection with WebSocket client */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_PROTOCOL_ERROR, (uint8_t*)"Wrong command", /* Close message */ 13); /* The length of close message */ return; } } else { if( vs_handle_handshake(C) == -1 ) { /* End connection */ session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* Try to close connection with WebSocket client */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_PROTOCOL_ERROR, (uint8_t*)"Wrong command", /* Close message */ 13); /* The length of close message */ return; } /* During handshake send response immediately. */ /* TODO: optionally send message fragmented, when it is needed using: * wslay_event_queue_fragmented_msg() */ msgarg.opcode = WSLAY_BINARY_FRAME; msgarg.msg = (uint8_t*)io_ctx->buf; msgarg.msg_length = io_ctx->buf_size; /* Queue message for sending */ if((ret = wslay_event_queue_msg(wslay_ctx, &msgarg)) != 0) { v_print_log(VRS_PRINT_ERROR, "Unable to queue message to WebSocket: %d\n", ret); return; } else { v_print_log(VRS_PRINT_DEBUG_MSG, "WebSocket message successfully queued\n"); } } } else if(arg->opcode == WSLAY_TEXT_FRAME) { v_print_log(VRS_PRINT_ERROR, "WebSocket text frame is not supported\n"); return; } } else { /* Print opcode of control message */ v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback Received Ctrl Message: opcode: %d\n", arg->opcode); /* Is it closing message? */ if(arg->opcode & WSLAY_CONNECTION_CLOSE) { v_print_log(VRS_PRINT_DEBUG_MSG, "Close message with code: %d, message: %s\n", arg->status_code, arg->msg); /* When this control message was received at second time, then * switch to the state CLOSED. Otherwise switch to the state * CLOSING */ if(session->stream_conn->host_state == TCP_SERVER_STATE_CLOSING) { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSED; } else { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* When server wasn't in the state closing, then send * confirmation to the client, that this connection will be * closed */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_NORMAL_CLOSURE, (uint8_t*)"Closing connection", 15); } } } }
/** * \brief This function is the callback function for received Change_L * commands in REQUEST state. */ static int vc_REQUEST_CHANGE_L_cb(struct vContext *C, struct Generic_Cmd *cmd) { struct VSession *vsession = CTX_current_session(C); struct VDgramConn *dgram_conn = CTX_current_dgram_conn(C); struct Negotiate_Cmd *change_l_cmd = (struct Negotiate_Cmd*)cmd; int value_rank; if(change_l_cmd->feature == FTR_TOKEN) { /* This block of code checks, if received token is the same as the * negotiated token. When token is not the same, then the client * will not response to the received packet. */ if( vsession->peer_token.str != NULL && change_l_cmd->count > 0) { v_print_log(VRS_PRINT_DEBUG_MSG, "Remote TOKEN: %s proposed\n", change_l_cmd->value[0].string8.str); return 1; } else { v_print_log(VRS_PRINT_WARNING, "Peer proposed wrong TOKEN\n"); return 0; } } /* Server proposes it's own Flow Control */ if(change_l_cmd->feature == FTR_FC_ID) { int ret = 0; for(value_rank=0; value_rank<change_l_cmd->count; value_rank++) { /* Is value in "list" of supported methods */ if(change_l_cmd->value[value_rank].uint8 == FC_NONE || change_l_cmd->value[value_rank].uint8 == FC_TCP_LIKE) { /* It will try to use first found supported method, but ... */ if(dgram_conn->fc_meth == FC_RESERVED) { /* Flow Control has not been proposed yet */ } else { /* Server has to propose same FC methods for client * and server. Client can't use different method then * server and vice versa. */ if(dgram_conn->fc_meth != change_l_cmd->value[value_rank].uint8) { v_print_log(VRS_PRINT_WARNING, "Skipping proposed local FC :%d; it is not teh same as proposed remote FC: %d\n", change_l_cmd->value[value_rank].uint8, dgram_conn->fc_meth); continue; } } dgram_conn->fc_meth = change_l_cmd->value[value_rank].uint8; v_print_log(VRS_PRINT_DEBUG_MSG, "Local Flow Control ID: %d proposed\n", change_l_cmd->value[0].uint8); ret = 1; break; } else { v_print_log(VRS_PRINT_ERROR, "Skipping unsupported Flow Control method: %d\n", change_l_cmd->value[value_rank].uint8); continue; } } return ret; } /* Server proposes it's own scale of Flow Control Window */ if(change_l_cmd->feature == FTR_RWIN_SCALE) { if(change_l_cmd->count >= 1) { dgram_conn->rwin_peer_scale = change_l_cmd->value[0].uint8; v_print_log(VRS_PRINT_DEBUG_MSG, "Scale of peer RWIN: %d proposed\n", dgram_conn->rwin_peer_scale); return 1; } else { v_print_log(VRS_PRINT_ERROR, "At last on value of RWIN scale has to be proposed\n"); return 0; } } /* Ignore unknown feature */ return 1; }
void* vc_main_dgram_loop(void *arg) { struct vContext *C = (struct vContext*)arg; struct VSession *vsession = CTX_current_session(C); struct VDgramConn *dgram_conn = vsession->dgram_conn; struct VStreamConn *stream_conn = CTX_current_stream_conn(C); struct VPacket *r_packet, *s_packet; char error = 0; const uint8 *ret_val = 0; if(dgram_conn==NULL) { /* Create new datagrame connection */ if((dgram_conn = vc_create_client_dgram_conn(C))==NULL) { goto finish; } vsession->dgram_conn = dgram_conn; } CTX_current_dgram_conn_set(C, dgram_conn); CTX_io_ctx_set(C, &dgram_conn->io_ctx); #if (defined WITH_OPENSSL) && OPENSSL_VERSION_NUMBER>=0x10000000 /* If negotiated security is DTLS, then try to do DTLS handshake */ if(dgram_conn->io_ctx.flags & SOCKET_SECURED) { if(vc_create_dtls_connection(C) == 0) { CTX_current_dgram_conn_set(C, NULL); CTX_io_ctx_set(C, NULL); free(dgram_conn); ret_val = &vrs_conn_term_error; goto finish; } } #endif /* Packet structure for receiving */ r_packet = (struct VPacket*)malloc(sizeof(struct VPacket)); CTX_r_packet_set(C, r_packet); /* Packet structure for sending */ s_packet = (struct VPacket*)malloc(sizeof(struct VPacket)); CTX_s_packet_set(C, s_packet); /* Run loop of the first phase of the handshake */ if((error = vc_REQUEST_loop(C)) == STATE_EXIT_ERROR) { /* Do not confirm proposed URL, when client was not able to connect * to the server */ CTX_io_ctx_set(C, &stream_conn->io_ctx); vc_NEGOTIATE_newhost(C, NULL); CTX_io_ctx_set(C, &dgram_conn->io_ctx); ret_val = &vrs_conn_term_error; goto end; } /* When server responded in the first phase of the handshake, then run loop of the second * phase of the handshake */ if((error = vc_PARTOPEN_loop(C)) == STATE_EXIT_ERROR) { /* Do not confirm proposed URL, when client was not able to connect * to the server */ CTX_io_ctx_set(C, &stream_conn->io_ctx); vc_NEGOTIATE_newhost(C, NULL); CTX_io_ctx_set(C, &dgram_conn->io_ctx); ret_val = &vrs_conn_term_error; goto end; } else { struct Connect_Accept_Cmd *conn_accept; /* Put connect accept command to queue -> call callback function */ conn_accept = v_Connect_Accept_create(vsession->avatar_id, vsession->user_id); v_in_queue_push(vsession->in_queue, (struct Generic_Cmd*)conn_accept); /* Send confirmation of the URL to the server */ CTX_io_ctx_set(C, &stream_conn->io_ctx); vc_NEGOTIATE_newhost(C, vsession->host_url); CTX_io_ctx_set(C, &dgram_conn->io_ctx); /* TCP connection could be closed now */ vsession->stream_conn->host_state = TCP_CLIENT_STATE_CLOSING; } /* Main loop for data exchange */ if((error = vc_OPEN_loop(C)) == STATE_EXIT_ERROR) { ret_val = &vrs_conn_term_error; goto end; } /* Closing loop */ if((error = vc_CLOSING_loop(C)) == STATE_EXIT_ERROR) { ret_val = &vrs_conn_term_error; goto end; } /* TODO: distinguish between terminating connection by server and client */ ret_val = &vrs_conn_term_server; end: /* CLOSED state */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Client state: CLOSED\n"); printf("%c[%dm", 27, 0); } free(r_packet); free(s_packet); #if (defined WITH_OPENSSL) && OPENSSL_VERSION_NUMBER>=0x10000000 if(dgram_conn->io_ctx.flags & SOCKET_SECURED) { vc_destroy_dtls_connection(C); } #endif v_conn_dgram_destroy(dgram_conn); CTX_current_dgram_conn_set(C, NULL); finish: pthread_exit((void*)ret_val); return NULL; }
int vc_create_dtls_connection(struct vContext *C) { struct VC_CTX *vc_ctx = CTX_client_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VDgramConn *dgram_conn = CTX_current_dgram_conn(C); struct timeval timeout; int ret = 0; v_print_log(VRS_PRINT_DEBUG_MSG, "Try to do DTLS handshake at UDP socket: %d\n", dgram_conn->io_ctx.sockfd); /* Create ssl for new connection */ if( (dgram_conn->io_ctx.ssl = SSL_new(vc_ctx->dtls_ctx)) == NULL) { v_print_log(VRS_PRINT_ERROR, "SSL_new(%p)\n", (void*)vc_ctx->dtls_ctx); return 0; } /* Set state of bio as connected */ if(dgram_conn->io_ctx.peer_addr.ip_ver == IPV4) { ret = BIO_ctrl(dgram_conn->io_ctx.bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &dgram_conn->io_ctx.peer_addr.addr.ipv6); } else if(dgram_conn->io_ctx.peer_addr.ip_ver == IPV6) { ret = BIO_ctrl(dgram_conn->io_ctx.bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &dgram_conn->io_ctx.peer_addr.addr.ipv4); } /* When BIO_ctrl was called with bad arguments, then it returns 0 */ if(ret==0) { v_print_log(VRS_PRINT_ERROR, "BIO_ctrl()\n"); SSL_free(dgram_conn->io_ctx.ssl); return 0; } /* Set and activate timeouts */ timeout.tv_sec = 1; timeout.tv_usec = 0; BIO_ctrl(dgram_conn->io_ctx.bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout); BIO_ctrl(dgram_conn->io_ctx.bio, BIO_CTRL_DGRAM_SET_SEND_TIMEOUT, 0, &timeout); /* Bind ssl and bio */ SSL_set_bio(dgram_conn->io_ctx.ssl, dgram_conn->io_ctx.bio, dgram_conn->io_ctx.bio); /* Try to do DTLS handshake */ again: if ((ret = SSL_connect(dgram_conn->io_ctx.ssl)) <= 0) { int err = SSL_get_error(dgram_conn->io_ctx.ssl, ret); if(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { gettimeofday(&timeout, NULL); if((timeout.tv_sec - vsession->peer_token.tv.tv_sec) > VRS_TIMEOUT) { v_print_log(VRS_PRINT_ERROR, "Token timed out\n"); return 0; } usleep(1000); goto again; } ERR_print_errors_fp(stderr); v_print_log(VRS_PRINT_ERROR, "SSL_connect() failed: %d -> %d\n", ret, err); SSL_free(dgram_conn->io_ctx.ssl); dgram_conn->io_ctx.ssl = NULL; dgram_conn->io_ctx.bio = NULL; return 0; } else { v_print_log(VRS_PRINT_DEBUG_MSG, "DTLS handshake finished\n"); v_print_log(VRS_PRINT_DEBUG_MSG, "Current cipher: %s\n", SSL_CIPHER_get_name(SSL_get_current_cipher(dgram_conn->io_ctx.ssl))); } return 1; }
/** * \brief This command handle received packets in OPEN state at client and * server in the same way. */ int handle_packet_in_OPEN_state(struct vContext *C) { struct VDgramConn *vconn = CTX_current_dgram_conn(C); struct VSession *vsession = CTX_current_session(C); struct VPacket *r_packet = CTX_r_packet(C); int ret, first_sys_index, i; /* Does packet contains node commands? */ if(r_packet->header.flags & PAY_FLAG) { ret = handle_node_commands(C); if(ret == RECEIVE_PACKET_UNORDERED) return ret; r_packet->acked=0; } /* Compute real RWIN of Flow Control */ vconn->rwin_peer = r_packet->header.window << vconn->rwin_peer_scale; /* Was packet received with any ACK or NAK command? */ if(r_packet->header.flags & ACK_FLAG) { ret = handle_ack_nak_commands(C); } /* Handle other system commands */ if(ret>=0) { first_sys_index = ret; } else { first_sys_index = 0; } for(i=first_sys_index; i<MAX_SYSTEM_COMMAND_COUNT && r_packet->sys_cmd[i].cmd.id != CMD_RESERVED_ID; i++) { if(r_packet->sys_cmd[i].cmd.id == CMD_CHANGE_L_ID && r_packet->sys_cmd[i].negotiate_cmd.feature == FTR_FPS && r_packet->sys_cmd[i].negotiate_cmd.count > 0) { vsession->fps_host = vsession->fps_peer = r_packet->sys_cmd[i].negotiate_cmd.value->real32; vsession->tmp_flags |= SYS_CMD_NEGOTIATE_FPS; } if(r_packet->sys_cmd[i].cmd.id == CMD_CONFIRM_L_ID && r_packet->sys_cmd[i].negotiate_cmd.feature == FTR_FPS && r_packet->sys_cmd[i].negotiate_cmd.count > 0) { vsession->fps_peer = r_packet->sys_cmd[i].negotiate_cmd.value->real32; } } /* Was packet received with valid ANK ID? */ if(r_packet->header.flags & ANK_FLAG) { /* Remove appropriate ACK and NAK commands form the history of ACK * and NAK commands */ v_ack_nak_history_remove_cmds(&vconn->ack_nak, r_packet->header.ank_id); } /* Does peer want to finish this connection? */ if(r_packet->header.flags & FIN_FLAG) { /* Change host state. Main connection loop will do the rest. */ vconn->host_state = UDP_CLIENT_STATE_CLOSING; } return RECEIVE_PACKET_SUCCESS; }
/** * \brief This function is called, when acknowledgment packet was received. * * This function processes all ACK and NAK commands and it add not obsolete * commands from the history of sent packets to the out queue again. This * function also removes positively and negatively acknowledged packets from * history of sent packets. ANK ID is updated. * * \param[in] *C The verse context. * * \return This function returns index of last ACK command in sequence of * system command, when ACK and NAK commands are the beginning of system * commands, otherwise it returns -2. When no ACK or NAK command was found, * then -1 is returned; */ static int handle_ack_nak_commands(struct vContext *C) { struct VSession *vsession = CTX_current_session(C); struct VDgramConn *vconn = CTX_current_dgram_conn(C); struct VPacket *r_packet = CTX_r_packet(C); struct VSent_Packet *sent_packet; struct VSent_Command *sent_cmd, *sent_cmd_prev; unsigned long int rtt = ULONG_MAX; struct timeval tv; uint32 ack_id, nak_id; int i, ret=-1; gettimeofday(&tv, NULL); /* Compute SRTT */ if(r_packet->sys_cmd[0].cmd.id==CMD_ACK_ID) { unsigned long int tmp; int i=0; /* Try to find the smallest RTT from acknowledged packets */ for(i=0; r_packet->sys_cmd[i].cmd.id!=CMD_RESERVED_ID; i++) { if(r_packet->sys_cmd[i].cmd.id==CMD_ACK_ID) { sent_packet = v_packet_history_find_packet(&vconn->packet_history, r_packet->sys_cmd[i].ack_cmd.pay_id); if(sent_packet!=NULL) { tmp = packet_rtt(sent_packet, &tv); if(tmp<rtt) rtt=tmp; } } } if(rtt<ULONG_MAX) { /* Computation of SRTT as described in RFC */ if(vconn->srtt==0) { vconn->srtt = rtt; } else { vconn->srtt = RTT_ALPHA*vconn->srtt + (1-RTT_ALPHA)*rtt; } v_print_log(VRS_PRINT_DEBUG_MSG, "RTT: %d [us]\n", rtt); v_print_log(VRS_PRINT_DEBUG_MSG, "SRTT: %d [us]\n", vconn->srtt); } } /* Process all ACK and NAK commands. ACK and NAK commands should be first * and there should not be other system commands between ACK and NAK * commands. */ for(i=0; r_packet->sys_cmd[i].cmd.id == CMD_NAK_ID || r_packet->sys_cmd[i].cmd.id == CMD_ACK_ID; i++) { if(r_packet->sys_cmd[i].cmd.id == CMD_ACK_ID) { /* Check if ACK and NAK commands are the first system commands */ if(ret!=-2 && ret==i-1) { ret = i; } else { ret = -2; } /* If this is not the last ACK command in the sequence of * ACK/NAK commands, then remove all packets from history of * sent packet, that are in following sub-sequence of ACK * commands */ if(r_packet->sys_cmd[i+1].cmd.id == CMD_NAK_ID || r_packet->sys_cmd[i+1].cmd.id == CMD_ACK_ID) { /* Remove all acknowledged payload packets from the history * of sent payload packets */ for(ack_id = r_packet->sys_cmd[i].ack_cmd.pay_id; ack_id < r_packet->sys_cmd[i+1].nak_cmd.pay_id; ack_id++) { v_packet_history_rem_packet(C, ack_id); } } else { /* Remove this acknowledged payload packets from the history * of sent payload packets */ v_packet_history_rem_packet(C, r_packet->sys_cmd[i].ack_cmd.pay_id); /* This is the last ACK command in the sequence of ACK/NAK * commands. Update ANK ID. */ vconn->ank_id = r_packet->sys_cmd[i].ack_cmd.pay_id; } } else if(r_packet->sys_cmd[i].cmd.id == CMD_NAK_ID) { /* Check if ACK and NAK commands are the first system commands */ if(ret!=-2 && ret==i-1) { ret = i; } else { ret = -2; } /* Go through the sub-sequence of NAk commands and try to re-send * not-obsolete data from these packets */ for(nak_id = r_packet->sys_cmd[i].nak_cmd.pay_id; nak_id < r_packet->sys_cmd[i+1].ack_cmd.pay_id; nak_id++) { /* Add not obsolete data of lost packet to the outgoing queue */ /* Update ANK ID */ sent_packet = v_packet_history_find_packet(&vconn->packet_history, nak_id); if(sent_packet != NULL) { v_print_log(VRS_PRINT_DEBUG_MSG, "Try to re-send packet: %d\n", nak_id); sent_cmd = sent_packet->cmds.last; /* Go through all commands in command list and add not * obsolete commands to the outgoing queue */ while(sent_cmd != NULL) { sent_cmd_prev = sent_cmd->prev; if(sent_cmd->vbucket != NULL && sent_cmd->vbucket->data != NULL) { /* Try to add command back to the outgoing command queue */ if(v_out_queue_push_head(vsession->out_queue, sent_cmd->prio, (struct Generic_Cmd*)sent_cmd->vbucket->data) == 1) { /* Remove bucket from the history of sent commands too */ v_hash_array_remove_item(&vconn->packet_history.cmd_hist[sent_cmd->id]->cmds, sent_cmd->vbucket->data); /* When command was added back to the queue, * then delete only sent command */ v_list_free_item(&sent_packet->cmds, sent_cmd); } } sent_cmd = sent_cmd_prev; } /* When all not obsolete commands are added to outgoing * queue, then this packet could be removed from packet * history*/ v_packet_history_rem_packet(C, nak_id); } } } } return ret; }
/** * \brief This function send packets in OPEN and CLOSEREQ state. */ int send_packet_in_OPEN_CLOSEREQ_state(struct vContext *C) { struct VDgramConn *vconn = CTX_current_dgram_conn(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VPacket *s_packet = CTX_s_packet(C); struct VSent_Packet *sent_packet = NULL; unsigned short buffer_pos = 0; struct timeval tv; int ret, keep_alive_packet = -1, full_packet = 0; int error_num; uint16 swin, prio_win, sent_size = 0; uint32 rwin; int cmd_rank = 0; /* Verse packet header */ s_packet->header.version = 1; /* Clear header flags */ s_packet->header.flags = 0; /* Check if it is necessary to send payload packet */ ret = check_pay_flag(C); if(ret!=0) { s_packet->header.flags |= PAY_FLAG; if(ret==2) { keep_alive_packet = 1; } } /* When server is in CLOSEREQ state, then FIN flag should be set up */ if(vconn->host_state == UDP_SERVER_STATE_CLOSEREQ) { s_packet->header.flags |= FIN_FLAG; } /* Check if it is necessary to send acknowledgment of received payload * packet */ ret = check_ack_nak_flag(C); if(ret==1) { s_packet->header.flags |= ACK_FLAG; /* Update last acknowledged Payload packet */ vconn->last_acked_pay = vconn->last_r_pay; /* Add ACK and NAK commands from the list of ACK and NAK commands to the * packet (only max count of ACK and NAK commands could be added to * the packet) */ for(cmd_rank = 0; cmd_rank < vconn->ack_nak.count && cmd_rank < MAX_SYSTEM_COMMAND_COUNT; cmd_rank++) { s_packet->sys_cmd[cmd_rank].ack_cmd.id = vconn->ack_nak.cmds[cmd_rank].id; s_packet->sys_cmd[cmd_rank].ack_cmd.pay_id = vconn->ack_nak.cmds[cmd_rank].pay_id; } s_packet->sys_cmd[cmd_rank].cmd.id = CMD_RESERVED_ID; } /* If there is no need to send Payload or AckNak packet, then cancel * sending of packet */ if(! ((s_packet->header.flags & PAY_FLAG) || (s_packet->header.flags & ACK_FLAG)) ) return SEND_PACKET_CANCELED; s_packet->header.flags |= ANK_FLAG; s_packet->header.ank_id = vconn->ank_id; /* Compute current windows for flow control and congestion control */ set_host_rwin(C); set_host_cwin(C); /* Set window of flow control that will sent to receiver */ rwin = vconn->rwin_host >> vconn->rwin_host_scale; s_packet->header.window = (unsigned short)(rwin > 0xFFFF) ? 0xFFFF : rwin; /*printf("\t---real window: %d---\n", s_packet->header.window);*/ /* Compute how many data could be sent to not congest receiver */ rwin = vconn->rwin_peer - vconn->sent_size; /* Select smallest window for sending (congestion control window or flow control window)*/ swin = (vconn->cwin < rwin) ? vconn->cwin : rwin; /* Set up Payload ID, when there is need to send payload packet */ if(s_packet->header.flags & PAY_FLAG) s_packet->header.payload_id = vconn->host_id + vconn->count_s_pay; else s_packet->header.payload_id = 0; /* Set up AckNak ID, when there are some ACK or NAK command in the packet */ if(s_packet->header.flags & ACK_FLAG) s_packet->header.ack_nak_id = vconn->count_s_ack; else s_packet->header.ack_nak_id = 0; /* When negotiated and used FPS is different, then pack negotiate command * for FPS */ if(vsession->fps_host != vsession->fps_peer) { cmd_rank += v_add_negotiate_cmd(s_packet->sys_cmd, cmd_rank, CMD_CHANGE_L_ID, FTR_FPS, &vsession->fps_host, NULL); } else { if(vsession->tmp_flags & SYS_CMD_NEGOTIATE_FPS) { cmd_rank += v_add_negotiate_cmd(s_packet->sys_cmd, cmd_rank, CMD_CONFIRM_L_ID, FTR_FPS, &vsession->fps_peer, NULL); /* Send confirmation only once for received system command */ vsession->tmp_flags &= ~SYS_CMD_NEGOTIATE_FPS; } } v_print_send_packet(C); /* Fill buffer */ buffer_pos += v_pack_packet_header(s_packet, &io_ctx->buf[buffer_pos]); buffer_pos += v_pack_dgram_system_commands(s_packet, &io_ctx->buf[buffer_pos]); /* When this is not pure keep alive packet */ if(s_packet->header.flags & PAY_FLAG) { sent_packet = v_packet_history_add_packet(&vconn->packet_history, s_packet->header.payload_id); assert(sent_packet != NULL); if(keep_alive_packet != 1) { real32 prio_sum_high, prio_sum_low, r_prio; uint32 prio_count; int16 prio, max_prio, min_prio; uint16 tot_cmd_size; /* Print outgoing command with green color */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 32); } max_prio = v_out_queue_get_max_prio(vsession->out_queue); min_prio = v_out_queue_get_min_prio(vsession->out_queue); prio_sum_high = v_out_queue_get_prio_sum_high(vsession->out_queue); prio_sum_low = v_out_queue_get_prio_sum_low(vsession->out_queue); v_print_log(VRS_PRINT_DEBUG_MSG, "Packing prio queues, cmd count: %d\n", v_out_queue_get_count(vsession->out_queue)); /* Go through all priorities and pick commands from priority queues */ for(prio = max_prio; prio >= min_prio; prio--) { /* TODO: Add better check here */ if(prio <= VRS_DEFAULT_PRIORITY && buffer_pos >= vconn->io_ctx.mtu) { break; } prio_count = v_out_queue_get_count_prio(vsession->out_queue, prio); if(prio_count > 0) { r_prio = v_out_queue_get_prio(vsession->out_queue, prio); /* Compute size of buffer that could be occupied by * commands from this queue */ if(prio >= VRS_DEFAULT_PRIORITY) { prio_win = ((swin - buffer_pos)*r_prio)/prio_sum_high; } else { prio_win = ((swin - buffer_pos)*r_prio)/prio_sum_low; } /* Debug print */ v_print_log(VRS_PRINT_DEBUG_MSG, "Queue: %d, count: %d, r_prio: %6.3f, prio_win: %d\n", prio, prio_count, r_prio, prio_win); /* Get total size of commands that were stored in queue (sent_size) */ tot_cmd_size = 0; /* Pack commands from queues with high priority to the buffer */ buffer_pos = pack_prio_queue(C, sent_packet, buffer_pos, prio, prio_win, &tot_cmd_size); sent_size += tot_cmd_size; } } /* Use default color for output */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%dm", 27, 0); } } else { if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 32); v_print_log(VRS_PRINT_DEBUG_MSG, "Keep alive packet\n"); printf("%c[%dm", 27, 0); } } } /* Update sent_size */ vconn->sent_size += sent_size; io_ctx->buf_size = buffer_pos; /* Send buffer */ ret = v_send_packet(io_ctx, &error_num); if(ret==SEND_PACKET_SUCCESS) { gettimeofday(&tv, NULL); /* Update time of sending last payload packet */ if(s_packet->header.flags & PAY_FLAG) { vconn->tv_pay_send.tv_sec = tv.tv_sec; vconn->tv_pay_send.tv_usec = tv.tv_usec; /* Store time of sending packet in history of sent packets. It is * used for computing RTT and SRTT */ if(sent_packet != NULL) { sent_packet->tv.tv_sec = tv.tv_sec; sent_packet->tv.tv_usec = tv.tv_usec; } } /* Update time of sending last acknowledgment packet */ if(s_packet->header.flags & ACK_FLAG) { vconn->tv_ack_send.tv_sec = tv.tv_sec; vconn->tv_ack_send.tv_usec = tv.tv_usec; } /* Update counter of sent packets */ if(s_packet->header.flags & PAY_FLAG) vconn->count_s_pay++; if(s_packet->header.flags & ACK_FLAG) vconn->count_s_ack++; /* If the packet was sent full and there are some pending data to send * then modify returned value*/ if(full_packet == 1) { ret = SEND_PACKET_FULL; } } else { /* When packet wasn't sent, then remove this packet from history */ if(sent_packet != NULL) { v_packet_history_rem_packet(C, s_packet->header.payload_id); } } /*v_print_packet_history(&vconn->packet_history);*/ return ret; }
/** * \brief This function packs and compress command to the packet from one * priority queue. * * \param[in] *C The verse context * \param[in] *sent_packet The pointer at structure with send packet * \param[in] buffer_pos The curent size of buffer of sent packet * \param[in] prio The priority of sub queue * \param[in] prio_win The window size of current prio queue * \param[out] tot_cmd_size The total size of commands that were poped from prio queue */ static int pack_prio_queue(struct vContext *C, struct VSent_Packet *sent_packet, int buffer_pos, uint8 prio, uint16 prio_win, uint16 *tot_cmd_size) { struct VSession *vsession = CTX_current_session(C); struct VDgramConn *vconn = CTX_current_dgram_conn(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct Generic_Cmd *cmd; int ret, last_cmd_count = 0; uint16 cmd_count, cmd_len, cmd_size, sum_len=0; int8 cmd_share; uint8 last_cmd_id = CMD_RESERVED_ID; while( (v_out_queue_get_count_prio(vsession->out_queue, prio) > 0) && (sum_len < prio_win) && (buffer_pos < vconn->io_ctx.mtu)) { cmd_count = 0; cmd_share = 0; /* Compute how many commands could be compressed to the packet * and compute right length of compressed commands. */ cmd_len = ((prio_win - sum_len)<(vconn->io_ctx.mtu - buffer_pos)) ? (prio_win - sum_len) : (vconn->io_ctx.mtu - buffer_pos); /* Remove command from queue */ cmd = v_out_queue_pop(vsession->out_queue, prio, &cmd_count, &cmd_share, &cmd_len); /* When it is not possible to pop more commands from queue, then break * while loop */ if(cmd == NULL) { break; } /* Is this command fake command? */ if(cmd->id < MIN_CMD_ID) { if(cmd->id == FAKE_CMD_CONNECT_TERMINATE) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); if(vs_ctx != NULL) { vconn->host_state = UDP_SERVER_STATE_CLOSEREQ; } else { vconn->host_state = UDP_CLIENT_STATE_CLOSING; } } else if(cmd->id == FAKE_CMD_FPS) { struct Fps_Cmd *fps_cmd = (struct Fps_Cmd*)cmd; /* Change value of FPS. It will be sent in negotiate command * until it is confirmed be the peer (server) */ vsession->fps_host = fps_cmd->fps; } v_cmd_destroy(&cmd); } else { /* What was size of command in queue */ cmd_size = v_cmd_size(cmd); if(!(buffer_pos < (vconn->io_ctx.mtu - cmd_size))) { /* When there is not enough space for other command, * then push command back to the beginning of queue. */ v_out_queue_push_head(vsession->out_queue, prio, cmd); break; } else { /* Update total size of commands that were poped from queue */ *tot_cmd_size += cmd_size; /* When compression is not allowed, then add this command as is */ if( vconn->host_cmd_cmpr == CMPR_NONE) { cmd_count = 0; cmd_len = cmd_size; /* Debug print */ v_print_log(VRS_PRINT_DEBUG_MSG, "Cmd: %d, count: %d, length: %d\n", cmd->id, cmd_count, cmd_len); /* Add command to the buffer */ buffer_pos += v_cmd_pack(&io_ctx->buf[buffer_pos], cmd, cmd_len, 0); } else { /* When command compression is allowed and was ID of command changed? */ if( (cmd->id != last_cmd_id) || (last_cmd_count <= 0) ) { /* When this command is alone, then use default command size */ if(cmd_count == 0) { cmd_len = cmd_size; } else { /* FIXME: do not recompute command length here, but do it right, * when command is added to the queue */ cmd_len = v_cmds_len(cmd, cmd_count, cmd_share, cmd_len); } /* Debug print */ v_print_log(VRS_PRINT_DEBUG_MSG, "Cmd: %d, count: %d, length: %d\n", cmd->id, cmd_count, cmd_len); /* Add command to the buffer */ buffer_pos += v_cmd_pack(&io_ctx->buf[buffer_pos], cmd, cmd_len, cmd_share); /* Set up current count of commands in the line */ last_cmd_count = cmd_count; /* Update summary of commands length */ sum_len += cmd_len; } else { buffer_pos += v_cmd_pack(&io_ctx->buf[buffer_pos], cmd, 0, cmd_share); } } /* Print command */ v_cmd_print(VRS_PRINT_DEBUG_MSG, cmd); /* TODO: remove command alias here (layer value set/unset) */ /* Add command to the packet history */ ret = v_packet_history_add_cmd(&vconn->packet_history, sent_packet, cmd, prio); assert(ret==1); /* Update last command id */ last_cmd_id = cmd->id; /* Decrement counter of commands in queue */ last_cmd_count--; } } } return buffer_pos; }
/** * \brief This function removes packet with id from history of sent packets. * It removes all its commands from the command history too. * * \param[in] *C The verse context. * \param[in] id The ID of packet, the will be removed from the history. * * \return This function returns 1, then packet with id was found in the history * and it returns 0 otherwise. */ int v_packet_history_rem_packet(struct vContext *C, uint32 id) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct VDgramConn *dgram_conn = CTX_current_dgram_conn(C); struct VPacket_History *history = &dgram_conn->packet_history; struct VPacket *r_packet = CTX_r_packet(C); struct VSent_Packet *sent_packet; int ret = 0, is_fake_cmd_received = 0; sent_packet = v_packet_history_find_packet(history, id); if(sent_packet != NULL) { struct VSent_Command *sent_cmd; v_print_log(VRS_PRINT_DEBUG_MSG, "Removing packet: %d from history\n", sent_packet->id); /* Go through the whole list of sent commands and free them from the * hashed linked list */ sent_cmd = sent_packet->cmds.first; while(sent_cmd != NULL) { /* Remove own command from hashed linked list if it wasn't already * removed, when command was obsoleted by some newer packet */ if(sent_cmd->vbucket!=NULL) { struct Generic_Cmd *cmd = (struct Generic_Cmd*)sent_cmd->vbucket->data; /* Bucket has to include some data */ assert(sent_cmd->vbucket->data!=NULL); /* Decrease total size of commands that were sent and wasn't acknowladged yet*/ dgram_conn->sent_size -= v_cmd_size(cmd); /* Put fake command for create/destroy commands at verse server */ if(vs_ctx != NULL) { struct VSession *vsession = CTX_current_session(C); struct Generic_Cmd *fake_cmd = NULL; switch(cmd->id) { case CMD_NODE_CREATE: fake_cmd = v_fake_node_create_ack_create(UINT32(cmd->data[UINT16_SIZE + UINT32_SIZE])); break; case CMD_NODE_DESTROY: fake_cmd = v_fake_node_destroy_ack_create(UINT32(cmd->data[0])); break; case CMD_TAGGROUP_CREATE: fake_cmd = v_fake_taggroup_create_ack_create(UINT32(cmd->data[0]), UINT16(cmd->data[UINT32_SIZE])); break; case CMD_TAGGROUP_DESTROY: fake_cmd = v_fake_taggroup_destroy_ack_create(UINT32(cmd->data[0]), UINT16(cmd->data[UINT32_SIZE])); break; case CMD_TAG_CREATE: fake_cmd = v_tag_create_ack_create(UINT32(cmd->data[0]), UINT16(cmd->data[UINT32_SIZE]), UINT16(cmd->data[UINT32_SIZE + UINT16_SIZE])); break; case CMD_TAG_DESTROY: fake_cmd = v_tag_destroy_ack_create(UINT32(cmd->data[0]), UINT16(cmd->data[UINT32_SIZE]), UINT16(cmd->data[UINT32_SIZE + UINT16_SIZE])); break; case CMD_LAYER_CREATE: fake_cmd = v_fake_layer_create_ack_create(UINT32(cmd->data[0]), UINT16(cmd->data[UINT32_SIZE + UINT16_SIZE])); break; case CMD_LAYER_DESTROY: fake_cmd = v_fake_layer_destroy_ack_create(UINT32(cmd->data[0]), UINT16(cmd->data[UINT32_SIZE])); break; default: break; } if(fake_cmd != NULL) { is_fake_cmd_received = 1; /* Push command to the queue of incomming commands */ v_in_queue_push(vsession->in_queue, fake_cmd); /* Print content of fake command */ v_fake_cmd_print(VRS_PRINT_DEBUG_MSG, fake_cmd); } } /* Remove command from the history of sent commands */ ret = v_hash_array_remove_item(&history->cmd_hist[sent_cmd->id]->cmds, sent_cmd->vbucket->data); if(ret == 1) { /* Destroy command */ v_cmd_destroy(&cmd); } else { v_print_log(VRS_PRINT_ERROR, "Unable to remove command id: %d\n", sent_cmd->id); ret = 0; } } sent_cmd = sent_cmd->next; } /* Free linked list of sent commands */ v_list_free(&sent_packet->cmds); /* Remove packet itself from the linked list of sent packet */ v_list_rem_item(&history->packets, sent_packet); free(sent_packet); ret = 1; } else { /* When acknowledged payload packet is not found in history, then it * is OK, because it is probably keep alive packet without any node * commands */ v_print_log(VRS_PRINT_DEBUG_MSG, "Packet with id: %d, not found in history\n", id); } /* When pure ack packet caused adding some fake commands to the queue, then * poke server data thread */ if( (vs_ctx != NULL) && (is_fake_cmd_received == 1) && (r_packet->data == NULL)) { sem_post(vs_ctx->data.sem); } return ret; }
/** * \brief WebSocket callback function for recived message */ void vs_ws_recv_msg_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { struct vContext *C = (struct vContext*)user_data; struct VSession *session = CTX_current_session(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); (void)ctx; if(!wslay_is_ctrl_frame(arg->opcode)) { /* Verse server uses binary message for communication */ if(arg->opcode == WSLAY_BINARY_FRAME) { struct wslay_event_msg msgarg; v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback received binary message\n"); /* Copy received data to IO context */ memcpy(io_ctx->buf, arg->msg, arg->msg_length); io_ctx->buf_size = arg->msg_length; if(session->stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { if(v_STREAM_handle_messages(C) == 0) { /* TODO: end connection */ return; } } else { if( vs_handle_handshake(C, NULL) == -1 ) { /* TODO: end connection */ return; } msgarg.opcode = WSLAY_BINARY_FRAME; msgarg.msg = (uint8_t*)io_ctx->buf; msgarg.msg_length = io_ctx->buf_size; wslay_event_queue_msg(ctx, &msgarg); } } else if(arg->opcode == WSLAY_TEXT_FRAME) { v_print_log(VRS_PRINT_ERROR, "WebSocket text frame is not supported\n"); return; } } else { /* Print opcode of control message */ v_print_log(VRS_PRINT_DEBUG_MSG, "WS Callback Received Ctrl Message: opcode: %d\n", arg->opcode); /* Is it closing message? */ if(arg->opcode & WSLAY_CONNECTION_CLOSE) { v_print_log(VRS_PRINT_DEBUG_MSG, "Close message with code: %d, message: %s\n", arg->status_code, arg->msg); if(session->stream_conn->host_state == TCP_SERVER_STATE_CLOSING) { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSED; } else { session->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; wslay_event_queue_close(ctx, WSLAY_CODE_NORMAL_CLOSURE, (uint8_t*)"Closing connection", 15); } } } }
/** * \brief The function with websocket infinite loop */ void *vs_websocket_loop(void *arg) { /* The vContext is passed as *user_data* in callback functions. */ struct vContext *C = (struct vContext*)arg; struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VStreamConn *stream_conn = CTX_current_stream_conn(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message=NULL, *s_message=NULL; wslay_event_context_ptr wslay_ctx; fd_set read_set, write_set; struct timeval tv; int ret, flags; unsigned int int_size; struct wslay_event_callbacks callbacks = { vs_recv_ws_callback_data, vs_send_ws_callback_data, NULL, NULL, NULL, NULL, vs_ws_recv_msg_callback }; /* Set socket blocking */ flags = fcntl(io_ctx->sockfd, F_GETFL, 0); fcntl(io_ctx->sockfd, F_SETFL, flags & ~O_NONBLOCK); http_handshake(io_ctx->sockfd); /* Try to get size of TCP buffer */ int_size = sizeof(int_size); getsockopt(io_ctx->sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&stream_conn->socket_buffer_size, &int_size); r_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); s_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); CTX_r_message_set(C, r_message); CTX_s_message_set(C, s_message); stream_conn->host_state = TCP_SERVER_STATE_RESPOND_METHODS; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: RESPOND_methods\n"); printf("%c[%dm", 27, 0); } /* Set non-blocking */ flags = fcntl(io_ctx->sockfd, F_GETFL, 0); fcntl(io_ctx->sockfd, F_SETFL, flags | O_NONBLOCK); wslay_event_context_server_init(&wslay_ctx, &callbacks, C); /* "Never ending" loop */ while(vsession->stream_conn->host_state != TCP_SERVER_STATE_CLOSED) { if(vs_ctx->state != SERVER_STATE_READY) { vsession->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; /* Try to close connection with websocket client */ wslay_event_queue_close(wslay_ctx, WSLAY_CODE_GOING_AWAY, (uint8_t*)"Server shutdown", /* Close message */ 15); /* The length of close message s*/ } /* Initialize read set */ FD_ZERO(&read_set); FD_SET(io_ctx->sockfd, &read_set); /* Initialize write set */ FD_ZERO(&write_set); FD_SET(io_ctx->sockfd, &write_set); /* Set timeout for select() */ if(stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { /* Use negotiated FPS */ tv.tv_sec = 0; tv.tv_usec = 1000000/vsession->fps_host; } else { /* User have to send something in 30 seconds */ tv.tv_sec = VRS_TIMEOUT; tv.tv_usec = 0; } if( (ret = select(io_ctx->sockfd+1, &read_set, &write_set, NULL, /* Exception*/ &tv)) == -1) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "%s:%s():%d select(): %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); goto end; /* Was event on the listen socket */ } else if(ret>0){ if(FD_ISSET(io_ctx->sockfd, &read_set)) { if( wslay_event_recv(wslay_ctx) != 0 ) { goto end; } } else if (FD_ISSET(io_ctx->sockfd, &write_set)) { if( wslay_event_send(wslay_ctx) != 0 ) { goto end; } } } if(stream_conn->host_state == TCP_SERVER_STATE_STREAM_OPEN) { /* Check if there is any command in outgouing queue * and eventually pack these commands to buffer */ if((ret = v_STREAM_pack_message(C)) == 0 ) { goto end; } /* When at least one command was packed to buffer, then * queue this buffer to WebSocket layer */ if(ret == 1) { struct wslay_event_msg msgarg; msgarg.opcode = WSLAY_BINARY_FRAME; msgarg.msg = (uint8_t*)io_ctx->buf; msgarg.msg_length = io_ctx->buf_size; wslay_event_queue_msg(wslay_ctx, &msgarg); } } } end: if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: CLOSING\n"); printf("%c[%dm", 27, 0); } /* Set up TCP CLOSING state (non-blocking) */ vs_CLOSING(C); /* Receive and Send messages are not neccessary any more */ if(r_message!=NULL) { free(r_message); r_message = NULL; CTX_r_message_set(C, NULL); } if(s_message!=NULL) { free(s_message); s_message = NULL; CTX_s_message_set(C, NULL); } /* TCP connection is considered as CLOSED, but it is not possible to use * this connection for other client */ stream_conn->host_state = TCP_SERVER_STATE_CLOSED; /* NULL pointer at stream connection */ CTX_current_stream_conn_set(C, NULL); /* Set TCP connection to CLOSED */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: CLOSED\n"); printf("%c[%dm", 27, 0); } pthread_mutex_lock(&vs_ctx->data.mutex); /* Unsubscribe this session (this avatar) from all nodes */ vs_node_free_avatar_reference(vs_ctx, vsession); /* Try to destroy avatar node */ vs_node_destroy_avatar_node(vs_ctx, vsession); pthread_mutex_unlock(&vs_ctx->data.mutex); /* This session could be used again for authentication */ stream_conn->host_state=TCP_SERVER_STATE_LISTEN; /* Clear session flags */ vsession->flags = 0; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: LISTEN\n"); printf("%c[%dm", 27, 0); } free(C); C = NULL; pthread_exit(NULL); return NULL; }
/** * \brief This function handles messages received during verse handshake * and it can create new thread for datagram connection. */ int vs_handle_handshake(struct vContext *C, char *u_name) { struct VSession *vsession = CTX_current_session(C); struct VStreamConn *stream_conn = CTX_current_stream_conn(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); int ret; /* Make sure, that buffer contains at least Verse message * header. If this condition is not reached, then somebody tries * to do some very bad things! .. Close this connection. */ if(io_ctx->buf_size < VERSE_MESSAGE_HEADER_SIZE) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "received buffer too small %d\n", io_ctx->buf_size); return -1; } switch(stream_conn->host_state) { case TCP_SERVER_STATE_RESPOND_METHODS: ret = vs_RESPOND_methods_loop(C); if(ret==1) { stream_conn->host_state = TCP_SERVER_STATE_RESPOND_USRAUTH; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: RESPOND_userauth\n"); printf("%c[%dm", 27, 0); } } else { return -1; } break; case TCP_SERVER_STATE_RESPOND_USRAUTH: ret = vs_RESPOND_userauth_loop(C); if(ret==1) { stream_conn->host_state = TCP_SERVER_STATE_NEGOTIATE_COOKIE_DED; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: NEGOTIATE_cookie_ded\n"); printf("%c[%dm", 27, 0); } } else { vsession->usr_auth_att++; if(vsession->usr_auth_att >= MAX_USER_AUTH_ATTEMPTS) { return -1; } } break; #ifdef WITH_KERBEROS case TCP_SERVER_STATE_RESPOND_KRB_AUTH: ret = vs_RESPOND_krb_auth_loop(C, u_name); if (ret == 1) { stream_conn->host_state = TCP_SERVER_STATE_NEGOTIATE_COOKIE_DED; if (is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: NEGOTIATE_cookie_ded\n"); printf("%c[%dm", 27, 0); } } else { return -1; } break; #endif case TCP_SERVER_STATE_NEGOTIATE_COOKIE_DED: ret = vs_NEGOTIATE_cookie_ded_loop(C); if(ret==1) { stream_conn->host_state = TCP_SERVER_STATE_NEGOTIATE_NEWHOST; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: NEGOTIATE_newhost\n"); printf("%c[%dm", 27, 0); } } else { return -1; } break; case TCP_SERVER_STATE_NEGOTIATE_NEWHOST: /* Wait VERSE_TIMEOT seconds to confirming proposed URL */ ret = vs_NEGOTIATE_newhost_loop(C); if(ret == 1) { if(vsession->flags & VRS_TP_UDP) { /* When URL was confirmed, then go to the end state */ return -1; } else if(vsession->flags & VRS_TP_TCP) { stream_conn->host_state = TCP_SERVER_STATE_STREAM_OPEN; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: STREAM_OPEN\n"); printf("%c[%dm", 27, 0); } vs_STREAM_OPEN_tcp_loop(C); return -1; } else if(vsession->flags & VRS_TP_WEBSOCKET) { stream_conn->host_state = TCP_SERVER_STATE_STREAM_OPEN; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: STREAM_OPEN\n"); printf("%c[%dm", 27, 0); } } } else { /* When thread was not confirmed, then try to cancel * UDP thread*/ if(pthread_cancel(vsession->udp_thread) != 0) { v_print_log(VRS_PRINT_DEBUG_MSG, "UDP thread was not canceled\n"); } return -1; } break; } return 1; }
/* Create new UDP connection to the server */ struct VDgramConn *vc_create_client_dgram_conn(struct vContext *C) { struct VSession *vsession = CTX_current_session(C); struct VDgramConn *dgram_conn = NULL; struct addrinfo hints, *result, *rp; int sockfd = -1; int flag, ret; struct VURL url; /* Seed random number generator, */ #ifdef __APPLE__ sranddev(); /* Other BSD based systems probably support this or similar function too. */ #else /* Other systems have to use this evil trick */ struct timeval tv; gettimeofday(&tv, NULL); srand(tv.tv_sec - tv.tv_usec); #endif if (v_url_parse(vsession->host_url, &url) != 1) { goto end; } else { /* v_print_url(VRS_PRINT_DEBUG_MSG, &url); */ } memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Allow datagram protocol */ hints.ai_flags = 0; /* No flags required */ hints.ai_protocol = IPPROTO_UDP; /* Allow UDP protocol only */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { if(url.ip_ver==IPV6) { v_print_log(VRS_PRINT_DEBUG_MSG, "Try to connect to: [%s]:%s\n", url.node, url.service); } else { v_print_log(VRS_PRINT_DEBUG_MSG, "Try to connect to: %s:%s\n", url.node, url.service); } } if( (ret = getaddrinfo(url.node, url.service, &hints, &result)) !=0 ) { v_print_log(VRS_PRINT_ERROR, "getaddrinfo(): %s\n", gai_strerror(ret)); goto end; } /* Try to use addrinfo from getaddrinfo() */ for(rp=result; rp!=NULL; rp=rp->ai_next) { if( (sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) { v_print_log(VRS_PRINT_ERROR, "socket(): %s\n", strerror(errno)); continue; } else { /* Try to "connect" to this address ... the client will be able to send and * receive packets only from this address. */ if(connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1) break; close(sockfd); sockfd = -1; } } if(rp == NULL) { if(is_log_level(VRS_PRINT_ERROR)) { if(url.ip_ver==IPV6) { v_print_log(VRS_PRINT_ERROR, "Could not connect to the [%s]:%s\n", url.node, url.service); } else { v_print_log(VRS_PRINT_ERROR, "Could not connect to the %s:%s\n", url.node, url.service); } } freeaddrinfo(result); goto end; } if( (dgram_conn = (struct VDgramConn*)calloc(1, sizeof(struct VDgramConn))) == NULL) { v_print_log(VRS_PRINT_ERROR, "calloc(): %s\n", strerror(errno)); freeaddrinfo(result); if(sockfd != -1) { close(sockfd); } goto end; } /* Initialize datagram connection */ v_conn_dgram_init(dgram_conn); /* Use first successfully assigned socket */ dgram_conn->io_ctx.sockfd = sockfd; /* Set socket non-blocking */ flag = fcntl(dgram_conn->io_ctx.sockfd, F_GETFL, 0); if( (fcntl(dgram_conn->io_ctx.sockfd, F_SETFL, flag | O_NONBLOCK)) == -1) { v_print_log(VRS_PRINT_ERROR, "fcntl(): %s\n", strerror(errno)); free(dgram_conn); dgram_conn = NULL; if(sockfd != -1) { close(sockfd); } freeaddrinfo(result); goto end; } /* Set socket to reuse address */ flag = 1; if( setsockopt(dgram_conn->io_ctx.sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1) { v_print_log(VRS_PRINT_ERROR, "setsockopt(): %s\n", strerror(errno)); free(dgram_conn); if(sockfd != -1) { close(sockfd); } dgram_conn = NULL; goto end; } /* Set address of peer and host */ if(rp->ai_family==AF_INET) { /* Address type of client */ dgram_conn->io_ctx.host_addr.ip_ver = IPV4; dgram_conn->io_ctx.host_addr.protocol = UDP; /* Address of peer */ dgram_conn->io_ctx.peer_addr.ip_ver = IPV4; dgram_conn->io_ctx.peer_addr.protocol = UDP; dgram_conn->io_ctx.peer_addr.port = ntohs(((struct sockaddr_in*)rp->ai_addr)->sin_port); memcpy(&dgram_conn->io_ctx.peer_addr.addr.ipv4, rp->ai_addr, rp->ai_addrlen); /* Address of peer (reference in connection) */ dgram_conn->peer_address.ip_ver = IPV4; dgram_conn->peer_address.protocol = UDP; dgram_conn->peer_address.port = ntohs(((struct sockaddr_in*)rp->ai_addr)->sin_port); memcpy(&dgram_conn->peer_address.addr.ipv4, rp->ai_addr, rp->ai_addrlen); } else if(rp->ai_family==AF_INET6) { /* Address type of client */ dgram_conn->io_ctx.host_addr.ip_ver = IPV6; dgram_conn->io_ctx.host_addr.protocol = UDP; /* Address of peer */ dgram_conn->io_ctx.peer_addr.ip_ver = IPV6; dgram_conn->io_ctx.peer_addr.protocol = UDP; dgram_conn->io_ctx.peer_addr.port = ntohs(((struct sockaddr_in6*)rp->ai_addr)->sin6_port); memcpy(&dgram_conn->io_ctx.peer_addr.addr.ipv6, rp->ai_addr, rp->ai_addrlen); /* Address of peer (reference in connection) */ dgram_conn->peer_address.ip_ver = IPV6; dgram_conn->peer_address.protocol = UDP; dgram_conn->peer_address.port = ntohs(((struct sockaddr_in6*)rp->ai_addr)->sin6_port); memcpy(&dgram_conn->peer_address.addr.ipv6, rp->ai_addr, rp->ai_addrlen); } freeaddrinfo(result); /* When DTLS was negotiated, then set flag */ if(url.security_protocol == VRS_SEC_DATA_TLS) { #if (defined WITH_OPENSSL) && OPENSSL_VERSION_NUMBER>=0x10000000 dgram_conn->io_ctx.flags |= SOCKET_SECURED; #else v_print_log(VRS_PRINT_ERROR, "Server tries to force client to use secured connection, but it is not supported\n"); goto end; #endif } #ifdef WITH_OPENSSL /* Create BIO, connect and set to already connected */ if( (dgram_conn->io_ctx.bio = BIO_new_dgram(dgram_conn->io_ctx.sockfd, BIO_CLOSE)) == NULL) { v_print_log(VRS_PRINT_ERROR, "BIO_new_dgram()\n"); goto end; } /* Try to do PMTU discovery */ if( BIO_ctrl(dgram_conn->io_ctx.bio, BIO_CTRL_DGRAM_MTU_DISCOVER, 0, NULL) < 0) { v_print_log(VRS_PRINT_ERROR, "BIO_ctrl()\n"); goto end; } /* Try to get MTU from the bio */ ret = BIO_ctrl(dgram_conn->io_ctx.bio, BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL); if(ret > 0) { dgram_conn->io_ctx.mtu = ret; v_print_log(VRS_PRINT_DEBUG_MSG, "PMTU: %d\n", dgram_conn->io_ctx.mtu); } else { dgram_conn->io_ctx.mtu = DEFAULT_MTU; v_print_log(VRS_PRINT_DEBUG_MSG, "Default MTU: %d\n", dgram_conn->io_ctx.mtu); } #else dgram_conn->io_ctx.mtu = DEFAULT_MTU; #endif /* Set up necessary flag for V_CTX (client will be able to send and receive packets only to/from server) */ dgram_conn->io_ctx.flags |= SOCKET_CONNECTED; dgram_conn->host_id = (unsigned int)rand(); end: v_url_clear(&url); return dgram_conn; }
/** * \brief This function is never ending loop of server state STREAM_OPEN. * This loop is used, when Verse server uses TCP for data exchange (not * UDP nor WebSocket) */ int vs_STREAM_OPEN_tcp_loop(struct vContext *C) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct VSession *vsession = CTX_current_session(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct timeval tv; fd_set set; int flag, ret, error; /* Set socket non-blocking */ flag = fcntl(io_ctx->sockfd, F_GETFL, 0); if( (fcntl(io_ctx->sockfd, F_SETFL, flag | O_NONBLOCK)) == -1) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "fcntl(): %s\n", strerror(errno)); return -1; } /* "Never ending" loop */ while(1) { FD_ZERO(&set); FD_SET(io_ctx->sockfd, &set); /* Use negotiated FPS */ tv.tv_sec = 0; tv.tv_usec = 1000000/vsession->fps_host; /* Wait for recieved data */ if( (ret = select(io_ctx->sockfd+1, &set, NULL, NULL, &tv)) == -1) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "%s:%s():%d select(): %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); goto end; /* Was event on the listen socket */ } else if(ret>0 && FD_ISSET(io_ctx->sockfd, &set)) { /* Try to receive data through SSL connection */ if( v_tcp_read(io_ctx, &error) <= 0 ) { goto end; } if(v_STREAM_handle_messages(C) == 0) { goto end; } /* When some payload data were received, then poke data thread */ sem_post(vs_ctx->data.sem); } if( (ret = v_STREAM_pack_message(C)) == 0 ) { goto end; } /* Send command to the client */ if(ret == 1) { if( v_tcp_write(io_ctx, &error) <= 0) { goto end; } } } end: /* Set socket blocking again */ flag = fcntl(io_ctx->sockfd, F_GETFL, 0); if( (fcntl(io_ctx->sockfd, F_SETFL, flag & ~O_NONBLOCK)) == -1) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "fcntl(): %s\n", strerror(errno)); return -1; } return 0; }
/** * \brief This function is the callback function for received Confirm_L * commands in REQUEST state. */ static int vc_REQUEST_CONFIRM_L_cb(struct vContext *C, struct Generic_Cmd *cmd) { struct VC_CTX *vc_ctx = CTX_client_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VDgramConn *dgram_conn = CTX_current_dgram_conn(C); struct Negotiate_Cmd *confirm_l_cmd = (struct Negotiate_Cmd*)cmd; if(confirm_l_cmd->feature == FTR_TOKEN) { /* This block of code checks if the server confirmed send token. */ if( vsession->host_token.str != NULL && confirm_l_cmd->count == 1 && strcmp((char*)confirm_l_cmd->value[0].string8.str, vsession->host_token.str)==0 ) { v_print_log(VRS_PRINT_DEBUG_MSG, "Local TOKEN: %s confirmed\n", confirm_l_cmd->value[0].string8.str); return 1; } else { v_print_log(VRS_PRINT_WARNING, "COOCKIE: %s not confirmed\n", vsession->peer_token.str); return 0; } } /* Server should confirm client proposal of Congestion Control (local) */ if(confirm_l_cmd->feature == FTR_CC_ID) { /* TODO: better implementation */ if(confirm_l_cmd->count == 1 && /* Any confirm command has to include only one value */ confirm_l_cmd->value[0].uint8 == CC_NONE) { /* list of supported methods */ v_print_log(VRS_PRINT_DEBUG_MSG, "Local Congestion Control ID: %d confirmed\n", confirm_l_cmd->value[0].uint8); dgram_conn->cc_meth = CC_NONE; return 1; } else { v_print_log(VRS_PRINT_ERROR, "Unsupported Congestion Control\n"); return 0; } } /* Server should confirm client proposal of Flow Control Window scale (local) */ if(confirm_l_cmd->feature == FTR_RWIN_SCALE) { if(confirm_l_cmd->count == 1 ) { if(vc_ctx->rwin_scale == confirm_l_cmd->value[0].uint8) { v_print_log(VRS_PRINT_DEBUG_MSG, "Scale of host RWIN: %d confirmed\n", vc_ctx->rwin_scale); dgram_conn->rwin_host_scale = vc_ctx->rwin_scale; return 1; } else { v_print_log(VRS_PRINT_ERROR, "Scale of RWIN: %d != %d wasn't confirmed\n", vc_ctx->rwin_scale, confirm_l_cmd->value[0].uint8); dgram_conn->rwin_host_scale = 0; return 0; } } else { v_print_log(VRS_PRINT_ERROR, "One value of RWIN scale should to be confirmed\n"); return 0; } } /* Server should confirm client proposal of command compression */ if(confirm_l_cmd->feature == FTR_CMD_COMPRESS) { if(confirm_l_cmd->count == 1) { if(confirm_l_cmd->value[0].uint8 == CMPR_NONE || confirm_l_cmd->value[0].uint8 == CMPR_ADDR_SHARE) { v_print_log(VRS_PRINT_DEBUG_MSG, "Local Command Compression: %d confirmed\n", confirm_l_cmd->value[0].uint8); dgram_conn->host_cmd_cmpr = confirm_l_cmd->value[0].uint8; return 1; } else { v_print_log(VRS_PRINT_ERROR, "Unsupported Command Compress\n"); return 0; } } } /* Ignore unknown feature */ return 1; }
/** * \brief This function is called, when server is in NEGOTIATE_cookie_ded state * * This function can create new thread for datagram connection */ int vs_NEGOTIATE_cookie_ded_loop(struct vContext *C) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VMessage *r_message = CTX_r_message(C); struct VMessage *s_message = CTX_s_message(C); int i, j, ret; unsigned short buffer_pos = 0; int host_url_proposed = 0, host_cookie_proposed = 0, peer_cookie_confirmed = 0, ded_confirmed = 0, client_name_proposed = 0, client_version_proposed = 0; struct timeval tv; struct VURL url; /* Reset content of received message */ memset(r_message, 0, sizeof(struct VMessage)); /* Unpack Verse message header */ buffer_pos += v_unpack_message_header(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); /* Unpack all system commands */ buffer_pos += v_unpack_message_system_commands(&io_ctx->buf[buffer_pos], (io_ctx->buf_size - buffer_pos), r_message); v_print_receive_message(C); /* Process all received system commands */ for(i=0; i<MAX_SYSTEM_COMMAND_COUNT && r_message->sys_cmd[i].cmd.id!=CMD_RESERVED_ID; i++) { switch(r_message->sys_cmd[i].cmd.id) { case CMD_CHANGE_R_ID: /* Client has to propose url in this state */ if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_HOST_URL) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { if(vsession->host_url!=NULL) { free(vsession->host_url); } /* Only first proposed URL will be used */ vsession->host_url = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); /* Check if proposed URL is correct */ ret = v_parse_url(vsession->host_url, &url); if(ret == 1) host_url_proposed = 1; else host_url_proposed = 0; } /* Client has to propose host cookie in this state */ } else if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_COOKIE) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { if(vsession->host_cookie.str != NULL) { free(vsession->host_cookie.str); } vsession->host_cookie.str = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); host_cookie_proposed = 1; } } else { v_print_log(VRS_PRINT_WARNING, "This feature id: %d is not supported in this state\n", r_message->sys_cmd[i].negotiate_cmd.feature); } break; case CMD_CHANGE_L_ID: /* Client could propose client name and version */ if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_CLIENT_NAME) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { /* Only first proposed client name will be used */ vsession->client_name = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); client_name_proposed = 1; } } else if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_CLIENT_VERSION) { if(r_message->sys_cmd[i].negotiate_cmd.count > 0) { /* Only first proposed client name will be used */ vsession->client_version = strdup((char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); client_version_proposed = 1; } } break; case CMD_CONFIRM_R_ID: /* Client has to confirm peer cookie in this state */ if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_COOKIE) { if (r_message->sys_cmd[i].negotiate_cmd.count == 1) { if(vsession->peer_cookie.str != NULL && strcmp(vsession->peer_cookie.str, (char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str) == 0) { gettimeofday(&tv, NULL); vsession->peer_cookie.tv.tv_sec = tv.tv_sec; vsession->peer_cookie.tv.tv_usec = tv.tv_usec; peer_cookie_confirmed = 1; } } } else { v_print_log(VRS_PRINT_WARNING, "This feature id: %d is not supported in this state\n", r_message->sys_cmd[i].negotiate_cmd.feature); } break; case CMD_CONFIRM_L_ID: /* Client has to confirm DED in this state */ if(r_message->sys_cmd[i].negotiate_cmd.feature == FTR_DED) { if(r_message->sys_cmd[i].negotiate_cmd.count == 1) { if(vsession->ded.str != NULL && strcmp(vsession->ded.str, (char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str) == 0) { ded_confirmed = 1; } else { printf("%s != %s\n", vsession->ded.str, (char*)r_message->sys_cmd[i].negotiate_cmd.value[0].string8.str); } } } break; default: v_print_log(VRS_PRINT_WARNING, "This command id: %d is not supported in this state\n", r_message->sys_cmd[i].cmd.id); break; } } /* Send response on cookie request */ if(host_url_proposed==1 && host_cookie_proposed==1 && peer_cookie_confirmed==1 && ded_confirmed==1) { struct vContext *new_C; char trans_proto[4]; char sec_proto[5]; int cmd_rank = 0; buffer_pos = VERSE_MESSAGE_HEADER_SIZE; /* Copy address of peer */ memcpy(&vsession->peer_address, &io_ctx->peer_addr, sizeof(struct VNetworkAddress)); /* Do not confirm proposed URL */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_R_ID, FTR_HOST_URL, NULL); /* Find first unused port from port range */ for(i=vs_ctx->port_low, j=0; i<vs_ctx->port_high; i++, j++) { if(!(vs_ctx->port_list[j].flag & SERVER_PORT_USED)) { vsession->dgram_conn->io_ctx.host_addr.port = vs_ctx->port_list[j].port_number; vs_ctx->port_list[j].flag |= SERVER_PORT_USED; break; } } /* Do not allow unsecure TCP data connection */ if(url.transport_protocol == VRS_TP_TCP) { url.security_protocol = VRS_SEC_DATA_TLS; } /* Copy settings about data connection to the session */ vsession->flags |= url.security_protocol; vsession->flags |= url.transport_protocol; if(vsession->flags & VRS_TP_UDP) { strncpy(trans_proto, "udp", 3); trans_proto[3] = '\0'; /* Create copy of new Verse context for new thread */ new_C = (struct vContext*)calloc(1, sizeof(struct vContext)); memcpy(new_C, C, sizeof(struct vContext)); /* Try to create new thread */ if((ret = pthread_create(&vsession->udp_thread, NULL, vs_main_dgram_loop, (void*)new_C)) != 0) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "pthread_create(): %s\n", strerror(errno)); ret = 0; goto end; } /* Wait for datagram thread to be in LISTEN state */ while(vsession->dgram_conn->host_state != UDP_SERVER_STATE_LISTEN) { /* Sleep 1 milisecond */ usleep(1000); } } else if(vsession->flags & VRS_TP_TCP) { strncpy(trans_proto, "tcp", 3); trans_proto[3] = '\0'; } else if(vsession->flags & VRS_TP_WEBSOCKET) { strncpy(trans_proto, "wss", 3); trans_proto[3] = '\0'; } #if (defined WITH_OPENSSL) && OPENSSL_VERSION_NUMBER>=0x10000000 if(url.security_protocol==VRS_SEC_DATA_NONE || !(vs_ctx->security_protocol & VRS_SEC_DATA_TLS)) { strncpy(sec_proto, "none", 4); sec_proto[4] = '\0'; } else if(url.security_protocol==VRS_SEC_DATA_TLS) { if(vsession->flags & VRS_TP_UDP) { strncpy(sec_proto, "dtls", 4); sec_proto[4] = '\0'; } else if((vsession->flags & VRS_TP_TCP) || (vsession->flags & VRS_TP_WEBSOCKET)) { strncpy(sec_proto, "tls", 3); sec_proto[3] = '\0'; } } else { strncpy(sec_proto, "none", 4); sec_proto[4] = '\0'; } #else strncpy(sec_proto, "none", 4); sec_proto[4] = '\0'; #endif /* Free proposed and now obsolete URL */ if(vsession->host_url != NULL) { free(vsession->host_url); vsession->host_url = NULL; } /* Set right host URL */ vsession->host_url = calloc(UCHAR_MAX, sizeof(char)); if(url.ip_ver==IPV6) { sprintf(vsession->host_url, "verse-%s-%s://[%s]:%d", trans_proto, sec_proto, url.node, vsession->dgram_conn->io_ctx.host_addr.port); } else { sprintf(vsession->host_url, "verse-%s-%s://%s:%d", trans_proto, sec_proto, url.node, vsession->dgram_conn->io_ctx.host_addr.port); } v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CHANGE_L_ID, FTR_HOST_URL, vsession->host_url, NULL); /* Set time for the host cookie */ gettimeofday(&tv, NULL); vsession->host_cookie.tv.tv_sec = tv.tv_sec; vsession->host_cookie.tv.tv_usec = tv.tv_usec; /* Send confirmation about host cookie */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_R_ID, FTR_COOKIE, vsession->host_cookie.str, NULL); /* Send confirmation about client name */ if(client_name_proposed == 1) { v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_NAME, vsession->client_name, NULL); } /* Send confirmation about client version only in situation, when * client proposed client name too */ if(client_version_proposed == 1) { if(client_name_proposed == 1) { v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_VERSION, vsession->client_version, NULL); } else { /* Client version without client name is not allowed */ v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank++, CMD_CONFIRM_L_ID, FTR_CLIENT_VERSION, NULL); } } /* Pack all system commands to the buffer */ buffer_pos += v_pack_stream_system_commands(s_message, &io_ctx->buf[buffer_pos]); /* Update length of message in the header (data in buffer) */ s_message->header.version = VRS_VERSION; s_message->header.len = io_ctx->buf_size = buffer_pos; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); v_print_send_message(C); ret = 1; } else { ret = 0; } end: v_clear_url(&url); return ret; }
/** * \brief This function try to pack message that is going to be * sent in STREAM OPEN state * * \param[in] *C The pointer at context * * \return This function return 1, when there is something to send, * it returns -1, when there is nothing to send and it returns 0, when * there is some error */ int v_STREAM_pack_message(struct vContext *C) { struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct VSession *vsession = CTX_current_session(C); struct VStreamConn *conn = CTX_current_stream_conn(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VMessage *s_message = CTX_s_message(C); struct Generic_Cmd *cmd, *fake_cmd; int ret = -1, queue_size = 0, buffer_pos = 0, prio_cmd_count, cmd_rank=0; int8 cmd_share; int16 prio, max_prio, min_prio; uint16 cmd_count, cmd_len, prio_win, swin, sent_size, tot_cmd_size; real32 prio_sum_high, prio_sum_low, r_prio; int is_fake_cmd_received = 0; /* Is here something to send? */ if((v_out_queue_get_count(vsession->out_queue) > 0) || (vsession->tmp_flags & SYS_CMD_NEGOTIATE_FPS)) { /* Get current size of data in TCP outgoing buffer */ #ifdef __linux__ if( ioctl(io_ctx->sockfd, SIOCOUTQ, &queue_size) == -1 ) { v_print_log(VRS_PRINT_ERROR, "ioctl(%d, SIOCOUTQ, ...): %s\n", io_ctx->sockfd, strerror(errno)); return 0; } #endif /* Compute, how many data could be added to the TCP stack? */ swin = conn->socket_buffer_size - queue_size; buffer_pos = VERSE_MESSAGE_HEADER_SIZE; s_message->sys_cmd[0].cmd.id = CMD_RESERVED_ID; /* When negotiated and used FPS is different, then pack negotiate command * for FPS */ if(vsession->fps_host != vsession->fps_peer) { cmd_rank += v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank, CMD_CHANGE_L_ID, FTR_FPS, &vsession->fps_host, NULL); } else { if(vsession->tmp_flags & SYS_CMD_NEGOTIATE_FPS) { cmd_rank += v_add_negotiate_cmd(s_message->sys_cmd, cmd_rank, CMD_CONFIRM_L_ID, FTR_FPS, &vsession->fps_peer, NULL); /* Send confirmation only once for received system command */ vsession->tmp_flags &= ~SYS_CMD_NEGOTIATE_FPS; } } buffer_pos += v_pack_stream_system_commands(s_message, &io_ctx->buf[buffer_pos]); max_prio = v_out_queue_get_max_prio(vsession->out_queue); min_prio = v_out_queue_get_min_prio(vsession->out_queue); prio_sum_high = v_out_queue_get_prio_sum_high(vsession->out_queue); prio_sum_low = v_out_queue_get_prio_sum_low(vsession->out_queue); v_print_log(VRS_PRINT_DEBUG_MSG, "Packing prio queues, cmd count: %d\n", v_out_queue_get_count(vsession->out_queue)); /* Go through all priorities and pick commands from priority queues */ for(prio = max_prio; prio >= min_prio; prio--) { prio_cmd_count = v_out_queue_get_count_prio(vsession->out_queue, prio); if(prio_cmd_count > 0) { r_prio = v_out_queue_get_prio(vsession->out_queue, prio); /* Compute size of buffer that could be occupied by * commands from this queue */ if(prio >= VRS_DEFAULT_PRIORITY) { prio_win = ((swin - buffer_pos)*r_prio)/prio_sum_high; } else { prio_win = ((swin - buffer_pos)*r_prio)/prio_sum_low; } /* Debug print */ v_print_log(VRS_PRINT_DEBUG_MSG, "Queue: %d, count: %d, r_prio: %6.3f, prio_win: %d\n", prio, prio_cmd_count, r_prio, prio_win); /* Get total size of commands that were stored in queue (sent_size) */ sent_size = 0; tot_cmd_size = 0; while(prio_cmd_count > 0) { cmd_share = 0; cmd_count = 0; cmd_len = prio_win - sent_size; /* Pack commands from queues with high priority to the buffer */ cmd = v_out_queue_pop(vsession->out_queue, prio, &cmd_count, &cmd_share, &cmd_len); if(cmd != NULL) { /* Is this command fake command? */ if(cmd->id < MIN_CMD_ID) { if(cmd->id == FAKE_CMD_CONNECT_TERMINATE) { /* Close connection */ struct VS_CTX *vs_ctx = CTX_server_ctx(C); if(vs_ctx != NULL) { vsession->stream_conn->host_state = TCP_SERVER_STATE_CLOSING; } else { vsession->stream_conn->host_state = TCP_CLIENT_STATE_CLOSING; } } else if(cmd->id == FAKE_CMD_FPS) { struct Fps_Cmd *fps_cmd = (struct Fps_Cmd*)cmd; /* Change value of FPS. It will be sent in negotiate command * until it is confirmed be the peer (server) */ vsession->fps_host = fps_cmd->fps; } } else { buffer_pos += tot_cmd_size = v_cmd_pack(&io_ctx->buf[buffer_pos], cmd, v_cmd_size(cmd), 0); if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 32); v_cmd_print(VRS_PRINT_DEBUG_MSG, cmd); printf("%c[%dm", 27, 0); } sent_size += tot_cmd_size; } fake_cmd = v_cmd_fake_ack(cmd); if(fake_cmd != NULL) { is_fake_cmd_received = 1; /* Push command to the queue of incoming commands */ v_in_queue_push(vsession->in_queue, fake_cmd); /* Print content of fake command */ v_fake_cmd_print(VRS_PRINT_DEBUG_MSG, fake_cmd); } /* It is not necessary to put cmd to history of sent commands, * when TCP is used. */ v_cmd_destroy(&cmd); prio_cmd_count--; } else { break; } } } } s_message->header.len = io_ctx->buf_size = buffer_pos; s_message->header.version = VRS_VERSION; /* Pack header to the beginning of the buffer */ v_pack_message_header(s_message, io_ctx->buf); /* Debug print of command to be send */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { v_print_send_message(C); } /* When pure ack packet caused adding some fake commands to the queue, then * poke server data thread */ if( (vs_ctx != NULL) && (is_fake_cmd_received == 1)) { sem_post(vs_ctx->data.sem); } ret = 1; } return ret; }
/** * \brief Main function for new thread. This thread is created for new * connection with client. This thread will try to authenticate new user * and negotiate new udp port. */ void *vs_tcp_conn_loop(void *arg) { struct vContext *C = (struct vContext*)arg; struct VS_CTX *vs_ctx = CTX_server_ctx(C); struct VSession *vsession = CTX_current_session(C); struct IO_CTX *io_ctx = CTX_io_ctx(C); struct VStreamConn *stream_conn = CTX_current_stream_conn(C); struct VMessage *r_message=NULL, *s_message=NULL; struct timeval tv; fd_set set; int error, ret; void *udp_thread_result; unsigned int int_size; /* Try to get size of TCP buffer */ int_size = sizeof(int_size); getsockopt(io_ctx->sockfd, SOL_SOCKET, SO_RCVBUF, (void *)&stream_conn->socket_buffer_size, &int_size); #ifdef WITH_OPENSSL /* Try to do TLS handshake with client */ if(vs_TLS_handshake(C)!=1) { goto end; } #endif r_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); s_message = (struct VMessage*)calloc(1, sizeof(struct VMessage)); CTX_r_message_set(C, r_message); CTX_s_message_set(C, s_message); stream_conn->host_state = TCP_SERVER_STATE_RESPOND_METHODS; if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: RESPOND_methods\n"); printf("%c[%dm", 27, 0); } /* "Never ending" loop */ while(1) { FD_ZERO(&set); FD_SET(io_ctx->sockfd, &set); tv.tv_sec = VRS_TIMEOUT; /* User have to send something in 30 seconds */ tv.tv_usec = 0; if( (ret = select(io_ctx->sockfd+1, &set, NULL, NULL, &tv)) == -1) { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "%s:%s():%d select(): %s\n", __FILE__, __FUNCTION__, __LINE__, strerror(errno)); goto end; /* Was event on the listen socket */ } else if(ret>0 && FD_ISSET(io_ctx->sockfd, &set)) { /* Try to receive data through TCP connection */ if( v_tcp_read(io_ctx, &error) <= 0 ) { goto end; } /* Handle verse handshake at TCP connection */ if( (ret = vs_handle_handshake(C)) == -1) { goto end; } /* When there is something to send, then send it to peer */ if( ret == 1 ) { /* Send response message to the client */ if( (ret = v_tcp_write(io_ctx, &error)) <= 0) { goto end; } } } else { if(is_log_level(VRS_PRINT_ERROR)) v_print_log(VRS_PRINT_ERROR, "No response in %d seconds\n", VRS_TIMEOUT); goto end; } } end: if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: CLOSING\n"); printf("%c[%dm", 27, 0); } /* Set up TCP CLOSING state (non-blocking) */ vs_CLOSING(C); /* Receive and Send messages are not neccessary any more */ if(r_message != NULL) { free(r_message); r_message = NULL; CTX_r_message_set(C, NULL); } if(s_message != NULL) { free(s_message); s_message = NULL; CTX_s_message_set(C, NULL); } /* TCP connection is considered as CLOSED, but it is not possible to use * this connection for other client */ stream_conn->host_state = TCP_SERVER_STATE_CLOSED; /* NULL pointer at stream connection */ CTX_current_stream_conn_set(C, NULL); /* Set TCP connection to CLOSED */ if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: CLOSED\n"); printf("%c[%dm", 27, 0); } /* Was udp thread created? */ if(vsession->udp_thread != 0 ) { /* Wait for UDP thread (this is blocking operation) */ v_print_log(VRS_PRINT_DEBUG_MSG, "Waiting for join with UDP thread ...\n"); if(pthread_join(vsession->udp_thread, &udp_thread_result) != 0) { v_print_log(VRS_PRINT_DEBUG_MSG, "UDP thread was not joined\n"); } } pthread_mutex_lock(&vs_ctx->data.mutex); /* Unsubscribe this session (this avatar) from all nodes */ vs_node_free_avatar_reference(vs_ctx, vsession); /* Try to destroy avatar node */ vs_node_destroy_avatar_node(vs_ctx, vsession); pthread_mutex_unlock(&vs_ctx->data.mutex); /* This session could be used again for authentication */ stream_conn->host_state = TCP_SERVER_STATE_LISTEN; /* Clear session flags */ vsession->flags = 0; if(vsession->peer_cookie.str != NULL) { free(vsession->peer_cookie.str); vsession->peer_cookie.str = NULL; } if(vsession->ded.str != NULL) { free(vsession->ded.str); vsession->ded.str = NULL; } if(vsession->client_name != NULL) { free(vsession->client_name); vsession->client_name = NULL; } if(vsession->client_version != NULL) { free(vsession->client_version); vsession->client_version = NULL; } if(is_log_level(VRS_PRINT_DEBUG_MSG)) { printf("%c[%d;%dm", 27, 1, 31); v_print_log(VRS_PRINT_DEBUG_MSG, "Server TCP state: LISTEN\n"); printf("%c[%dm", 27, 0); } free(C); C = NULL; pthread_exit(NULL); return NULL; }