/* * housekeeping.c * * housekeeping thread functions. * * Copyright (c) 2004 Todd MacDermid * */ #include #include #include #include #include #include #include #include #include #include void timeout_action(cutlass_t *cut_handle, conn_t *conn) { struct cut_action_obj action_obj; if(cut_handle->action_handlers[CUT_USER_CONN] != NULL) { memset(&action_obj, 0, sizeof(struct cut_action_obj)); action_obj.action_type = CUT_USER_TIMEOUT; memcpy(&(action_obj.remote_addr), &(conn->remote_addr), sizeof(struct sockaddr_in)); strncpy(action_obj.remote_nick, conn->conn_name, CUTLASS_NAME_LEN-1); cutlass_action(cut_handle, conn, &action_obj); } } void shutdown_action(cutlass_t *cut_handle, conn_t *conn) { struct cut_action_obj action_obj; if(cut_handle->action_handlers[CUT_USER_CONN] != NULL) { memset(&action_obj, 0, sizeof(struct cut_action_obj)); action_obj.action_type = CUT_USER_DROP; memcpy(&(action_obj.remote_addr), &(conn->remote_addr), sizeof(struct sockaddr_in)); strncpy(action_obj.remote_nick, conn->conn_name, CUTLASS_NAME_LEN-1); cutlass_action(cut_handle, conn, &action_obj); } } /* * housekeeping_conn_delete adds to the list of connections to be * freed eventually. We can't just free the memory yet, because we * may have a pointer to the connection stored in our conn_array (from * housekeeping_thread(). We have to be sure we can remove connections * gracefully, so we set the state to CUTSTATE_DELETING, and then add * it to a list of connections that will be free by the housekeeping_thread() * before it generates its list of all connections. */ int housekeeping_conn_delete(cutlass_t *cut_handle, conn_t *conn) { struct conn_delete_stack *new_node; if((cut_handle == NULL) || (conn == NULL)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "housekeeping_conn_delete: Passed a NULL pointer\n"); return(-1);; } new_node = malloc(sizeof(struct conn_delete_stack)); if(new_node == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "housekeeping_conn_delete: Could not allocate memory\n"); return(-1); } cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "housekeeping_conn_delete locking del_stack\n"); pthread_mutex_lock(&(cut_handle->del_stack_mutex)); cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "housekeeping_conn_delete locked del_stack\n"); new_node->next = cut_handle->del_stack; new_node->conn = conn; cut_handle->del_stack = new_node; cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "housekeeping_conn_delete unlocking del_stack\n"); pthread_mutex_unlock(&(cut_handle->del_stack_mutex)); cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "End of housekeeping_conn_delete\n"); return(0); } int conn_housekeeping(cutlass_t *cut_handle, struct cutlass_connection *conn, struct timespec *now, struct timespec *sleep_time) { char err_message[CUT_MSG_LEN_MAX]; char tmp_array[INET_ADDRSTRLEN]; int i; int retval; /* struct file_transit_info *file_info; */ struct timespec timeout; if((cut_handle == NULL) || (conn == NULL) || (now == NULL) || (sleep_time == NULL)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "conn_housekeeping: Passed a NULL pointer\n"); return(-1); } cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "Start of housekeeping loop\n"); timeout.tv_sec = CUT_TIMEOUT_INTERVAL; timeout.tv_nsec = 0; /**** * CRITICAL SECTION BEGINS */ cutlass_sysmsg(cut_handle, CUT_DEBUG, "conn_housekeeping locking conn\n"); pthread_mutex_lock(&(conn->conn_mutex)); cutlass_sysmsg(cut_handle, CUT_DEBUG, "conn_housekeeping conn locked!\n"); if(conn->conn_state == CUTSTATE_DELETING) { /*** * CRITICAL SECTION ENDPOINT */ cutlass_sysmsg(cut_handle, CUT_DEBUG, "conn_housekeeping unlocking conn\n"); pthread_mutex_unlock(&(conn->conn_mutex)); return(0); /* Do nothing, we're already on our way out. */ } if(cutlass_time_check(now, &(conn->last_packet_received), &timeout, sleep_time) == 1) goto conn_del; switch(conn->conn_state) { case(CUTSTATE_UNINITIALZED): case(CUTSTATE_C_SENT_HELLO): case(CUTSTATE_S_SENT_HELLO): case(CUTSTATE_C_SENT_DH): case(CUTSTATE_S_SENT_DH): /* Check to see if we're timed out */ timeout.tv_sec = CUT_KEX_RETRANSMIT_INTERVAL; if(cutlass_time_check(now, &(conn->last_packet_sent), &timeout, sleep_time) == 1) { retval = cutlass_process_handshake(cut_handle, conn, NULL, NULL, now); memcpy(&(conn->last_packet_sent), now, sizeof(struct timespec)); if(retval < 0) { goto conn_del; } break; } case(CUTSTATE_ACTIVE): timeout.tv_sec = CUT_PING_INTERVAL; if(cutlass_time_check(now, &(conn->last_packet_received), &timeout, sleep_time) == 1) { timeout.tv_sec = CUT_PING_RETRANSMIT_INTERVAL; if(cutlass_time_check(now, &(conn->last_ping_sent), &timeout, sleep_time) == 1) { retval = cutlass_ping(cut_handle, conn, now); if(retval < 0) { goto conn_del; } } } if(conn->have_info == 0) { retval = cutlass_info_request(cut_handle, conn, now); if(retval < 0) { goto conn_del; } } /* Retransmit messages, if needed, or send new traffic */ for(i = 1; i < CUT_NUM_CHANNELS; i++) { switch(conn->in_channels[i].channel_type) { case CHANNEL_NONE: /* Do nothing */ break; case CHANNEL_MSG: case CHANNEL_FILE: case CHANNEL_REQUEST: case CHANNEL_DIR_INFO: case CHANNEL_REPLY: cutlass_transport_housekeeping(cut_handle, conn, &conn->in_channels[i], CUT_IN, now, sleep_time); break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "conn_housekeeping: " "Saw an unknown channel type: %d\n", conn->in_channels[i].channel_type); break; } switch(conn->out_channels[i].channel_type) { case CHANNEL_NONE: /* Do nothing */ break; case CHANNEL_MSG: case CHANNEL_FILE: case CHANNEL_REQUEST: case CHANNEL_DIR_INFO: case CHANNEL_REPLY: cutlass_transport_housekeeping(cut_handle, conn, &conn->out_channels[i], CUT_OUT, now, sleep_time); break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "conn_housekeeping: " "Saw an unknown channel type: %d\n", conn->out_channels[i].channel_type); break; } } break; case(CUTSTATE_CLOSE_SENT): timeout.tv_sec = CUT_TIMEOUT_INTERVAL; if(cutlass_time_check(now, &(conn->last_packet_received), &timeout, sleep_time) == 1) goto conn_del; timeout.tv_sec = CUT_CLOSE_RETRANSMIT_INTERVAL; if(cutlass_time_check(now, &(conn->last_packet_received), &timeout, sleep_time) == 1) { cutlass_close_send(cut_handle, conn, now); } break; case(CUTSTATE_CLOSE_ACKED): timeout.tv_sec = CUT_SHUTDOWN_GRACE; if(cutlass_time_check(now, &(conn->last_packet_received), &timeout, sleep_time) == 1) { if(conn->have_info) { snprintf(err_message, CUT_MSG_LEN_MAX, "Remote connection close from %s\n", conn->conn_name); cutlass_sysmsg(cut_handle, CUT_INFO, err_message); } else { snprintf(err_message, CUT_MSG_LEN_MAX, "Remote connection close from %s\n", cutlass_ntop(AF_INET, &(conn->remote_addr.sin_addr), tmp_array, INET_ADDRSTRLEN)); cutlass_sysmsg(cut_handle, CUT_INFO, err_message); } shutdown_action(cut_handle, conn); cutlass_conn_del(cut_handle, conn); return(1); } break; } /*** * CRITICAL SECTION ENDPOINT */ cutlass_sysmsg(cut_handle, CUT_DEBUG, "conn_housekeeping unlocking conn 3\n"); pthread_mutex_unlock(&(conn->conn_mutex)); return(0); conn_del: snprintf(err_message, CUT_MSG_LEN_MAX, "Connection to %s timed out\n", cutlass_ntop(AF_INET, &(conn->remote_addr.sin_addr), tmp_array, INET_ADDRSTRLEN)); cutlass_sysmsg(cut_handle, CUT_INFO, err_message); timeout_action(cut_handle, conn); cutlass_conn_del(cut_handle, conn); cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "End of housekeeping loop\n"); return(1); } /* * clear_deleted should be called before each invocation of * cutlass_conn_list(), clearing out all the entries of connection * structures that still need deleting. cutlass_conn_del() should have * sanitized these for deletion first, so we don't need to do * anything but free the memory. */ void clear_deleted(cutlass_t *cut_handle) { struct conn_delete_stack *free_node; struct conn_delete_stack *walk_node; struct conn_delete_stack *prev_node; conn_t *tmp_conn; cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "clear_deleted locking del_stack\n"); pthread_mutex_lock(&(cut_handle->del_stack_mutex)); cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "clear_deleted locked del_stack!\n"); walk_node = cut_handle->del_stack; prev_node = NULL; while(walk_node != NULL) { tmp_conn = walk_node->conn; free_node = walk_node; if(free_node == cut_handle->del_stack) { cut_handle->del_stack = walk_node->next; walk_node = walk_node->next; } else { walk_node = walk_node->next; if(prev_node != NULL) prev_node->next = walk_node; } /* cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "calling cutlass_conn_del\n"); cutlass_conn_del(cut_handle, free_node->conn); cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "returned from cutlass_conn_del\n"); */ free(free_node); } cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "clear_deleted unlocking del_stack\n"); pthread_mutex_unlock(&(cut_handle->del_stack_mutex)); } /* housekeeping_thread() is the main housekeeping loop */ void * housekeeping_thread(void *void_handle) { cutlass_t *cut_handle; conn_t *conn_array[CUT_HK_ARRAY_SZ]; int i; int num_conn; struct timespec now; struct timespec sleep_time; pthread_detach(pthread_self()); cut_handle = void_handle; while(cut_handle->running_state) { cutlass_sysmsg(cut_handle, CUT_SUPAA_DEBUG, "housekeeping_thread loop ... \n"); if(cut_handle->del_stack != NULL) { clear_deleted(cut_handle); } /* acquire timer settings from cutlass handle */ sleep_time.tv_sec = cut_handle->sleep_time.tv_sec; sleep_time.tv_nsec = cut_handle->sleep_time.tv_nsec; if(cutlass_time(&now) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "housekeeping: cutlass_time failed\n"); } else { num_conn = internal_conn_list(cut_handle, conn_array, CUT_HK_ARRAY_SZ); for (i = 0; i < num_conn; i++) { conn_housekeeping(cut_handle, conn_array[i], &now, &sleep_time); } } nanosleep(&sleep_time, NULL); } pthread_exit(NULL); }