blob: b62af45e3dbeb5d966f5064db36d839f64a53188 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2014-2015, 2020, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <linux/version.h>
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/icmp.h>
#include <linux/sysctl.h>
#include <linux/kthread.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/pkt_sched.h>
#include <linux/string.h>
#include <net/route.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <asm/unaligned.h>
#include <asm/uaccess.h> /* for put_user */
#include <net/ipv6.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_bridge.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_l4proto.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/ipv4/nf_conntrack_ipv4.h>
#include <net/netfilter/ipv4/nf_defrag_ipv4.h>
/*
* Debug output levels
* 0 = OFF
* 1 = ASSERTS / ERRORS
* 2 = 1 + WARN
* 3 = 2 + INFO
* 4 = 3 + TRACE
*/
#define DEBUG_LEVEL ECM_TRACKER_TCP_DEBUG_LEVEL
#include "ecm_types.h"
#include "ecm_db_types.h"
#include "ecm_state.h"
#include "ecm_tracker.h"
#include "ecm_tracker_tcp.h"
/*
* TCP control flags.
*/
#define TCF_FIN 0x01
#define TCF_SYN 0x02
#define TCF_RST 0x04
#define TCF_PSH 0x08
#define TCF_ACK 0x10
#define TCF_URG 0x20
/*
* Extract a byte tcp flags field from a union tcp_word_hdr.
*/
#define ECM_TRACKER_TCP_WORD_HDR_TO_FLAGS(word_hdr) ((ntohl(word_hdr->words[3]) >> 16) & 0xff)
/*
* Valid TCP flag combinations.
*/
#define ECM_TRACKER_TCP_VALID_FLAGS_MAX ((TCF_URG | TCF_ACK | TCF_PSH | TCF_RST | TCF_SYN | TCF_FIN) + 1)
static uint32_t ecm_tracker_tcp_valid_flags[ECM_TRACKER_TCP_VALID_FLAGS_MAX] = {
[TCF_FIN | TCF_RST | TCF_ACK] = 1,
[TCF_FIN | TCF_PSH | TCF_ACK] = 1,
[TCF_FIN | TCF_PSH | TCF_ACK | TCF_URG] = 1,
[TCF_FIN | TCF_ACK] = 1,
[TCF_FIN | TCF_ACK | TCF_URG] = 1,
[TCF_SYN] = 1,
[TCF_SYN | TCF_ACK] = 1,
[TCF_RST] = 1,
[TCF_RST | TCF_PSH | TCF_ACK] = 1,
[TCF_RST | TCF_ACK] = 1,
[TCF_PSH | TCF_ACK] = 1,
[TCF_PSH | TCF_ACK | TCF_URG] = 1,
[TCF_ACK] = 1,
[TCF_ACK | TCF_URG] = 1,
};
/*
* Magic numbers
*/
#define ECM_TRACKER_TCP_INSTANCE_MAGIC 0x5129
#define ECM_TRACKER_TCP_SKB_CB_MAGIC 0x4398
#define ECM_TRACKER_TCP_READER_INSTANCE_MAGIC 0x1123
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
/*
* struct ecm_tracker_tcp_skb_cb_format
* Map of the cb[] array within our cached buffers - we use that area for our tracking
*/
struct ecm_tracker_tcp_skb_cb_format {
struct sk_buff *next; /* Next sk buff in the recvd_order list */
struct sk_buff *prev; /* Previous sk buff in the recvd_order */
uint32_t seq_no; /* Sequence number of the first readable DATA BYTE in this buffer. There may be others but they may be old. */
uint32_t num_seqs; /* Number of DATA BYTES/sequences, i.e. the number of sequences from seq_no. */
uint32_t offset; /* Offset from skb->data to seq_no (i.e. header size we must skip to get to data) */
#if (DEBUG_LEVEL > 0)
uint16_t magic;
#endif
};
/*
* struct ecm_tracker_tcp_host_data
* data tracking for a host
*/
struct ecm_tracker_tcp_host_data {
uint16_t mss; /* MSS as sent by that host in a SYN packet */
bool mss_seen; /* true if the mss was from a SYN packet rather than an assigned default */
uint32_t seq_no; /* Sequence number of the first data byte we are tracking */
uint32_t num_seqs; /* Number of sequences from first */
bool seq_no_valid; /* True when our sequencing is valid */
/*
* All skbuffs are added into the recvd order list in addition to POSSIBLY being added into the in_order or future lists as well.
* NOTE: Buffers in recv_order list make use of the [cb]->next and [cb]->prev.
*/
struct sk_buff *recvd_order; /* skbuffs inserted in order of reception */
struct sk_buff *recvd_order_last; /* skbuffs inserted in order of reception - tracks last insertion point for speed of appending */
int32_t recvd_bytes_total; /* Current total of bytes of all buffers received */
int32_t recvd_count; /* Current total buffers received */
/*
* Buffers that have useful in-sequence-order data are added into this list
* NOTE: These lists make use of sk_buff->next and sk_buff->prev because:
* 1. An skb cannot be in in_order and future at the same time.
* 2. It's faster to work with these pointers - which is important when sliding over lists for reading etc..
*/
struct sk_buff *in_order; /* A list of skb's tracked in our recvd_order list in sequence space order that we can read from.
* NOTE: An skb is added into both the recvd_order AND, if applicable, this list.
*/
struct sk_buff *in_order_last; /* Fast appending of in-order data buffers */
int32_t in_order_count; /* The number of skb's in the in order list */
struct sk_buff *future; /* A list of skb's that are in the "future" (sequence space wise) and need more sequence space
* received to join the 'gap' allowing their use.
*/
};
#endif
/*
* struct ecm_tracker_tcp_sender_state
* Connection state of each sender
*/
struct ecm_tracker_tcp_sender_state {
ecm_tracker_sender_state_t state; /* State of the sender */
uint32_t syn_seq; /* Sequence number of the syn (ISN): used to detect when peer has ack'd the syn, transitioned this sender to established. */
uint32_t fin_seq; /* Sequence number of the fin: used to detect when peer has ack'd the fin, transitioned this sender to closed. */
};
/*
* struct ecm_tracker_tcp_internal_instance
*/
struct ecm_tracker_tcp_internal_instance {
struct ecm_tracker_tcp_instance tcp_base; /* MUST BE FIRST FIELD */
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
struct ecm_tracker_tcp_host_data sender_data[ECM_TRACKER_SENDER_MAX]; /* Data tracked sent by the src of the connection */
#endif
struct ecm_tracker_tcp_sender_state sender_states[ECM_TRACKER_SENDER_MAX]; /* Sender states */
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
int32_t data_limit; /* Limit for tracked data */
#endif
int refs; /* Integer to trap we never go negative */
spinlock_t lock;
#if (DEBUG_LEVEL > 0)
uint16_t magic;
#endif
};
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
/*
* struct ecm_tracker_tcp_reader_instance
* Reader.
*/
struct ecm_tracker_tcp_reader_instance {
struct ecm_tracker_tcp_internal_instance *ttii; /* Reference to the internal tracker instance */
struct ecm_tracker_tcp_host_data *data; /* Host data this reader is using */
/*
* Tracking current position
*/
uint32_t offset; /* Logical offset from a start of 0 - i.e. from start of tracker data.
* NOTE: When bytes are discarded the offset is NOT adjusted to reflect discarded bytes, i.e. they are not shifted left.
* It remains as it is so that external notions of positions remain correct - especially when setting positions.
*/
uint32_t discarded; /* Tracks the amount of data that has been discarded so far so that we don't try to discard them again.
* This is important because offset is never modified when bytes are discarded.
*/
/*
* The real work is done by the segment tracking variables that follow.
* These 'slide' over the available segments and always represent actual data and positions within a segment.
*/
uint32_t segment_offset; /* Offset from the start of the segment at which the next read will occur (header offset + data already advanced over in this segment) */
uint32_t segment_remain; /* Data bytes remaining to be read in this segment given the read pointer */
struct sk_buff *segment; /* Current segment skb */
struct ecm_tracker_tcp_skb_cb_format *segment_dcb;
/* The private cb area of the current segment, cached for convenience */
int refs; /* Integer to trap we never go negative */
spinlock_t lock;
#if (DEBUG_LEVEL > 0)
uint16_t magic;
#endif
};
#endif
/*
* ecm_tracker_tcp_timer_group_from_state[]
* Given a prevailing connection state return the appropriate timer group.
*
* Different states have different timeouts, for example, established state is the longest timeout.
* Using these timeouts ensures efficient resource uses and avoids connections hanging around when it is unnecessary.
*/
static ecm_db_timer_group_t ecm_tracker_tcp_timer_group_from_state[] = {
ECM_DB_TIMER_GROUPS_CONNECTION_TCP_SHORT_TIMEOUT, /* ECM_TRACKER_CONNECTION_STATE_ESTABLISHING */
ECM_DB_TIMER_GROUPS_CONNECTION_TCP_LONG_TIMEOUT, /* ECM_TRACKER_CONNECTION_STATE_ESTABLISHED */
ECM_DB_TIMER_GROUPS_CONNECTION_TCP_SHORT_TIMEOUT, /* ECM_TRACKER_CONNECTION_STATE_CLOSING */
ECM_DB_TIMER_GROUPS_CONNECTION_TCP_CLOSED_TIMEOUT, /* ECM_TRACKER_CONNECTION_STATE_CLOSED */
ECM_DB_TIMER_GROUPS_CONNECTION_TCP_RESET_TIMEOUT, /* ECM_TRACKER_CONNECTION_STATE_FAULT */
};
int ecm_tracker_tcp_count = 0; /* Counts the number of TCP data trackers right now */
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
int ecm_tracker_tcp_reader_count = 0; /* Counts the number of TCP readers right now */
#endif
static DEFINE_SPINLOCK(ecm_tracker_tcp_lock); /* Global lock for the tracker globals */
/*
* ecm_tracker_tcp_connection_state_matrix[][]
* Matrix to convert from/to states to connection state
*/
static ecm_tracker_connection_state_t ecm_tracker_tcp_connection_state_matrix[ECM_TRACKER_SENDER_STATE_MAX][ECM_TRACKER_SENDER_STATE_MAX] =
{ /* Unknown Establishing Established Closing Closed Fault */
/* Unknown */ {ECM_TRACKER_CONNECTION_STATE_ESTABLISHING, ECM_TRACKER_CONNECTION_STATE_ESTABLISHING, ECM_TRACKER_CONNECTION_STATE_ESTABLISHED, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT},
/* Establishing */ {ECM_TRACKER_CONNECTION_STATE_ESTABLISHING, ECM_TRACKER_CONNECTION_STATE_ESTABLISHING, ECM_TRACKER_CONNECTION_STATE_ESTABLISHING, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT},
/* Established */ {ECM_TRACKER_CONNECTION_STATE_ESTABLISHED, ECM_TRACKER_CONNECTION_STATE_ESTABLISHING, ECM_TRACKER_CONNECTION_STATE_ESTABLISHED, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_FAULT},
/* Closing */ {ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_FAULT},
/* Closed */ {ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_CLOSING, ECM_TRACKER_CONNECTION_STATE_CLOSED, ECM_TRACKER_CONNECTION_STATE_FAULT},
/* Fault */ {ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT, ECM_TRACKER_CONNECTION_STATE_FAULT},
};
/*
* ecm_tracker_tcp_check_header_and_read()
* Check that we have a complete transport-level header, check it and return it if it's okay.
*/
struct tcphdr *ecm_tracker_tcp_check_header_and_read(struct sk_buff *skb, struct ecm_tracker_ip_header *ip_hdr, struct tcphdr *port_buffer)
{
struct tcphdr *hdr;
union tcp_word_hdr *word_hdr;
uint16_t tcp_hdr_len;
uint32_t tcp_flags;
struct ecm_tracker_ip_protocol_header *header;
/*
* Is there a TCP header?
*/
header = &ip_hdr->headers[ECM_TRACKER_IP_PROTOCOL_TYPE_TCP];
if (header->size == 0) {
DEBUG_WARN("No TCP header %px\n", skb);
return NULL;
}
hdr = (struct tcphdr *)skb_header_pointer(skb, header->offset, sizeof(*port_buffer), port_buffer);
if (unlikely(!hdr)) {
DEBUG_WARN("Cant read TCP header %px\n", skb);
return NULL;
}
word_hdr = (union tcp_word_hdr *)hdr;
/*
* Check that we have all of the TCP data we're supposed to have
*/
tcp_hdr_len = (uint16_t)hdr->doff;
tcp_hdr_len <<= 2;
if (unlikely(tcp_hdr_len < 20)) {
DEBUG_WARN("Packet %px TCP header to short %u\n", skb, tcp_hdr_len);
return NULL;
}
if (unlikely(ip_hdr->total_length < (header->offset + header->size))) {
DEBUG_WARN("TCP packet %px too short (ip_total_len = %u, size needed=%u)\n", skb, ip_hdr->total_length, header->offset + header->size);
return NULL;
}
/*
* Validate flags
*/
tcp_flags = ECM_TRACKER_TCP_WORD_HDR_TO_FLAGS(word_hdr);
DEBUG_TRACE("%px: TCP flags = 0x%04x\n", skb, (unsigned)tcp_flags);
if (unlikely(!ecm_tracker_tcp_valid_flags[tcp_flags & 0x3f])) {
DEBUG_WARN("%px: Invalid flags 0x%x", skb, (unsigned)tcp_flags);
return NULL;
}
return hdr;
}
EXPORT_SYMBOL(ecm_tracker_tcp_check_header_and_read);
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
/*
* ecm_tracker_tcp_bytes_discard()
* Discard n bytes from the available in_order bytes
*/
static void ecm_tracker_tcp_bytes_discard(struct ecm_tracker_tcp_internal_instance *ttii, struct ecm_tracker_tcp_host_data *data, uint32_t n)
{
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
/*
* Advance sequence space by 'n'. This may allow throwing of datagrams.
*/
spin_lock_bh(&ttii->lock);
DEBUG_ASSERT(n <= data->num_seqs, "%px: discard invalid %u, available %u\n", ttii, n, data->num_seqs);
data->num_seqs -= n;
data->seq_no += n;
DEBUG_TRACE("%px: Discard %u, New seq space for %px, seq_no: %u, num_seqs: %u\n", ttii, n, data, data->seq_no, data->num_seqs);
spin_unlock_bh(&ttii->lock);
while (n) {
struct ecm_tracker_tcp_skb_cb_format *skb_cb;
struct sk_buff *skb;
/*
* Examine the buffer at the head of the in order data list
*/
spin_lock_bh(&ttii->lock);
skb = data->in_order;
DEBUG_ASSERT(skb, "%px: data list invalid\n", ttii);
skb_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb->cb;
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, skb_cb);
if (n < skb_cb->num_seqs) {
/*
* skb has more sequences in it than we are discarding. Simply advance the sequence space by n
*/
skb_cb->seq_no += n;
skb_cb->offset += n;
skb_cb->num_seqs -= n;
DEBUG_TRACE("%px: PARTIAL BUFFER DISCARD SEQ: %u, NUM SEQ: %u, OFFSET: %u\n", ttii, skb_cb->seq_no, skb_cb->num_seqs, skb_cb->offset);
spin_unlock_bh(&ttii->lock);
return;
}
/*
* Remove skb from the recvd_order list (it may be anywhere in this list)
*/
if (!skb_cb->next) {
DEBUG_ASSERT(data->recvd_order_last == skb, "%px: bad list\n", ttii);
data->recvd_order_last = skb_cb->prev;
} else {
struct ecm_tracker_tcp_skb_cb_format *next_cb;
next_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb_cb->next->cb;
DEBUG_CHECK_MAGIC(next_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, next_cb);
next_cb->prev = skb_cb->prev;
}
if (!skb_cb->prev) {
DEBUG_ASSERT(data->recvd_order == skb, "%px: bad list\n", ttii);
data->recvd_order = skb_cb->next;
} else {
struct ecm_tracker_tcp_skb_cb_format *prev_cb;
prev_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb_cb->prev->cb;
DEBUG_CHECK_MAGIC(prev_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, prev_cb);
prev_cb->next = skb_cb->next;
}
/*
* Buffer is to be discarded in its entirety
* Link it out of the in_order list - this datagram will be at the head of the list
*/
data->in_order = skb->next;
if (data->in_order) {
data->in_order->prev = NULL;
} else {
DEBUG_ASSERT(data->in_order_last == skb, "%px: bad list\n", ttii);
data->in_order_last = NULL;
}
/*
* Update our stats
*/
data->in_order_count--;
DEBUG_ASSERT(data->in_order_count >= 0, "%px: bad in order count\n", ttii);
data->recvd_count--;
DEBUG_ASSERT(data->recvd_count >= 0, "%px: bad count\n", ttii);
data->recvd_bytes_total -= skb->truesize;
DEBUG_ASSERT(data->recvd_bytes_total >= 0, "%px: bad bytes count\n", ttii);
ecm_tracker_data_total_decrease(skb->len, skb->truesize);
/*
* Decrement n by the number of segments we are discarding in this segment
*/
n -= skb_cb->num_seqs;
DEBUG_TRACE("%px: BUFFER DISCARD %px, size: %u, new head: %px end: %px, discard remain: %u, in order count: %d\n", ttii, skb, skb_cb->num_seqs, data->in_order, data->in_order_last, n, data->in_order_count);
spin_unlock_bh(&ttii->lock);
dev_kfree_skb_any(skb);
}
}
/*
* ecm_tracker_tcp_bytes_discard_callback()
* Discard n bytes from the available stream bytes
*/
static void ecm_tracker_tcp_bytes_discard_callback(struct ecm_tracker_tcp_instance *tti, ecm_tracker_sender_type_t sender, uint32_t n)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti;
struct ecm_tracker_tcp_host_data *data;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
data = &ttii->sender_data[sender];
ecm_tracker_tcp_bytes_discard(ttii, data, n);
}
/*
* ecm_tracker_tcp_datagram_discard_callback()
* Discard n number of datagrams from the in_order list
*
* NOTE: This discards from the in_order list - this means that any data in the future list cannot be destroyed by this method.
*/
static void ecm_tracker_tcp_datagram_discard_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_type_t sender, int32_t n)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
struct ecm_tracker_tcp_host_data *data;
struct sk_buff *skb;
uint32_t seqs_discard;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
data = &ttii->sender_data[sender];
/*
* Get the number of sequences in n datagrams
*/
seqs_discard = 0;
spin_lock_bh(&ttii->lock);
skb = data->in_order;
while (n) {
struct ecm_tracker_tcp_skb_cb_format *skb_cb;
skb_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb->cb;
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, skb);
seqs_discard += skb_cb->num_seqs;
skb = skb->next;
n--;
}
spin_unlock_bh(&ttii->lock);
/*
* Now simply discard bytes to the tune of seqs_discard!
*/
ecm_tracker_tcp_bytes_discard(ttii, data, seqs_discard);
}
/*
* _ecm_tracker_tcp_discard_all()
* Discard all tracked data
*
* NOTE: Calling this will erase all data within the tracker - including any future unsequenced data. FOR BOTH SENDERS.
* Sequence space tracking shall pick up again on the next tracked data packets.
* Don't call this function often (slow) - usually you call it when your classifier has determined responsibility for a connection and
* it no longer needs any data - and will not, typically, track any further data either.
*/
static void _ecm_tracker_tcp_discard_all(struct ecm_tracker_tcp_internal_instance *ttii)
{
int s;
DEBUG_TRACE("%px: destroy all\n", ttii);
for (s = 0; s < ECM_TRACKER_SENDER_MAX; ++s) {
struct ecm_tracker_tcp_host_data *data;
/*
* Get sender data
*/
data = &ttii->sender_data[s];
/*
* Discard all in the recvd_order list
*/
while (data->recvd_count) {
struct ecm_tracker_tcp_skb_cb_format *skb_cb;
struct sk_buff *skb;
/*
* Get private region of skb that maintains the recvd_order list pointers
*/
skb = data->recvd_order;
skb_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb->cb;
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, skb);
/*
* Remove the skb from the recvd_order list
* NOTE: We don't have to maintain recvd_order_last or any list prev pointers its because we are discarding the lot!
*/
data->recvd_order = skb_cb->next;
data->recvd_count--;
/*
* Destroy
*/
ecm_tracker_data_total_decrease(skb->len, skb->truesize);
dev_kfree_skb_any(skb);
}
DEBUG_ASSERT(data->recvd_order == NULL, "%px: recvd_count not match recvd_order length: %px\n", ttii, data->recvd_order);
data->recvd_order_last = NULL;
data->recvd_bytes_total = 0;
data->in_order = NULL;
data->in_order_last = NULL;
data->in_order_count = 0;
data->future = NULL;
/*
* Invalidate the sequence space
*/
data->seq_no_valid = false;
}
}
/*
* ecm_tracker_tcp_discard_all_callback()
* Discard all tracked data
*
* NOTE: Calling this will erase all data within the tracker - including any future unsequenced data. FOR BOTH SENDERS.
* Sequence space tracking shall pick up again on the next tracked data packets.
* Don't call this function often (slow) - usually you call it when your classifier has determined responsibility for a connection and
* it no longer needs any data - and will not, typically, track any further data either.
*/
static void ecm_tracker_tcp_discard_all_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, "%px: magic failed", ttii);
spin_lock_bh(&ttii->lock);
_ecm_tracker_tcp_discard_all(ttii);
spin_unlock_bh(&ttii->lock);
}
#endif
/*
* 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, "%px: magic failed", ttii);
spin_lock_bh(&ttii->lock);
ttii->refs++;
DEBUG_ASSERT(ttii->refs > 0, "%px: ref wrap", ttii);
DEBUG_TRACE("%px: ref %d\n", ttii, ttii->refs);
spin_unlock_bh(&ttii->lock);
}
/*
* 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, "%px: magic failed", ttii);
spin_lock_bh(&ttii->lock);
ttii->refs--;
refs = ttii->refs;
DEBUG_ASSERT(ttii->refs >= 0, "%px: ref wrap", ttii);
DEBUG_TRACE("%px: deref %d\n", ttii, ttii->refs);
if (ttii->refs > 0) {
spin_unlock_bh(&ttii->lock);
return refs;
}
DEBUG_TRACE("%px: final\n", ttii);
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
_ecm_tracker_tcp_discard_all(ttii);
#endif
spin_unlock_bh(&ttii->lock);
spin_lock_bh(&ecm_tracker_tcp_lock);
ecm_tracker_tcp_count--;
DEBUG_ASSERT(ecm_tracker_tcp_count >= 0, "%px: tracker count wrap", ttii);
spin_unlock_bh(&ecm_tracker_tcp_lock);
DEBUG_INFO("%px: TCP tracker final\n", ttii);
DEBUG_CLEAR_MAGIC(ttii);
kfree(ttii);
return 0;
}
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
/*
* ecm_tracker_tcp_datagram_count_get_callback()
* Return number of available datagrams sent to the specified target in the in_order list
*/
static int32_t ecm_tracker_tcp_datagram_count_get_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_type_t sender)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
int32_t count;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
spin_lock_bh(&ttii->lock);
count = ttii->sender_data[sender].in_order_count;
spin_unlock_bh(&ttii->lock);
DEBUG_TRACE("%px: datagram count get for %d is %d\n", ttii, sender, count);
return count;
}
/*
* ecm_tracker_tcp_datagram_size_get_callback()
* Return size in bytes of in_order datagram at index i that was sent by the sender
*/
static int32_t ecm_tracker_tcp_datagram_size_get_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_type_t sender, int32_t i)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
int32_t size;
struct sk_buff *skb;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
DEBUG_TRACE("%px: get datagram size at %u for %d\n", ttii, i, sender);
/*
* Which list?
*/
spin_lock_bh(&ttii->lock);
skb = ttii->sender_data[sender].in_order;
/*
* Iterate to the i'th datagram
*/
while (i) {
DEBUG_ASSERT(skb, "%px: index bad\n", ttii);
skb = skb->next;
i--;
}
DEBUG_ASSERT(skb, "%px: index bad\n", ttii);
/*
* Get size
*/
size = skb->len;
spin_unlock_bh(&ttii->lock);
DEBUG_TRACE("%px: datagram size is %d\n", ttii, size);
return size;
}
/*
* ecm_tracker_tcp_datagram_read_callback()
* Read size bytes from the i'th in_order datagram
*/
static int ecm_tracker_tcp_datagram_read_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_type_t sender, int32_t i, int32_t offset, int32_t size, void *buffer)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
int res;
struct sk_buff *skb;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_TRACE("%px: datagram %d read at offset %d for %d bytes for %d\n", ttii, i, offset, size, sender);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
spin_lock_bh(&ttii->lock);
skb = ttii->sender_data[sender].in_order;
/*
* Iterate to the i'th datagram
*/
while (i) {
DEBUG_ASSERT(skb, "%px: index bad\n", ttii);
skb = skb->next;
i--;
}
DEBUG_ASSERT(skb, "%px: index bad\n", ttii);
/*
* Perform read
*/
res = skb_copy_bits(skb, offset, buffer, (unsigned int)size);
spin_unlock_bh(&ttii->lock);
return res;
}
/*
* _ecm_tracker_tcp_data_future_replay()
* Replay packets in the future data list to see if any are now in sequence.
*
* NOTE: 'Future' is a misnomer here because the arrival of data may have actually cause some future data
* to now be old, example:
* SPACE: --------------CCCCCCCC-------
* FUTURE: ------------------------FFF--
* NEW: ----------------------NNNNNN
* As you can see the future data is now rendered old by the arrival of sequences
*
* NOTE: The future data list may also contain duplicate future data - we weren't that fussy when we inserted the segment into the future list.
*/
static void _ecm_tracker_tcp_data_future_replay(struct ecm_tracker_tcp_internal_instance *ttii,
struct ecm_tracker_tcp_host_data *data)
{
while (data->future) {
struct ecm_tracker_tcp_skb_cb_format *skb_cb;
struct sk_buff *skb;
int32_t in_sequence;
int32_t new_seqs;
int32_t offset;
/*
* Get our private area of future buffer
*/
skb = data->future;
skb_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb->cb;
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, skb_cb);
/*
* Identify the sequence space
*/
in_sequence = (int32_t)(skb_cb->seq_no - (data->seq_no + data->num_seqs));
new_seqs = (int32_t)(skb_cb->num_seqs + in_sequence);
offset = -in_sequence;
DEBUG_TRACE("%px: FUTURE REPLAY %px: in_sequence: %d, new_seqs: %d, offset: %d\n", ttii, skb, in_sequence, new_seqs, offset);
/*
* Is the segment still in the future?
*/
if (in_sequence > 0) {
/*
* Still awaiting sequences until this segment becomes valid.
* We are done
*/
DEBUG_TRACE("%px: FUTURE REPLAY FUTURE SEQ: %u LEN:%u\n", ttii, skb_cb->seq_no, skb_cb->num_seqs);
break;
}
/*
* Whatever happens now we are definately linking this skb out of the futures list
*/
data->future = skb->next;
if (data->future) {
data->future->prev = NULL;
}
/*
* Does the segment contain only old data?
*/
if (new_seqs <= 0) {
/*
* all old - skb needs to be discarded
*/
DEBUG_TRACE("%px: FUTURE REPLAY OLD SEQ: %u LEN:%u, new future head: %px\n", ttii, skb_cb->seq_no, skb_cb->num_seqs, data->future);
/*
* Remove skb from the recvd_order list (it may be anywhere in this list)
*/
if (!skb_cb->next) {
DEBUG_ASSERT(data->recvd_order_last == skb, "%px: bad list\n", ttii);
data->recvd_order_last = skb_cb->prev;
} else {
struct ecm_tracker_tcp_skb_cb_format *next_cb;
next_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb_cb->next->cb;
DEBUG_CHECK_MAGIC(next_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, next_cb);
next_cb->prev = skb_cb->prev;
}
if (!skb_cb->prev) {
DEBUG_ASSERT(data->recvd_order == skb, "%px: bad list\n", ttii);
data->recvd_order = skb_cb->next;
} else {
struct ecm_tracker_tcp_skb_cb_format *prev_cb;
prev_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb_cb->prev->cb;
DEBUG_CHECK_MAGIC(prev_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, prev_cb);
prev_cb->next = skb_cb->next;
}
/*
* Update stats
*/
data->recvd_count--;
DEBUG_ASSERT(data->recvd_count >= 0, "%px: bad count\n", ttii);
data->recvd_bytes_total -= skb->truesize;
ecm_tracker_data_total_decrease(skb->len, skb->truesize);
DEBUG_ASSERT(data->recvd_bytes_total >= 0, "%px: bad bytes count\n", ttii);
/*
* Release skb containing obsoleted data
*/
dev_kfree_skb_any(skb);
continue;
}
/*
* We are dealing with new or some new/old data.
*/
DEBUG_ASSERT(offset >= 0, "%px: alg bad, offset negative at %d\n", ttii, offset);
/*
* Initialise the private cb space
* The sequence space recorded here is READABLE space i.e. any old/dup is removed
*/
skb_cb->seq_no += offset; /* Advance sequence space to new data sequences */
skb_cb->num_seqs = (uint32_t)new_seqs;
skb_cb->offset += offset; /* Add new offset to existing offset */
/*
* Insert the skb onto the end of the data list - it must be the end because the data here is considered in order
*/
skb->next = NULL;
skb->prev = data->in_order_last;
data->in_order_last = skb;
if (skb->prev) {
skb->prev->next = skb;
} else {
/*
* If there is no data_end then skb is at the head of the list
*/
DEBUG_ASSERT(data->in_order == NULL, "%px: bad data_end, data points to %px\n", ttii, data->in_order);
data->in_order = skb;
}
data->in_order_count++;
DEBUG_ASSERT(data->in_order_count > 0, "%px: invalid in_order count %d for data %px\n", ttii, data->in_order_count, data);
/*
* Update our contiguous space pointer
*/
data->num_seqs += (uint32_t)new_seqs;
DEBUG_TRACE("%px: FUTURE REPLAY IN SEQ:%u LEN:%u. Inserted between %px and %px. SEQ SPACE: %u, len %u, in_order_count %d\n", ttii, skb_cb->seq_no, skb_cb->num_seqs, skb_cb->prev,
skb_cb->next, data->seq_no, data->num_seqs, data->in_order_count);
}
DEBUG_TRACE("%px: FUTURE REPLAY COMPLETE\n", ttii);
}
/*
* _ecm_tracker_tcp_stream_segment_add()
* Add skb buff into our stream tracking lists if appropriate.
*
* Must pass a validly formed tcp segment to this function.
* Returns true when the segment is to be recorded in the recvd_order list.
*/
static bool _ecm_tracker_tcp_stream_segment_add(struct ecm_tracker_tcp_internal_instance *ttii,
struct ecm_tracker_tcp_host_data *data,
struct ecm_tracker_ip_header *ip_hdr, struct ecm_tracker_ip_protocol_header *ecm_tcp_header,
struct tcphdr *tcp_hdr, struct sk_buff *skb,
struct ecm_tracker_tcp_skb_cb_format *skb_cb)
{
uint16_t data_offset;
uint16_t data_len;
uint32_t seq;
uint32_t num_seqs;
int32_t in_sequence;
int32_t new_seqs;
int32_t offset;
/*
* We should have been passed a valid cb area
*/
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: skb bad cb magic\n", ttii);
/*
* Reset? ignore
*/
if (unlikely(tcp_hdr->rst)) {
DEBUG_TRACE("%px: reset seen - ignoring\n", ttii);
return false;
}
/*
* Figure out the data length in this TCP segment.
*/
DEBUG_ASSERT((ecm_tcp_header->offset + ecm_tcp_header->size) <= ip_hdr->total_length, "%px: invalid hdrs\n", ttii);
data_offset = ecm_tcp_header->offset + ecm_tcp_header->header_size;
data_len = ecm_tcp_header->size - ecm_tcp_header->header_size;
DEBUG_TRACE("%px: data offset: %u, data_len = %u\n", ttii, data_offset, data_len);
/*
* Set sequence number of first byte of data in the header.
* NOTE: If the syn flag is set then this is sequenced like a data byte
*/
seq = ntohl(tcp_hdr->seq);
/*
* If SYN or FIN are set then we have one sequence
*/
if (unlikely(tcp_hdr->syn)) {
/*
* First actual byte of data has the next sequence number
*/
seq++;
}
/*
* If our sequence space is not yet validated then we take the sequence number of this packet
* to validate our sequence numbering
*/
if (unlikely(!data->seq_no_valid)) {
DEBUG_ASSERT(data->in_order == NULL, "%px: in_order not null %px\n", ttii, data->in_order);
DEBUG_ASSERT(data->in_order_last == NULL, "%px: in_order_last not null %px\n", ttii, data->in_order_last);
DEBUG_ASSERT(data->in_order_count == 0, "%px: in_order_count not 0: %d\n", ttii, data->in_order_count);
/*
* Sequence numbers are not valid at the moment so this segment is where we start from
*/
data->seq_no = seq;
data->num_seqs = 0;
data->seq_no_valid = true;
DEBUG_TRACE("%px: FIRST SEQ:%u\n", ttii, seq);
}
/*
* Do we have any data?
*/
if (data_len == 0) {
return false;
}
/*
* Examine the data sequence space:
* It contains in sequence new data - USE NEW.
* It is old data - IGNORE
* It is duplicate - IGNORE
* It contains some old, duplicate, new - IGNORE OLD and DUPLICATE. USE NEW.
* It contains some future - ADD TO FUTURES LIST
* Sequence space possibilities:
* SEQ SPACE: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
* CURRENT SPACE: -----------------CCCCCCCCCCCCCCCCCC---------------------------
* NEW: -----------------------------------NNN------------------------
* ALL OLD: ----------OOOOOOO---------------------------------------------
* DUPLICATE: -----------------DDDDDDD--------------------------------------
* OLD/DUP/NEW: --------------OOODDDDDDDDDDDDDDDDDDNNNNNNNNNNN----------------
* FUTURE: ------------------------------------F-------------------------
*/
/*
* Number of sequences is now the data length
*/
num_seqs = data_len;
/*
* Is the segment in sequence?
* Sequence space scenarios:
* data->seq data->num_seqs seq num_seqs in_sequence new_seqs Offset Result?
* 100 20 120 30 0 30 0 In sequence 30 bytes, new data is from the first data byte in skb
* 100 20 121 30 1 31 -1 Future, new_seqs is irrelevant here, offset irrelevant
* 100 20 119 30 -1 29 1 1 DUP, 29 new, offset to new data is 1
* 100 20 100 30 -20 10 20 20 DUP, 10 new, offset to new data is 20
* 100 20 99 30 -21 9 21 21 DUP, 9 new, offset to new data is 21
* 100 20 90 30 -30 0 30 Nothing new, offset irrelevant
* 100 20 70 30 -50 -20 50 Nothing new, offset irrelevant
*/
in_sequence = (int32_t)(seq - (data->seq_no + data->num_seqs));
new_seqs = (int32_t)(num_seqs + in_sequence);
offset = -in_sequence;
DEBUG_TRACE("%px: in_sequence: %d, new_seqs: %d, offset: %d\n", ttii, in_sequence, new_seqs, offset);
/*
* Does the segment contain only old data?
*/
if (new_seqs <= 0) {
/*
* all old
*/
DEBUG_TRACE("%px: OLD SEQ: %u LEN:%u\n", ttii, seq, num_seqs);
return false;
}
/*
* Is the segment in the future?
*/
if (unlikely(in_sequence > 0)) {
/*
* Packet contains sequences in the future
*/
struct sk_buff *nskb;
struct sk_buff *pskb;
/*
* Initialise the private cb space
* Note that sequence space here may duplicate other future data but that will be corrected when future data is sorted out.
*/
skb_cb->seq_no = seq;
skb_cb->num_seqs = num_seqs;
skb_cb->offset = data_offset;
DEBUG_INFO("%px: FUTURE SEQ: %u LEN:%u\n", ttii, seq, num_seqs);
/*
* Insert the segment into the future list in order of sequence number
*/
nskb = data->future;
pskb = NULL;
while (nskb) {
struct ecm_tracker_tcp_skb_cb_format *nskb_cb;
nskb_cb = (struct ecm_tracker_tcp_skb_cb_format *)nskb->cb;
DEBUG_CHECK_MAGIC(nskb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, nskb_cb);
DEBUG_TRACE("%px: seq: %u, nskb_cb->seq: %u\n", ttii, seq, nskb_cb->seq_no);
if (seq <= nskb_cb->seq_no) {
break;
}
pskb = nskb;
nskb = nskb->next;
}
DEBUG_TRACE("%px: Insert after %px and before %px\n", ttii, pskb, nskb);
/*
* Insert skb between pskb and nskb in the futures list
*/
skb->next = nskb;
skb->prev = pskb;
if (nskb) {
nskb->prev = skb;
}
if (pskb) {
pskb->next = skb;
} else {
/*
* Head of the list
*/
DEBUG_ASSERT(data->future == nskb, "%px: invalid future list %px != %px\n", ttii, data->future, nskb);
data->future = skb;
}
DEBUG_TRACE("%px: FUTURE SEQ:%u LEN:%u. Inserted after %px and before %px\n", ttii, seq, num_seqs, pskb, nskb);
return true;
}
/*
* We are dealing with new or some new/old data.
*/
DEBUG_ASSERT(offset >= 0, "%px: alg bad, offset negative at %d\n", ttii, offset);
/*
* Initialise the private cb space
* The sequence space recorded here is READABLE space i.e. any old/dup is removed
*/
skb_cb->seq_no = seq + offset;
skb_cb->num_seqs = (uint32_t)new_seqs;
skb_cb->offset = data_offset + offset;
/*
* Insert the skb onto the end of the data list - it must be the end because the data here is considered in order
*/
skb->next = NULL;
skb->prev = data->in_order_last;
data->in_order_last = skb;
if (skb->prev) {
skb->prev->next = skb;
} else {
/*
* If there is no data_end then skb is at the head of the list
*/
DEBUG_ASSERT(data->in_order == NULL, "%px: bad data_end, data points to %px\n", ttii, data->in_order);
data->in_order = skb;
}
data->in_order_count++;
DEBUG_ASSERT(data->in_order_count > 0, "%px: invalid in_order count %d for data %px\n", ttii, data->in_order_count, data);
/*
* Update our contiguous space pointers
*/
data->num_seqs += (uint32_t)new_seqs;
DEBUG_TRACE("%px: IN SEQ:%u LEN:%u. Inserted between %px and %px. SEQ SPACE starts at: %u, len %u, in_order_count %d\n",
ttii, skb_cb->seq_no, skb_cb->num_seqs, skb_cb->prev, skb_cb->next, data->seq_no, data->num_seqs, data->in_order_count);
/*
* Do we have any future data? It's likely we won't as it is rare for packets to arrive out of sequence in this way.
*/
if (likely(!data->future)) {
return true;
}
/*
* We now need to process future data - the reception of seqences here may now release some
*/
_ecm_tracker_tcp_data_future_replay(ttii, data);
return true;
}
/*
* ecm_tracker_tcp_extract_mss()
* Read the MSS from the options block of the tcp header
*/
static bool ecm_tracker_tcp_extract_mss(struct sk_buff *skb, uint16_t *mss, struct ecm_tracker_ip_protocol_header *ecm_tcp_header)
{
struct tcp_options_received opt_rx;
/*
* Set he transport header offset (tcp) - unlikely not to be set BUT we don't take the chance
*/
skb_set_transport_header(skb, ecm_tcp_header->offset);
/*
* Parse the TCP header options
*/
memset(&opt_rx, 0, sizeof(opt_rx));
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0))
tcp_parse_options(skb, &opt_rx, 0, NULL);
#else
tcp_parse_options(dev_net(skb->dev), skb, &opt_rx, 0, NULL);
#endif
/*
* Was there an MSS?
*/
if (!opt_rx.mss_clamp) {
DEBUG_WARN("No mss clamp in skb %px\n", skb);
return false;
}
DEBUG_INFO("MSS clamp seen in %px as %u\n", skb, opt_rx.mss_clamp);
*mss = opt_rx.mss_clamp;
return true;
}
/*
* ecm_tracker_tcp_segment_add_callback()
* Append the segment onto the tracker queue for the given target
*/
static bool ecm_tracker_tcp_segment_add_callback(struct ecm_tracker_tcp_instance *tti, ecm_tracker_sender_type_t sender,
struct ecm_tracker_ip_header *ip_hdr, struct ecm_tracker_ip_protocol_header *ecm_tcp_header,
struct tcphdr *tcp_hdr, struct sk_buff *skb)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti;
struct sk_buff *skbc;
struct ecm_tracker_tcp_host_data *data;
struct ecm_tracker_tcp_skb_cb_format *skbc_cb;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
DEBUG_ASSERT(ecm_tcp_header->protocol_number == IPPROTO_TCP, "%px: invalid sender %u\n", ttii, ecm_tcp_header->protocol_number);
DEBUG_TRACE("%px: segment %px add for %d\n", ttii, skb, sender);
/*
* Clone the packet
*/
skbc = skb_clone(skb, GFP_ATOMIC | __GFP_NOWARN);
if (!skbc) {
DEBUG_WARN("%px: Failed to clone packet %px\n", ttii, skb);
return false;
}
DEBUG_TRACE("%px: cloned %px to %px\n", ttii, skb, skbc);
/*
* Get the private cb area and initialise it.
* ALL SEGMENTS HAVE TO HAVE ONE.
*/
skbc_cb = (struct ecm_tracker_tcp_skb_cb_format *)skbc->cb;
DEBUG_SET_MAGIC(skbc_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC);
spin_lock_bh(&ttii->lock);
/*
* Are we within instance limit?
*/
DEBUG_ASSERT((ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].recvd_bytes_total + ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].recvd_bytes_total + skbc->truesize) > 0, "%px: bad total\n", ttii);
if ((ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].recvd_bytes_total + ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].recvd_bytes_total + skbc->truesize) > ttii->data_limit) {
DEBUG_TRACE("%px: over limit\n", ttii);
spin_unlock_bh(&ttii->lock);
dev_kfree_skb_any(skbc);
return false;
}
/*
* Within global limit?
*/
if (!ecm_tracker_data_total_increase(skbc->len, skbc->truesize)) {
DEBUG_TRACE("%px: over global limit\n", ttii);
spin_unlock_bh(&ttii->lock);
dev_kfree_skb_any(skbc);
return false;
}
/*
* Which list?
*/
data = &ttii->sender_data[sender];
/*
* Do we have the MSS for this host? Unlikely as most streams we would have seen the SYN already and retrieved the MSS.
*/
if (unlikely(tcp_hdr->syn) && unlikely(!data->mss_seen)) {
uint16_t mss;
if (ecm_tracker_tcp_extract_mss(skbc, &mss, ecm_tcp_header)) {
data->mss = mss;
data->mss_seen = true;
DEBUG_TRACE("%px: Seen mss as %u for %px\n", ttii, mss, data);
}
}
/*
* If sequence tracking is enabled but the segment is not wanted then we simply discard it
*/
if (!_ecm_tracker_tcp_stream_segment_add(ttii, data, ip_hdr, ecm_tcp_header, tcp_hdr, skbc, skbc_cb)) {
spin_unlock_bh(&ttii->lock);
ecm_tracker_data_total_decrease(skbc->len, skbc->truesize);
dev_kfree_skb_any(skbc);
/*
* Return true - there is nothing wrong with the datagram we just don't want this datagram but we will still
* allow it to pass and still want to account for it in the connection stats
*/
return true;
}
/*
* Store the packet in the reception order list.
* NOTE: When sequence tracking is not enabled we want to store things as they are received no matter what.
* When we are sequence tracking we only store the skb when the segment has been added successfully
*/
skbc_cb->next = NULL;
skbc_cb->prev = data->recvd_order_last;
data->recvd_order_last = skbc;
if (!skbc_cb->prev) {
DEBUG_ASSERT(data->recvd_order == NULL, "%px: bad list\n", ttii);
data->recvd_order = skbc;
} else {
struct ecm_tracker_tcp_skb_cb_format *prev_cb;
prev_cb = (struct ecm_tracker_tcp_skb_cb_format *)skbc_cb->prev->cb;
DEBUG_CHECK_MAGIC(prev_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, prev_cb);
prev_cb->next = skbc;
}
/*
* Update stats
*/
data->recvd_count++;
DEBUG_ASSERT(data->recvd_count > 0, "%px: bad count\n", ttii);
data->recvd_bytes_total += skbc->truesize;
DEBUG_TRACE("%px: Segment %px added between %px and %px. recvd total %u, bytes total %u\n",
ttii, skbc, skbc->prev, skbc->next, data->recvd_count, data->recvd_bytes_total);
spin_unlock_bh(&ttii->lock);
return true;
}
/*
* ecm_tracker_tcp_datagram_add_callback()
* Append the datagram onto the tracker queue for the given target
*/
static bool ecm_tracker_tcp_datagram_add_callback(struct ecm_tracker_instance *ti, ecm_tracker_sender_type_t sender, struct sk_buff *skb)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
struct sk_buff *skbc;
struct ecm_tracker_tcp_skb_cb_format *skbc_cb;
struct ecm_tracker_ip_header ip_hdr;
struct ecm_tracker_ip_protocol_header *ecm_tcp_header;
struct tcphdr tcp_hdr_buff;
struct tcphdr *tcp_hdr;
struct ecm_tracker_tcp_host_data *data;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
DEBUG_TRACE("%px: datagram %px add for sender: %d\n", ttii, skb, sender);
/*
* Clone the packet
*/
skbc = skb_clone(skb, GFP_ATOMIC | __GFP_NOWARN);
if (!skbc) {
DEBUG_WARN("%px: Failed to clone packet %px\n", ttii, skb);
return false;
}
DEBUG_TRACE("%px: cloned %px to %px\n", ttii, skb, skbc);
/*
* Get the private cb area and initialise it.
* ALL SEGMENTS HAVE TO HAVE ONE.
*/
skbc_cb = (struct ecm_tracker_tcp_skb_cb_format *)skbc->cb;
DEBUG_SET_MAGIC(skbc_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC);
/*
* Obtain the IP header from the skb
*/
if (!ecm_tracker_ip_check_header_and_read(&ip_hdr, skb)) {
DEBUG_WARN("%px: no ip_hdr for %px\n", ttii, skbc);
dev_kfree_skb_any(skbc);
return false;
}
/*
* Extract the TCP header
*/
tcp_hdr = ecm_tracker_tcp_check_header_and_read(skbc, &ip_hdr, &tcp_hdr_buff);
if (!tcp_hdr) {
DEBUG_WARN("%px: invalid tcp header %px\n", ttii, skbc);
dev_kfree_skb_any(skbc);
return false;
}
ecm_tcp_header = &ip_hdr.headers[ECM_TRACKER_IP_PROTOCOL_TYPE_TCP];
spin_lock_bh(&ttii->lock);
/*
* Are we within instance limit?
*/
DEBUG_ASSERT((ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].recvd_bytes_total + ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].recvd_bytes_total + skbc->truesize) > 0, "%px: bad total\n", ttii);
if ((ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].recvd_bytes_total + ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].recvd_bytes_total + skbc->truesize) > ttii->data_limit) {
DEBUG_TRACE("%px: over limit: SRC recvd_bytes_total: %u, DEST recvd_bytes_total: %u, skb true_size: %u, limit: %u\n",
ttii,
ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].recvd_bytes_total,
ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].recvd_bytes_total,
skbc->truesize,
ttii->data_limit);
spin_unlock_bh(&ttii->lock);
dev_kfree_skb_any(skbc);
return false;
}
/*
* Within global limit?
*/
if (!ecm_tracker_data_total_increase(skbc->len, skbc->truesize)) {
DEBUG_TRACE("%px: over global limit\n", ttii);
spin_unlock_bh(&ttii->lock);
dev_kfree_skb_any(skbc);
return false;
}
/*
* Which list?
*/
data = &ttii->sender_data[sender];
/*
* Do we have the MSS for this host? Unlikely as most streams we would have seen the SYN already and retrieved the MSS.
*/
if (unlikely(tcp_hdr->syn) && unlikely(!data->mss_seen)) {
uint16_t mss;
if (ecm_tracker_tcp_extract_mss(skbc, &mss, ecm_tcp_header)) {
data->mss = mss;
data->mss_seen = true;
DEBUG_TRACE("%px: Seen mss as %u for %px\n", ttii, mss, data);
}
}
/*
* If sequence tracking is enabled but the segment is not wanted then we simply discard it
*/
if (!_ecm_tracker_tcp_stream_segment_add(ttii, data, &ip_hdr, ecm_tcp_header, tcp_hdr, skbc, skbc_cb)) {
spin_unlock_bh(&ttii->lock);
ecm_tracker_data_total_decrease(skbc->len, skbc->truesize);
dev_kfree_skb_any(skbc);
/*
* Return true - there is nothing wrong with the datagram we just don't want this datagram but we will still
* allow it to pass and still want to account for it in the connection stats
*/
return true;
}
/*
* Store the packet in the reception order list.
* NOTE: When sequence tracking is not enabled we want to store things as they are received no matter what.
* When we are sequence tracking we only store the skb when the segment has been added successfully
*/
skbc_cb->next = NULL;
skbc_cb->prev = data->recvd_order_last;
data->recvd_order_last = skbc;
if (!skbc_cb->prev) {
DEBUG_ASSERT(data->recvd_order == NULL, "%px: bad list\n", ttii);
data->recvd_order = skbc;
} else {
struct ecm_tracker_tcp_skb_cb_format *prev_cb;
prev_cb = (struct ecm_tracker_tcp_skb_cb_format *)skbc_cb->prev->cb;
DEBUG_CHECK_MAGIC(prev_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, prev_cb);
prev_cb->next = skbc;
}
/*
* Update stats
*/
data->recvd_count++;
DEBUG_ASSERT(data->recvd_count > 0, "%px: bad count\n", ttii);
data->recvd_bytes_total += skbc->truesize;
DEBUG_TRACE("%px: Segment %px added between %px and %px. recvd total %u, bytes total %u\n",
ttii, skbc, skbc->prev, skbc->next, data->recvd_count, data->recvd_bytes_total);
spin_unlock_bh(&ttii->lock);
return true;
}
/*
* ecm_tracker_tcp_bytes_avail_get_callback()
* Return number of bytes available to read
*/
static uint32_t ecm_tracker_tcp_bytes_avail_get_callback(struct ecm_tracker_tcp_instance *tti, ecm_tracker_sender_type_t sender)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti;
struct ecm_tracker_tcp_host_data *data;
uint32_t bytes_avail;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
data = &ttii->sender_data[sender];
/*
* The number of available bytes is our sequence space
*/
spin_lock_bh(&ttii->lock);
bytes_avail = data->num_seqs;
spin_unlock_bh(&ttii->lock);
DEBUG_TRACE("%px: get bytes available for %d: %u\n", ttii, sender, bytes_avail);
return bytes_avail;
}
/*
* ecm_tracker_tcp_bytes_read_callback()
* Read a number of bytes
*/
static int ecm_tracker_tcp_bytes_read_callback(struct ecm_tracker_tcp_instance *tti, ecm_tracker_sender_type_t sender, uint32_t offset, uint32_t size, void *buffer)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti;
struct ecm_tracker_tcp_host_data *data;
uint32_t read_start_seq;
uint32_t seq_offset;
uint32_t seg_seq_avail;
struct sk_buff *skb;
int res;
uint8_t *p;
struct ecm_tracker_tcp_skb_cb_format *skb_cb;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
data = &ttii->sender_data[sender];
/*
* Compute the sequence to start reading at given the offset
*/
spin_lock_bh(&ttii->lock);
DEBUG_ASSERT((offset + size) <= data->num_seqs, "%px: attempt to read %u bytes from offset %u. Available bytes is %u\n", ttii, size, offset, data->num_seqs);
read_start_seq = data->seq_no + offset;
DEBUG_TRACE("%px: seq: %u, num_seqs: %u, offset: %u, size: %u, read_start_seq: %u\n", ttii, data->seq_no, data->num_seqs, offset, size, read_start_seq);
/*
* Identify which skb in our sequence space seq_start begins at
*/
skb = data->in_order;
while (skb) {
uint32_t next_seg_seq;
skb_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb->cb;
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, skb_cb);
/*
* Get the sequence number of the start of the next segment
*/
next_seg_seq = skb_cb->seq_no + skb_cb->num_seqs;
/*
* If the point at which we start reading is at or greater then the next segment sequence then we need to advance onto that
* i.e. skb does not contain the sequences we need
*/
if (read_start_seq >= next_seg_seq) {
DEBUG_TRACE("%px: skip skb %px, we start at %u and this segment ends at %u\n", ttii, skb, read_start_seq, next_seg_seq - 1);
skb = skb->next;
continue;
}
/*
* We start reading at this skb
*/
DEBUG_TRACE("%px: skb to start reading from is at %px, seq_no of this is %u\n", ttii, skb, skb_cb->seq_no);
break;
}
DEBUG_ASSERT(skb, "%px: no read start could be found\n", ttii);
/*
* Finally perform read
*/
/*
* We may need to apply an offset to the first skb to get to our seq_start point
*/
seq_offset = read_start_seq - skb_cb->seq_no;
seg_seq_avail = skb_cb->num_seqs - seq_offset;
DEBUG_TRACE("%px: seq_offset: %u, avail: %u, want %u bytes\n", ttii, seq_offset, seg_seq_avail, size);
if (seg_seq_avail > size) {
seg_seq_avail = size;
}
/*
* Read that data.
*/
p = (uint8_t *)buffer;
res = skb_copy_bits(skb, skb_cb->offset + seq_offset, p, seg_seq_avail);
if (res < 0) {
spin_unlock_bh(&ttii->lock);
return res;
}
p += seg_seq_avail;
size -= seg_seq_avail;
/*
* Read bytes
*/
while (size) {
uint32_t read_seq;
skb = skb->next;
DEBUG_ASSERT(skb, "%px: size %u not zero but no skb\n", ttii, size);
skb_cb = (struct ecm_tracker_tcp_skb_cb_format *)skb->cb;
DEBUG_CHECK_MAGIC(skb_cb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid cb magic %px\n", ttii, skb_cb);
/*
* Read available bytes from this segment.
* No offset here as it's linear all the way from this point on.
*/
read_seq = skb_cb->num_seqs;
if (read_seq > size) {
read_seq = size;
}
DEBUG_TRACE("%px: Read %u sequences from seq_no %u\n", ttii, read_seq, skb_cb->seq_no);
res = skb_copy_bits(skb, skb_cb->offset, p, read_seq);
if (res < 0) {
spin_unlock_bh(&ttii->lock);
return res;
}
p += read_seq;
size -= read_seq;
}
spin_unlock_bh(&ttii->lock);
return 0;
}
/*
* ecm_tracker_tcp_mss_get_callback()
* Get the MSS as sent BY the given target i.e. the MSS the other party is allowed to send
*
* Returns true if the MSS was seen in a SYN or false is system default
*/
static bool ecm_tracker_tcp_mss_get_callback(struct ecm_tracker_tcp_instance *tti, ecm_tracker_sender_type_t sender, uint16_t *mss)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti;
struct ecm_tracker_tcp_host_data *data;
bool res;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", ttii, sender);
/*
* Which list?
*/
data = &ttii->sender_data[sender];
spin_lock_bh(&ttii->lock);
*mss = data->mss;
res = data->mss_seen;
spin_unlock_bh(&ttii->lock);
DEBUG_TRACE("%px: get mss for %d: %u, seen? %u\n", ttii, sender, *mss, res);
return res;
}
/*
* ecm_tracker_tcp_data_total_get_callback()
* Return total tracked data
*/
static int32_t ecm_tracker_tcp_data_total_get_callback(struct ecm_tracker_instance *ti)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
int32_t data_total;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
spin_lock_bh(&ttii->lock);
data_total = ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].recvd_bytes_total + ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].recvd_bytes_total;
spin_unlock_bh(&ttii->lock);
return data_total;
}
/*
* ecm_tracker_tcp_data_limit_get_callback()
* Get tracked data limit
*/
static int32_t ecm_tracker_tcp_data_limit_get_callback(struct ecm_tracker_instance *ti)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
int32_t data_limit;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
spin_lock_bh(&ttii->lock);
data_limit = ttii->data_limit;
spin_unlock_bh(&ttii->lock);
return data_limit;
}
/*
* ecm_tracker_tcp_data_limit_set_callback()
* Set tracked data limit
*/
static void ecm_tracker_tcp_data_limit_set_callback(struct ecm_tracker_instance *ti, int32_t data_limit)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
spin_lock_bh(&ttii->lock);
ttii->data_limit = data_limit;
spin_unlock_bh(&ttii->lock);
}
#endif
/*
* 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, "%px: magic failed", ttii);
/*
* Get refereces to states
*/
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: 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("%px: no tcp_hdr for %px\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("%px: 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("%px: 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("%px: ack %u for syn_seq %u? ackd = %d\n", ttii, ack_seq, syn_seq, ackd);
if (ackd <= 0) {
DEBUG_TRACE("%px: No change\n", ttii);
} else {
DEBUG_INFO("%px: 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("%px: ack %u for fin_seq %u? ackd = %d\n", ttii, ack_seq, fin_seq, ackd);
if (ackd <= 0) {
DEBUG_TRACE("%px: No change\n", ttii);
} else {
DEBUG_TRACE("%px: 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, "%px: 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("%px: 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("%px: 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, "%px: 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("%px: 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("%px: 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("%px: 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("%px: 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, "%px: unhandled state: %d\n", ttii, sender_state_current);
}
}
spin_unlock_bh(&ttii->lock);
}
/*
* 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, "%px: 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];
}
#ifdef ECM_STATE_OUTPUT_ENABLE
/*
* ecm_tracker_tcp_sender_state_get()
* Return state
*/
static int ecm_tracker_tcp_sender_state_get(struct ecm_state_file_instance *sfi, ecm_tracker_sender_type_t sender,
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
struct ecm_tracker_tcp_host_data *data,
#endif
struct ecm_tracker_tcp_sender_state *state)
{
int result;
if ((result = ecm_state_write(sfi, "state", "%s", ecm_tracker_sender_state_to_string(state->state)))) {
return result;
}
if ((result = ecm_state_write(sfi, "syn_seq", "%u", state->syn_seq))) {
return result;
}
if ((result = ecm_state_write(sfi, "fin_seq", "%u", state->fin_seq))) {
return result;
}
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
if ((result = ecm_state_write(sfi, "mss_seen", "%s", (data->mss_seen)? "yes" : "no"))) {
return result;
}
if ((result = ecm_state_write(sfi, "mss", "%u", data->mss))) {
return result;
}
if ((result = ecm_state_write(sfi, "seq_no", "%u", data->seq_no))) {
return result;
}
if ((result = ecm_state_write(sfi, "num_seqs", "%u", data->num_seqs))) {
return result;
}
if ((result = ecm_state_write(sfi, "seq_valid", "%s", (data->seq_no_valid)? "yes" : "no"))) {
return result;
}
if ((result = ecm_state_write(sfi, "bytes_total", "%d", data->recvd_bytes_total))) {
return result;
}
if ((result = ecm_state_write(sfi, "buffers_total", "%d", data->recvd_count))) {
return result;
}
#endif
return 0;
}
/*
* ecm_tracker_tcp_state_text_get_callback()
* Return state
*/
static int ecm_tracker_tcp_state_text_get_callback(struct ecm_tracker_instance *ti, struct ecm_state_file_instance *sfi)
{
int result;
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)ti;
struct ecm_tracker_tcp_sender_state sender_states[ECM_TRACKER_SENDER_MAX];
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
struct ecm_tracker_tcp_host_data sender_data[ECM_TRACKER_SENDER_MAX];
int32_t data_limit;
#endif
ecm_tracker_connection_state_t connection_state;
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
if ((result = ecm_state_prefix_add(sfi, "tracker_tcp"))) {
return result;
}
/*
* Capture state
*/
spin_lock_bh(&ttii->lock);
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
data_limit = ttii->data_limit;
sender_data[ECM_TRACKER_SENDER_TYPE_SRC] = ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC];
sender_data[ECM_TRACKER_SENDER_TYPE_DEST] = ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST];
#endif
sender_states[ECM_TRACKER_SENDER_TYPE_SRC] = ttii->sender_states[ECM_TRACKER_SENDER_TYPE_SRC];
sender_states[ECM_TRACKER_SENDER_TYPE_DEST] = ttii->sender_states[ECM_TRACKER_SENDER_TYPE_DEST];
spin_unlock_bh(&ttii->lock);
connection_state = ecm_tracker_tcp_connection_state_matrix[sender_states[ECM_TRACKER_SENDER_TYPE_SRC].state][sender_states[ECM_TRACKER_SENDER_TYPE_DEST].state];
if ((result = ecm_state_write(sfi, "connection_state", "%s", ecm_tracker_connection_state_to_string(connection_state)))) {
return result;
}
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
if ((result = ecm_state_write(sfi, "data_limit", "%d", data_limit))) {
return result;
}
#endif
if ((result = ecm_state_prefix_add(sfi, "senders"))) {
return result;
}
/*
* Output src sender
*/
if ((result = ecm_state_prefix_add(sfi, "src"))) {
return result;
}
if ((result = ecm_tracker_tcp_sender_state_get(sfi,
ECM_TRACKER_SENDER_TYPE_SRC,
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
&sender_data[ECM_TRACKER_SENDER_TYPE_SRC],
#endif
&sender_states[ECM_TRACKER_SENDER_TYPE_SRC]))) {
return result;
}
if ((result = ecm_state_prefix_remove(sfi))) {
return result;
}
/*
* Output dest sender
*/
if ((result = ecm_state_prefix_add(sfi, "dest"))) {
return result;
}
if ((result = ecm_tracker_tcp_sender_state_get(sfi,
ECM_TRACKER_SENDER_TYPE_DEST,
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
&sender_data[ECM_TRACKER_SENDER_TYPE_DEST],
#endif
&sender_states[ECM_TRACKER_SENDER_TYPE_DEST]))) {
return result;
}
if ((result = ecm_state_prefix_remove(sfi))) {
return result;
}
if ((result = ecm_state_prefix_remove(sfi))) {
return result;
}
return ecm_state_prefix_remove(sfi);
}
#endif
/*
* 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, "%px: magic failed", ttii);
DEBUG_TRACE("%px: init host addresses src mss: %d, dest mss: %d\n", ttii, mss_src_default, mss_dest_default);
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
spin_lock_bh(&ttii->lock);
ttii->data_limit = data_limit;
ttii->sender_data[ECM_TRACKER_SENDER_TYPE_SRC].mss = mss_src_default;
ttii->sender_data[ECM_TRACKER_SENDER_TYPE_DEST].mss = mss_dest_default;
spin_unlock_bh(&ttii->lock);
#endif
}
EXPORT_SYMBOL(ecm_tracker_tcp_init);
/*
* ecm_tracker_tcp_alloc()
*/
struct ecm_tracker_tcp_instance *ecm_tracker_tcp_alloc(void)
{
struct ecm_tracker_tcp_internal_instance *ttii;
ttii = (struct ecm_tracker_tcp_internal_instance *)kzalloc(sizeof(struct ecm_tracker_tcp_internal_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!ttii) {
DEBUG_WARN("Failed to allocate tcp tracker instance\n");
return NULL;
}
ttii->tcp_base.base.ref = ecm_tracker_tcp_ref_callback;
ttii->tcp_base.base.deref = ecm_tracker_tcp_deref_callback;
ttii->tcp_base.base.state_update = ecm_tracker_tcp_state_update_callback;
ttii->tcp_base.base.state_get = ecm_tracker_tcp_state_get_callback;
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
ttii->tcp_base.base.datagram_count_get = ecm_tracker_tcp_datagram_count_get_callback;
ttii->tcp_base.base.datagram_discard = ecm_tracker_tcp_datagram_discard_callback;
ttii->tcp_base.base.datagram_read = ecm_tracker_tcp_datagram_read_callback;
ttii->tcp_base.base.datagram_size_get = ecm_tracker_tcp_datagram_size_get_callback;
ttii->tcp_base.base.datagram_add = ecm_tracker_tcp_datagram_add_callback;
ttii->tcp_base.base.discard_all = ecm_tracker_tcp_discard_all_callback;
ttii->tcp_base.base.data_total_get = ecm_tracker_tcp_data_total_get_callback;
ttii->tcp_base.base.data_limit_get = ecm_tracker_tcp_data_limit_get_callback;
ttii->tcp_base.base.data_limit_set = ecm_tracker_tcp_data_limit_set_callback;
ttii->tcp_base.bytes_avail_get = ecm_tracker_tcp_bytes_avail_get_callback;
ttii->tcp_base.bytes_read = ecm_tracker_tcp_bytes_read_callback;
ttii->tcp_base.bytes_discard = ecm_tracker_tcp_bytes_discard_callback;
ttii->tcp_base.mss_get = ecm_tracker_tcp_mss_get_callback;
ttii->tcp_base.segment_add = ecm_tracker_tcp_segment_add_callback;
#endif
#ifdef ECM_STATE_OUTPUT_ENABLE
ttii->tcp_base.base.state_text_get = ecm_tracker_tcp_state_text_get_callback;
#endif
spin_lock_init(&ttii->lock);
ttii->refs = 1;
DEBUG_SET_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC);
spin_lock_bh(&ecm_tracker_tcp_lock);
ecm_tracker_tcp_count++;
DEBUG_ASSERT(ecm_tracker_tcp_count > 0, "%px: tcp tracker count wrap\n", ttii);
spin_unlock_bh(&ecm_tracker_tcp_lock);
DEBUG_TRACE("TCP tracker created %px\n", ttii);
return (struct ecm_tracker_tcp_instance *)ttii;
}
EXPORT_SYMBOL(ecm_tracker_tcp_alloc);
#ifdef ECM_TRACKER_DPI_SUPPORT_ENABLE
/*
* ecm_tracker_tcp_reader_fwd_read_u8()
* Read a byte, advancing the read position
*/
uint8_t ecm_tracker_tcp_reader_fwd_read_u8(struct ecm_tracker_tcp_reader_instance *tri)
{
uint8_t b;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
spin_lock_bh(&tri->ttii->lock);
/*
* Do we need to advance onto the next segment? Unlikely as most segments contain a lot more than 1 byte of data.
*/
if (unlikely(tri->segment_remain == 0)) {
if (likely(tri->segment)) {
tri->segment = tri->segment->next;
} else {
/*
* Initialise segment to allow the walking of the in_order list
*/
tri->segment = tri->data->in_order;
}
DEBUG_ASSERT(tri->segment, "%px: attempt to read past end of stream\n", tri);
tri->segment_dcb = (struct ecm_tracker_tcp_skb_cb_format *)tri->segment->cb;
DEBUG_CHECK_MAGIC(tri->segment_dcb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid magic for cb %px\n", tri, tri->segment_dcb);
tri->segment_remain = tri->segment_dcb->num_seqs;
tri->segment_offset = tri->segment_dcb->offset;
DEBUG_TRACE("%px: Advance to next segment %px, segment remain: %u, segment offset: %u\n",
tri, tri->segment, tri->segment_remain, tri->segment_offset);
DEBUG_ASSERT(tri->segment_remain, "%px: Next segment has no data\n", tri);
}
/*
* Read byte
*/
skb_copy_bits(tri->segment, tri->segment_offset, &b, 1);
tri->segment_offset++;
tri->segment_remain--;
tri->offset++;
spin_unlock_bh(&tri->ttii->lock);
spin_unlock_bh(&tri->lock);
return b;
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_fwd_read_u8);
/*
* _ecm_tracker_tcp_reader_retreat()
* Retreat reading position by number of bytes.
*
* This function CANNOT advance.
*/
static void _ecm_tracker_tcp_reader_retreat(struct ecm_tracker_tcp_reader_instance *tri, uint32_t retreatment)
{
while (retreatment) {
uint32_t seg_prior_data;
/*
* Identify how much DATA precedes our current tri->segment_offset.
* This tells us how much data we can retreat over before having to move onto the prior segment
*/
seg_prior_data = tri->segment_offset - tri->segment_dcb->offset;
DEBUG_TRACE("%px: Retreat %u, data_offs %u\n", tri, retreatment, seg_prior_data);
/*
* Is there enough prior data in the current segment to satisfy the retreat?
*/
if (retreatment <= seg_prior_data) {
tri->segment_remain += retreatment;
tri->segment_offset -= retreatment;
DEBUG_TRACE("%px: retreat completed seg_remain %u, seg offset %u\n", tri, tri->segment_remain, tri->segment_offset);
return;
}
retreatment -= seg_prior_data;
/*
* Move onto previous actual segment
*/
tri->segment = tri->segment->prev;
DEBUG_ASSERT(tri->segment, "%px: attempt to read past start of stream\n", tri);
tri->segment_dcb = (struct ecm_tracker_tcp_skb_cb_format *)tri->segment->cb;
DEBUG_CHECK_MAGIC(tri->segment_dcb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid magic for cb %px\n", tri, tri->segment_dcb);
/*
* Position at the end of the previous segment
*/
tri->segment_remain = 0;
tri->segment_offset = tri->segment_dcb->num_seqs + tri->segment_dcb->offset;
DEBUG_TRACE("%px: advanvcement now %u this seg_remain %u, this seg_offset %u\n",
tri, retreatment, tri->segment_remain, tri->segment_offset);
}
}
/*
* ecm_tracker_tcp_reader_retreat()
* Retreat reading position by number of bytes
*/
void ecm_tracker_tcp_reader_retreat(struct ecm_tracker_tcp_reader_instance *tri, uint32_t retreatment)
{
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
spin_lock_bh(&tri->ttii->lock);
DEBUG_ASSERT(tri->segment, "%px: segment pointer invalid\n", tri);
_ecm_tracker_tcp_reader_retreat(tri, retreatment);
tri->offset -= retreatment;
spin_unlock_bh(&tri->ttii->lock);
spin_unlock_bh(&tri->lock);
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_retreat);
/*
* _ecm_tracker_tcp_reader_advance()
* Advance reading position by number of bytes.
*
* This function CANNOT retreat.
*/
static void _ecm_tracker_tcp_reader_advance(struct ecm_tracker_tcp_reader_instance *tri, uint32_t advancement)
{
while (advancement) {
DEBUG_TRACE("%px: Advance %u\n", tri, advancement);
if (advancement <= tri->segment_remain) {
tri->segment_remain -= advancement;
tri->segment_offset += advancement;
DEBUG_TRACE("%px: advanvce completed seg_remain %u, seg offset %u\n", tri, tri->segment_remain, tri->segment_offset);
return;
}
advancement -= tri->segment_remain;
/*
* Move onto next actual segment
*/
if (likely(tri->segment)) {
tri->segment = tri->segment->next;
} else {
/*
* Initialise segment to allow the walking of the in_order list
*/
tri->segment = tri->data->in_order;
}
DEBUG_ASSERT(tri->segment, "%px: attempt to read past end of stream\n", tri);
tri->segment_dcb = (struct ecm_tracker_tcp_skb_cb_format *)tri->segment->cb;
DEBUG_CHECK_MAGIC(tri->segment_dcb, ECM_TRACKER_TCP_SKB_CB_MAGIC, "%px: invalid magic for cb %px\n", tri, tri->segment_dcb);
tri->segment_remain = tri->segment_dcb->num_seqs;
tri->segment_offset = tri->segment_dcb->offset;
DEBUG_TRACE("%px: advanvcement now %u this seg_remain %u, this seg_offset %u\n",
tri, advancement, tri->segment_remain, tri->segment_offset);
}
}
/*
* ecm_tracker_tcp_reader_advance()
* Advance reading position by number of bytes
*/
void ecm_tracker_tcp_reader_advance(struct ecm_tracker_tcp_reader_instance *tri, uint32_t advancement)
{
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
spin_lock_bh(&tri->ttii->lock);
_ecm_tracker_tcp_reader_advance(tri, advancement);
tri->offset += advancement;
spin_unlock_bh(&tri->ttii->lock);
spin_unlock_bh(&tri->lock);
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_advance);
/*
* ecm_tracker_tcp_reader_position_get()
* Return the current reading position
*
* NOTE: position is always referenced from the ORIGINAL 0, EVEN AFTER discarding bytes.
* For example, calling:
* _position_set(50)
* _discard_preceding()
* _position_get() WILL RETURN 50
* I.e. positions are not shifted left by a discarded operation.
* This is so that once a stream is parsed and positions known, data can be discarded without
* having to recalculate all notions of positions.
*/
uint32_t ecm_tracker_tcp_reader_position_get(struct ecm_tracker_tcp_reader_instance *tri)
{
uint32_t pos;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
pos = tri->offset;
spin_unlock_bh(&tri->lock);
return pos;
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_position_get);
/*
* ecm_tracker_tcp_reader_remain_get()
* Return the available bytes to be read from the current position
*/
uint32_t ecm_tracker_tcp_reader_remain_get(struct ecm_tracker_tcp_reader_instance *tri)
{
uint32_t remain;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
spin_lock_bh(&tri->ttii->lock);
remain = tri->data->num_seqs - (tri->offset - tri->discarded);
spin_unlock_bh(&tri->ttii->lock);
spin_unlock_bh(&tri->lock);
return remain;
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_remain_get);
/*
* ecm_tracker_tcp_reader_position_set()
* Set the reading position as an offset from the start
*/
void ecm_tracker_tcp_reader_position_set(struct ecm_tracker_tcp_reader_instance *tri, uint32_t offset)
{
int32_t delta;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
DEBUG_TRACE("%px: Position set %u\n", tri, offset);
spin_lock_bh(&tri->lock);
spin_lock_bh(&tri->ttii->lock);
/*
* Is this a retreat or an advance?
*/
delta = (int32_t)(offset - tri->offset);
DEBUG_TRACE("%px: Position set delta %d\n", tri, delta);
if (delta <= 0) {
_ecm_tracker_tcp_reader_retreat(tri, -delta);
} else {
_ecm_tracker_tcp_reader_advance(tri, delta);
}
tri->offset = offset;
spin_unlock_bh(&tri->ttii->lock);
spin_unlock_bh(&tri->lock);
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_position_set);
/*
* ecm_tracker_tcp_reader_discard_preceding()
* Discard bytes from beginning of available data before the reader position
*
* NOTE: After discarding you DO NOT have to modify your notions of positions i.e. position X is still position X to you.
* Obviously deleted positions are no longer valid but positions that have not been discarded remain valid.
*/
void ecm_tracker_tcp_reader_discard_preceding(struct ecm_tracker_tcp_reader_instance *tri)
{
uint32_t discard;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
/*
* Discard all bytes preceding our offset
*/
spin_lock_bh(&tri->lock);
discard = tri->offset - tri->discarded;
tri->discarded += discard;
/*
* If the reader position is at the end of a segment then the tri->segment is going to be ripped from
* under the reader.
*/
if (!tri->segment_remain) {
/*
* By setting the segment to null we effectively cause a re-init of the tri->segment on the next operation
*/
DEBUG_TRACE("%px: Discarding when segment_remain is 0 - causing re-init of tri->segment on next oper\n", tri);
tri->segment = NULL;
}
spin_unlock_bh(&tri->lock);
/*
* Discard the actual bytes in the tracker
*/
DEBUG_TRACE("%px: discard_preceding %u bytes\n", tri, discard);
ecm_tracker_tcp_bytes_discard(tri->ttii, tri->data, discard);
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_discard_preceding);
/*
* ecm_tracker_tcp_reader_init()
*/
void ecm_tracker_tcp_reader_init(struct ecm_tracker_tcp_reader_instance *tri, struct ecm_tracker_tcp_instance *tti, ecm_tracker_sender_type_t sender)
{
struct ecm_tracker_tcp_internal_instance *ttii = (struct ecm_tracker_tcp_internal_instance *)tti;
struct ecm_tracker_tcp_host_data *data;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
DEBUG_CHECK_MAGIC(ttii, ECM_TRACKER_TCP_INSTANCE_MAGIC, "%px: magic failed", ttii);
DEBUG_ASSERT((sender >= 0) && (sender <= 1), "%px: invalid sender %d\n", tri, sender);
/*
* Take a reference to the tcp tracker
*/
((struct ecm_tracker_instance *)tti)->ref((struct ecm_tracker_instance *)tti);
/*
* Get a pointer to the host data
*/
data = &ttii->sender_data[sender];
/*
* take a copy of the ttii and data pointers for quick reference later on during reading
*/
tri->ttii = ttii;
tri->data = data;
DEBUG_TRACE("%px: Init reader for %px (%d): data %px. \n", tri, ttii, sender, data);
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_init);
/*
* ecm_tracker_tcp_reader_ref()
*/
void ecm_tracker_tcp_reader_ref(struct ecm_tracker_tcp_reader_instance *tri)
{
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
tri->refs++;
DEBUG_ASSERT(tri->refs > 0, "%px: ref wrap", tri);
DEBUG_TRACE("%px: ref %d\n", tri, tri->refs);
spin_unlock_bh(&tri->lock);
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_ref);
/*
* ecm_tracker_tcp_reader_deref()
*/
int ecm_tracker_tcp_reader_deref(struct ecm_tracker_tcp_reader_instance *tri)
{
int refs;
DEBUG_CHECK_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC, "%px: magic failed", tri);
spin_lock_bh(&tri->lock);
tri->refs--;
refs = tri->refs;
DEBUG_ASSERT(tri->refs >= 0, "%px: ref wrap", tri);
DEBUG_TRACE("%px: deref %d\n", tri, tri->refs);
if (tri->refs > 0) {
spin_unlock_bh(&tri->lock);
return refs;
}
spin_unlock_bh(&tri->lock);
DEBUG_TRACE("%px: final\n", tri);
/*
* Release the reference to the tcp tracker
*/
((struct ecm_tracker_instance *)tri->ttii)->deref((struct ecm_tracker_instance *)tri->ttii);
spin_lock_bh(&ecm_tracker_tcp_lock);
ecm_tracker_tcp_reader_count--;
DEBUG_ASSERT(ecm_tracker_tcp_reader_count >= 0, "%px: tracker count wrap", tri);
spin_unlock_bh(&ecm_tracker_tcp_lock);
DEBUG_INFO("%px: TCP Reader final\n", tri);
DEBUG_CLEAR_MAGIC(tri);
kfree(tri);
return 0;
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_deref);
/*
* ecm_tracker_tcp_reader_alloc()
*/
struct ecm_tracker_tcp_reader_instance *ecm_tracker_tcp_reader_alloc(void)
{
struct ecm_tracker_tcp_reader_instance *tri;
tri = (struct ecm_tracker_tcp_reader_instance *)kzalloc(sizeof(struct ecm_tracker_tcp_reader_instance), GFP_ATOMIC | __GFP_NOWARN);
if (!tri) {
DEBUG_WARN("Failed to allocate tcp reader instance\n");
return NULL;
}
spin_lock_init(&tri->lock);
tri->refs = 1;
DEBUG_SET_MAGIC(tri, ECM_TRACKER_TCP_READER_INSTANCE_MAGIC);
spin_lock_bh(&ecm_tracker_tcp_lock);
ecm_tracker_tcp_reader_count++;
DEBUG_ASSERT(ecm_tracker_tcp_reader_count > 0, "%px: tcp tracker reader count wrap\n", tri);
spin_unlock_bh(&ecm_tracker_tcp_lock);
DEBUG_TRACE("TCP reader created %px\n", tri);
return tri;
}
EXPORT_SYMBOL(ecm_tracker_tcp_reader_alloc);
#endif