/* * ecm_state_char_device_release() * Called when a process closes the device file. */ static int ecm_state_char_device_release(struct inode *inode, struct file *file) { struct ecm_state_file_instance *sfi; sfi = (struct ecm_state_file_instance *)file->private_data; DEBUG_CHECK_MAGIC(sfi, ECM_STATE_FILE_INSTANCE_MAGIC, "%p: magic failed", sfi); DEBUG_INFO("%p: State close\n", sfi); /* * Release any references held */ if (sfi->ci) { ecm_db_connection_deref(sfi->ci); } if (sfi->mi) { ecm_db_mapping_deref(sfi->mi); } if (sfi->hi) { ecm_db_host_deref(sfi->hi); } if (sfi->ni) { ecm_db_node_deref(sfi->ni); } if (sfi->ii) { ecm_db_iface_deref(sfi->ii); } #ifdef ECM_DB_CTA_TRACK_ENABLE ecm_state_file_classifier_type_assignments_release(sfi); #endif DEBUG_CLEAR_MAGIC(sfi); kfree(sfi); return 0; }
/* * ecm_tracker_tcp_deref_callback() */ int ecm_tracker_tcp_deref_callback(struct ecm_tracker_instance *ti) { struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti; int refs; DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%p: magic failed", ttii); spin_lock_bh(&ttii->lock); ttii->refs--; refs = ttii->refs; DEBUG_ASSERT(ttii->refs >= 0, "%p: ref wrap", ttii); DEBUG_TRACE("%p: deref %d\n", ttii, ttii->refs); if (ttii->refs > 0) { spin_unlock_bh(&ttii->lock); return refs; } DEBUG_TRACE("%p: final\n", ttii); spin_unlock_bh(&ttii->lock); spin_lock_bh(&ecm_tracker_tcp_lock); ecm_tracker_tcp_count--; DEBUG_ASSERT(ecm_tracker_tcp_count >= 0, "%p: tracker count wrap", ttii); spin_unlock_bh(&ecm_tracker_tcp_lock); DEBUG_INFO("%p: TCP tracker final\n", ttii); DEBUG_CLEAR_MAGIC(ttii); kfree(ttii); return 0; }
/* * ecm_state_prefix_remove() * Remove level from the prefix * * Returns 0 on success */ int ecm_state_prefix_remove(struct ecm_state_file_instance *sfi) { int pxsz; DEBUG_CHECK_MAGIC(sfi, ECM_STATE_FILE_INSTANCE_MAGIC, "%p: magic failed", sfi); sfi->prefix_level--; DEBUG_ASSERT(sfi->prefix_level >= 0, "Bad prefix handling\n"); pxsz = sfi->prefix_levels[sfi->prefix_level]; sfi->prefix[pxsz] = 0; return 0; }
/* * ecm_tracker_tcp_state_get_callback() * Get state */ static void ecm_tracker_tcp_state_get_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_state_t *src_state, ecm_tracker_sender_state_t *dest_state, ecm_tracker_connection_state_t *state, ecm_db_timer_group_t *tg) { struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti; DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%p: magic failed", ttii); spin_lock_bh(&ttii->lock); *src_state = ttii->sender_states[ECM_TRACKER_SENDER_TYPE_SRC].state; *dest_state = ttii->sender_states[ECM_TRACKER_SENDER_TYPE_DEST].state; spin_unlock_bh(&ttii->lock); *state = ecm_tracker_tcp_connection_state_matrix[*src_state][*dest_state]; *tg = ecm_tracker_tcp_timer_group_from_state[*state]; }
/* * ecm_tracker_tcp_ref_callback() */ void ecm_tracker_tcp_ref_callback(struct ecm_tracker_instance *ti) { struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti; DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%p: magic failed", ttii); spin_lock_bh(&ttii->lock); ttii->refs++; DEBUG_ASSERT(ttii->refs > 0, "%p: ref wrap", ttii); DEBUG_TRACE("%p: ref %d\n", ttii, ttii->refs); spin_unlock_bh(&ttii->lock); }
/* * ecm_state_write_reset() * Reset the msg buffer, specifying a new initial prefix * * Returns 0 on success */ int ecm_state_write_reset(struct ecm_state_file_instance *sfi, char *prefix) { int result; DEBUG_CHECK_MAGIC(sfi, ECM_STATE_FILE_INSTANCE_MAGIC, "%p: magic failed", sfi); sfi->msgp = sfi->msg; sfi->msg_len = 0; result = snprintf(sfi->prefix, ECM_STATE_FILE_PREFIX_SIZE, "%s", prefix); if ((result < 0) || (result >= ECM_STATE_FILE_PREFIX_SIZE)) { return -1; } sfi->prefix_level = 0; sfi->prefix_levels[sfi->prefix_level] = result; return 0; }
/* * ecm_state_prefix_index_add() * Add another level (numeric) to the prefix * * Returns 0 on success */ int ecm_state_prefix_index_add(struct ecm_state_file_instance *sfi, int index) { int pxsz; int pxremain; int result; DEBUG_CHECK_MAGIC(sfi, ECM_STATE_FILE_INSTANCE_MAGIC, "%p: magic failed", sfi); pxsz = sfi->prefix_levels[sfi->prefix_level]; pxremain = ECM_STATE_FILE_PREFIX_SIZE - pxsz; result = snprintf(sfi->prefix + pxsz, pxremain, ".%d", index); if ((result < 0) || (result >= pxremain)) { return -1; } sfi->prefix_level++; DEBUG_ASSERT(sfi->prefix_level < ECM_STATE_FILE_PREFIX_LEVELS_MAX, "Bad prefix handling\n"); sfi->prefix_levels[sfi->prefix_level] = pxsz + result; return 0; }
/* * ecm_state_write() * Write out to the message buffer, prefix is added automatically. * * Returns 0 on success */ int ecm_state_write(struct ecm_state_file_instance *sfi, char *name, char *fmt, ...) { int remain; char *ptr; int result; va_list args; DEBUG_CHECK_MAGIC(sfi, ECM_STATE_FILE_INSTANCE_MAGIC, "%p: magic failed", sfi); remain = ECM_STATE_FILE_BUFFER_SIZE - sfi->msg_len; ptr = sfi->msg + sfi->msg_len; result = snprintf(ptr, remain, "%s.%s=", sfi->prefix, name); if ((result < 0) || (result >= remain)) { return -1; } sfi->msg_len += result; remain -= result; ptr += result; va_start(args, fmt); result = vsnprintf(ptr, remain, fmt, args); va_end(args); if ((result < 0) || (result >= remain)) { return -2; } sfi->msg_len += result; remain -= result; ptr += result; result = snprintf(ptr, remain, "\n"); if ((result < 0) || (result >= remain)) { return -3; } sfi->msg_len += result; return 0; }
/* * ecm_state_char_device_read() * Called to read the state */ static ssize_t ecm_state_char_device_read(struct file *file, /* see include/linux/fs.h */ char *buffer, /* buffer to fill with data */ size_t length, /* length of the buffer */ loff_t *offset) /* Doesn't apply - this is a char file */ { struct ecm_state_file_instance *sfi; int bytes_read = 0; /* Number of bytes actually written to the buffer */ #ifdef ECM_DB_CTA_TRACK_ENABLE ecm_classifier_type_t ca_type; #endif sfi = (struct ecm_state_file_instance *)file->private_data; DEBUG_CHECK_MAGIC(sfi, ECM_STATE_FILE_INSTANCE_MAGIC, "%p: magic failed", sfi); DEBUG_TRACE("%p: State read up to length %d bytes\n", sfi, length); /* * If there is still some message remaining to be output then complete that first */ if (sfi->msg_len) { goto char_device_read_output; } if (sfi->ci) { struct ecm_db_connection_instance *cin; if (ecm_state_char_dev_conn_msg_prep(sfi)) { return -EIO; } /* * Next connection for when we return */ cin = ecm_db_connection_get_and_ref_next(sfi->ci); ecm_db_connection_deref(sfi->ci); sfi->ci = cin; goto char_device_read_output; } if (sfi->mi) { struct ecm_db_mapping_instance *min; if (ecm_state_char_dev_mapping_msg_prep(sfi)) { return -EIO; } /* * Next mapping for when we return */ min = ecm_db_mapping_get_and_ref_next(sfi->mi); ecm_db_mapping_deref(sfi->mi); sfi->mi = min; goto char_device_read_output; } if (sfi->hi) { struct ecm_db_host_instance *hin; if (ecm_state_char_dev_host_msg_prep(sfi)) { return -EIO; } /* * Next host for when we return */ hin = ecm_db_host_get_and_ref_next(sfi->hi); ecm_db_host_deref(sfi->hi); sfi->hi = hin; goto char_device_read_output; } if (sfi->ni) { struct ecm_db_node_instance *nin; if (ecm_state_char_dev_node_msg_prep(sfi)) { return -EIO; } /* * Next node for when we return */ nin = ecm_db_node_get_and_ref_next(sfi->ni); ecm_db_node_deref(sfi->ni); sfi->ni = nin; goto char_device_read_output; } if (sfi->ii) { struct ecm_db_iface_instance *iin; if (ecm_state_char_dev_iface_msg_prep(sfi)) { return -EIO; } /* * Next iface for when we return */ iin = ecm_db_interface_get_and_ref_next(sfi->ii); ecm_db_iface_deref(sfi->ii); sfi->ii = iin; goto char_device_read_output; } if ((sfi->output_mask & ECM_STATE_FILE_OUTPUT_CONNECTIONS_CHAIN) && (sfi->connection_hash_index >= 0)) { if (ecm_state_char_dev_conn_chain_msg_prep(sfi)) { return -EIO; } sfi->connection_hash_index = ecm_db_connection_hash_index_get_next(sfi->connection_hash_index); goto char_device_read_output; } if ((sfi->output_mask & ECM_STATE_FILE_OUTPUT_MAPPINGS_CHAIN) && (sfi->mapping_hash_index >= 0)) { if (ecm_state_char_dev_mapping_chain_msg_prep(sfi)) { return -EIO; } sfi->mapping_hash_index = ecm_db_mapping_hash_index_get_next(sfi->mapping_hash_index); goto char_device_read_output; } if ((sfi->output_mask & ECM_STATE_FILE_OUTPUT_HOSTS_CHAIN) && (sfi->host_hash_index >= 0)) { if (ecm_state_char_dev_host_chain_msg_prep(sfi)) { return -EIO; } sfi->host_hash_index = ecm_db_host_hash_index_get_next(sfi->host_hash_index); goto char_device_read_output; } if ((sfi->output_mask & ECM_STATE_FILE_OUTPUT_NODES_CHAIN) && (sfi->node_hash_index >= 0)) { if (ecm_state_char_dev_node_chain_msg_prep(sfi)) { return -EIO; } sfi->node_hash_index = ecm_db_node_hash_index_get_next(sfi->node_hash_index); goto char_device_read_output; } if ((sfi->output_mask & ECM_STATE_FILE_OUTPUT_INTERFACES_CHAIN) && (sfi->iface_hash_index >= 0)) { if (ecm_state_char_dev_iface_chain_msg_prep(sfi)) { return -EIO; } sfi->iface_hash_index = ecm_db_iface_hash_index_get_next(sfi->iface_hash_index); goto char_device_read_output; } if ((sfi->output_mask & ECM_STATE_FILE_OUTPUT_PROTOCOL_COUNTS) && (sfi->protocol >= 0)) { if (ecm_state_char_dev_protocol_count_msg_prep(sfi)) { return -EIO; } sfi->protocol = ecm_db_protocol_get_next(sfi->protocol); goto char_device_read_output; } #ifdef ECM_DB_CTA_TRACK_ENABLE for (ca_type = 0; ca_type < ECM_CLASSIFIER_TYPES; ++ca_type) { if (!sfi->classifier_type_assignments[ca_type]) continue; if (ecm_state_char_dev_cta_msg_prep(sfi, ca_type)) { return -EIO; } goto char_device_read_output; } #endif /* * EOF */ return 0; char_device_read_output: /* * If supplied buffer is small we limit what we output */ bytes_read = sfi->msg_len; if (bytes_read > length) { bytes_read = length; } if (copy_to_user(buffer, sfi->msgp, bytes_read)) { return -EIO; } sfi->msg_len -= bytes_read; sfi->msgp += bytes_read; DEBUG_TRACE("State read done, bytes_read %d bytes\n", bytes_read); /* * Most read functions return the number of bytes put into the buffer */ return bytes_read; }
/* * ecm_tracker_tcp_init() * Initialise the instance */ void ecm_tracker_tcp_init(struct ecm_tracker_tcp_instance *tti, int32_t data_limit, uint16_t mss_src_default, uint16_t mss_dest_default) { struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti; DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%p: magic failed", ttii); DEBUG_TRACE("%p: init host addresses src mss: %d, dest mss: %d\n", ttii, mss_src_default, mss_dest_default); }
/* * ecm_tracker_tcp_state_update_callback() * Update connection state based on the knowledge we have and the skb given */ static void ecm_tracker_tcp_state_update_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_type_t sender, struct ecm_tracker_ip_header *ip_hdr, struct sk_buff *skb) { struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti; struct tcphdr tcp_hdr_buff; struct tcphdr *tcp_hdr; struct ecm_tracker_tcp_sender_state *sender_state; struct ecm_tracker_tcp_sender_state *peer_state; DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%p: magic failed", ttii); /* * Get refereces to states */ DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%p: invalid sender %d\n", ttii, sender); sender_state = &ttii->sender_states[sender]; peer_state = &ttii->sender_states[!sender]; /* * Get tcp header */ tcp_hdr = ecm_tracker_tcp_check_header_and_read(skb, ip_hdr, &tcp_hdr_buff); if (unlikely(!tcp_hdr)) { DEBUG_WARN("%p: no tcp_hdr for %p\n", ttii, skb); spin_lock_bh(&ttii->lock); sender_state->state = ECM_TRACKER_SENDER_STATE_FAULT; peer_state->state = ECM_TRACKER_SENDER_STATE_FAULT; spin_unlock_bh(&ttii->lock); return; } /* * If either side reports a reset this is catastrophic for the connection */ if (unlikely(tcp_hdr->rst)) { DEBUG_INFO("%p: RESET\n", ttii); spin_lock_bh(&ttii->lock); sender_state->state = ECM_TRACKER_SENDER_STATE_FAULT; peer_state->state = ECM_TRACKER_SENDER_STATE_FAULT; spin_unlock_bh(&ttii->lock); return; } /* * Likely ack is set - this constitutes the mainstay of a TCP connection * The sending of an ack may put the other side of the connection into a different state */ spin_lock_bh(&ttii->lock); if (likely(tcp_hdr->ack)) { ecm_tracker_sender_state_t peer_state_current = peer_state->state; uint32_t ack_seq = ntohl(tcp_hdr->ack_seq); switch (peer_state_current) { case ECM_TRACKER_SENDER_STATE_UNKNOWN: { /* * Looks like we came into this connection mid-flow. * Flag that the peer is established which is all we can infer right now and * initialise the peers SYN sequence for further analysis of sequence space. */ peer_state->state = ECM_TRACKER_SENDER_STATE_ESTABLISHED; peer_state->syn_seq = (ack_seq - 1); /* -1 is because the ACK has ack'd our ficticious SYN */ DEBUG_INFO("%p: From unkown to established, ack_seq: %u, syn_seq: %u\n", ttii, ack_seq, peer_state->syn_seq); break; } case ECM_TRACKER_SENDER_STATE_ESTABLISHING: { int32_t ackd; uint32_t syn_seq; syn_seq = peer_state->syn_seq; ackd = (int32_t)(ack_seq - syn_seq); DEBUG_TRACE("%p: ack %u for syn_seq %u? ackd = %d\n", ttii, ack_seq, syn_seq, ackd); if (ackd <= 0) { DEBUG_TRACE("%p: No change\n", ttii); } else { DEBUG_INFO("%p: Established\n", ttii); peer_state->state = ECM_TRACKER_SENDER_STATE_ESTABLISHED; } break; } case ECM_TRACKER_SENDER_STATE_CLOSING: { int32_t ackd; uint32_t fin_seq; fin_seq = peer_state->fin_seq; ackd = (int32_t)(ack_seq - fin_seq); DEBUG_TRACE("%p: ack %u for fin_seq %u? ackd = %d\n", ttii, ack_seq, fin_seq, ackd); if (ackd <= 0) { DEBUG_TRACE("%p: No change\n", ttii); } else { DEBUG_TRACE("%p: Closed\n", ttii); peer_state->state = ECM_TRACKER_SENDER_STATE_CLOSED; } break; } case ECM_TRACKER_SENDER_STATE_ESTABLISHED: case ECM_TRACKER_SENDER_STATE_CLOSED: case ECM_TRACKER_SENDER_STATE_FAULT: /* * No change */ break; default: DEBUG_ASSERT(false, "%p: unhandled state: %d\n", ttii, peer_state_current); } } /* * Handle control flags sent by the sender (SYN & FIN) * Handle SYN first because, in sequence space, SYN is first. */ if (tcp_hdr->syn) { ecm_tracker_sender_state_t sender_state_current = sender_state->state; uint32_t seq = ntohl(tcp_hdr->seq); switch (sender_state_current) { case ECM_TRACKER_SENDER_STATE_UNKNOWN: sender_state->state = ECM_TRACKER_SENDER_STATE_ESTABLISHING; sender_state->syn_seq = seq; /* Seq is the sequence number of the SYN */ DEBUG_INFO("%p: From unkown to establishing, syn_seq: %u\n", ttii, sender_state->syn_seq); break; case ECM_TRACKER_SENDER_STATE_CLOSING: case ECM_TRACKER_SENDER_STATE_CLOSED: /* * SYN after seeing a FIN? FAULT! */ sender_state->state = ECM_TRACKER_SENDER_STATE_FAULT; DEBUG_INFO("%p: SYN after FIN - fault\n", ttii); break; case ECM_TRACKER_SENDER_STATE_ESTABLISHED: /* * SYN when established is just a duplicate down to syn/ack timing subtleties */ case ECM_TRACKER_SENDER_STATE_ESTABLISHING: case ECM_TRACKER_SENDER_STATE_FAULT: /* * No change */ break; default: DEBUG_ASSERT(false, "%p: unhandled state: %d\n", ttii, sender_state_current); } } if (tcp_hdr->fin) { ecm_tracker_sender_state_t sender_state_current = sender_state->state; uint32_t seq = ntohl(tcp_hdr->seq); switch (sender_state_current) { case ECM_TRACKER_SENDER_STATE_UNKNOWN: /* * Looks like we joined mid-flow. * Have to set up both SYN and FIN. * NOTE: It's possible that SYN is in the same packet as FIN, account for that in the seq numbers */ sender_state->state = ECM_TRACKER_SENDER_STATE_CLOSING; if (tcp_hdr->syn) { sender_state->syn_seq = seq; sender_state->fin_seq = seq + 1; } else { sender_state->fin_seq = seq; /* seq is the FIN sequence */ sender_state->syn_seq = seq - 1; /* Make a guess at what the SYN was */ } DEBUG_INFO("%p: From unkown to closing, syn_seq: %u, fin_seq: %u\n", ttii, sender_state->syn_seq, sender_state->fin_seq); break; case ECM_TRACKER_SENDER_STATE_ESTABLISHED: /* * Connection becomes closing. */ sender_state->state = ECM_TRACKER_SENDER_STATE_CLOSING; sender_state->fin_seq = seq; DEBUG_INFO("%p: From established to closing, fin_seq: %u\n", ttii, sender_state->fin_seq); break; case ECM_TRACKER_SENDER_STATE_ESTABLISHING: { int32_t newer; /* * FIN while waiting for SYN to be acknowledged is possible but only if it * it is in the same packet or later sequence space */ newer = (int32_t)(seq - sender_state->syn_seq); if (!tcp_hdr->syn || (newer <= 0)) { DEBUG_INFO("%p: From establishing to fault - odd FIN seen, syn: %u, syn_seq: %u, newer: %d\n", ttii, tcp_hdr->syn, sender_state->syn_seq, newer); sender_state->state = ECM_TRACKER_SENDER_STATE_FAULT; } else { uint32_t fin_seq = seq; if (tcp_hdr->syn) { fin_seq++; } sender_state->state = ECM_TRACKER_SENDER_STATE_CLOSING; DEBUG_INFO("%p: From establishing to closing, syn: %u, syn_seq: %u, fin_seq: %u\n", ttii, tcp_hdr->syn, sender_state->syn_seq, sender_state->fin_seq); } break; } case ECM_TRACKER_SENDER_STATE_CLOSING: case ECM_TRACKER_SENDER_STATE_CLOSED: case ECM_TRACKER_SENDER_STATE_FAULT: /* * No change */ break; default: DEBUG_ASSERT(false, "%p: unhandled state: %d\n", ttii, sender_state_current); } } spin_unlock_bh(&ttii->lock); }