/*
* (C) 2008 Zeng Zhaorong
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/*
* Derived from linux netfilter conntrack codes
*/
#ifndef LINUX_VERSION_CODE
#include <linux/version.h>
#endif
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
#define nf_proto_csum_replace4 inet_proto_csum_replace4
#define NF_IP_PRE_ROUTING NF_INET_PRE_ROUTING
#define NF_IP_POST_ROUTING NF_INET_POST_ROUTING
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
#define skb_dst(skb) (skb->dst)
#endif
#include "daemon_kernel.h"
#define TCPTRACK_VERSION "0.0.1"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wheelz");
MODULE_DESCRIPTION("Drcom-Kernel " TCPTRACK_VERSION);
enum tcp_state {
TCP_STATE_NONE = 0,
TCP_STATE_SYN_SENT,
TCP_STATE_SYN_RECV,
TCP_STATE_ESTABLISHED,
TCP_STATE_FIN_WAIT,
TCP_STATE_TIME_WAIT,
TCP_STATE_CLOSE,
TCP_STATE_CLOSE_WAIT,
TCP_STATE_LAST_ACK,
TCP_STATE_LISTEN,
TCP_STATE_MAX
};
#define SECS *HZ
#define MINS * 60 SECS
#define HOURS * 60 MINS
#define DAYS * 24 HOURS
static unsigned long tcp_timeouts[]
= { 30 MINS, /* TCP_STATE_NONE, */
2 MINS, /* TCP_STATE_SYN_SENT, */
60 SECS, /* TCP_STATE_SYN_RECV, */
5 DAYS, /* TCP_STATE_ESTABLISHED, */
2 MINS, /* TCP_STATE_FIN_WAIT, */
2 MINS, /* TCP_STATE_TIME_WAIT, */
10 SECS, /* TCP_STATE_CLOSE, */
60 SECS, /* TCP_STATE_CLOSE_WAIT, */
30 SECS, /* TCP_STATE_LAST_ACK, */
2 MINS, /* TCP_STATE_LISTEN, */
};
#define sNO TCP_STATE_NONE
#define sES TCP_STATE_ESTABLISHED
#define sSS TCP_STATE_SYN_SENT
#define sSR TCP_STATE_SYN_RECV
#define sFW TCP_STATE_FIN_WAIT
#define sTW TCP_STATE_TIME_WAIT
#define sCL TCP_STATE_CLOSE
#define sCW TCP_STATE_CLOSE_WAIT
#define sLA TCP_STATE_LAST_ACK
#define sLI TCP_STATE_LISTEN
#define sIV TCP_STATE_MAX
static enum tcp_state tcp_states[2][5][TCP_STATE_MAX] = {
{
/* ORIGINAL */
/* sNO, sSS, sSR, sES, sFW, sTW, sCL, sCW, sLA, sLI */
/*syn*/ {sSS, sSS, sSR, sES, sSS, sSS, sSS, sSS, sSS, sLI },
/*fin*/ {sTW, sSS, sTW, sFW, sFW, sTW, sCL, sTW, sLA, sLI },
/*ack*/ {sES, sSS, sES, sES, sFW, sTW, sCL, sCW, sLA, sES },
/*rst*/ {sCL, sSS, sCL, sCL, sCL, sTW, sCL, sCL, sCL, sCL },
/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV }
},
{
/* REPLY */
/* sNO, sSS, sSR, sES, sFW, sTW, sCL, sCW, sLA, sLI */
/*syn*/ {sSR, sSR, sSR, sES, sSR, sSR, sSR, sSR, sSR, sSR },
/*fin*/ {sCL, sSS, sTW, sCW, sTW, sTW, sCL, sCW, sLA, sLI },
/*ack*/ {sCL, sSS, sSR, sES, sFW, sTW, sCL, sCW, sCL, sLI },
/*rst*/ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sLA, sLI },
/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV }
}
};
struct tcp_tuple
{
__be32 src_ip;
__be32 dst_ip;
__be16 src_port;
__be16 dst_port;
u_int8_t dir;
};
struct tcp_tuplehash
{
struct list_head list;
struct tcp_tuple tuple;
};
struct tcp_seq
{
u_int32_t syn_seq;
u_int32_t correction_pos;
int16_t offset_before, offset_after;
};
struct tcp_conn
{
struct tcp_tuplehash tuplehash[2];
atomic_t ref;
struct timer_list timeout;
u_int8_t flags;
enum tcp_state state;
struct tcp_seq seq[2];
};
#define CONN_F_NEW 0x01
#define CONN_F_AUTHSENT 0x02
#define TODO_NONE 0x00
#define TODO_ADJUST_SEQ 0x01
#define TODO_SEND_ACK 0x02
#define TODO_SEND_AUTH 0x04
#define CONN_DIR_ORIG 0
#define CONN_DIR_REPLY 1
#define TCP_CONN_HASH_SIZE 32
static pid_t conn_pid = 0;
static int conn_autologout = 0;
static struct timer_list conn_keepalive_timer;
static unsigned char conn_auth_data[CONN_AUTH_DATA_LEN];
static struct list_head tcp_conn_hash[TCP_CONN_HASH_SIZE];
static atomic_t tcp_conn_count = ATOMIC_INIT(0);
static struct net_device *track_dev = NULL;
static struct e_address *conn_e_addr = NULL;
static int conn_e_count = 0;
static int track_mode = CONN_MODE_NONE;
static DEFINE_RWLOCK(mode_lock);
static DEFINE_RWLOCK(hash_lock);
static DEFINE_RWLOCK(state_lock);
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
#if 0
static const char *tcp_state_names[] = {
"NONE",
"SYN_SENT",
"SYN_RECV",
"ESTABLISHED",
"FIN_WAIT",
"TIME_WAIT",
"CLOSE",
"CLOSE_WAIT",
"LAST_ACK",
"LISTEN"
};
#endif
static inline int tuple_equal(struct tcp_tuple *t1, struct tcp_tuple *t2)
{
return (t1->src_ip == t2->src_ip && t1->dst_ip == t2->dst_ip
&& t1->src_port == t2->src_port && t1->dst_port == t2->dst_port);
}
static inline u_int32_t hash_conn(const struct tcp_tuple *tuple)
{
return (ntohl(tuple->src_ip + tuple->dst_ip + tuple->src_port + tuple->dst_port)
+ ntohs(tuple->src_port)) % TCP_CONN_HASH_SIZE;
}
static inline struct tcp_conn *tuplehash_to_conn(const struct tcp_tuplehash *hash)
{
return container_of(hash, struct tcp_conn, tuplehash[hash->tuple.dir]);
}
static inline void conn_get(struct tcp_conn *conn)
{
if (conn)
atomic_inc(&conn->ref);
}
static inline void conn_put(struct tcp_conn *conn)
{
if (conn && atomic_dec_and_test(&conn->ref)) {
kfree(conn);
atomic_dec(&tcp_conn_count);
}
}
/* under state_lock */
static void __conn_refresh_timer(struct tcp_conn *conn, unsigned long timeout)
{
unsigned long newtime;
if (conn->flags & CONN_F_NEW) {
conn->flags &= ~CONN_F_NEW;
conn->timeout.expires = jiffies + timeout;
add_timer(&conn->timeout);
} else {
newtime = jiffies + timeout;
if (newtime - conn->timeout.expires >= HZ && del_timer(&conn->timeout)) {
conn->timeout.expires = newtime;
add_timer(&conn->timeout);
}
}
}
static void death_by_timeout(unsigned long ul_conn)
{
struct tcp_conn *conn = (struct tcp_conn*)ul_conn;
write_lock_bh(&hash_lock);
list_del(&conn->tuplehash[CONN_DIR_ORIG].list);
list_del(&conn->tuplehash[CONN_DIR_REPLY].list);
write_unlock_bh(&hash_lock);
conn_put(conn);
}
static void conn_tuple_init(struct tcp_conn *conn, struct tcp_tuple *tuple)
{
struct tcp_tuple *t;
t = &conn->tuplehash[CONN_DIR_ORIG].tuple;
t->src_ip = tuple->src_ip;
t->dst_ip = tuple->dst_ip;
t->src_port = tuple->src_port;
t->dst_port = tuple->dst_port;
t->dir = CONN_DIR_ORIG;
t = &conn->tuplehash[CONN_DIR_REPLY].tuple;
t->src_ip = tuple->dst_ip;
t->dst_ip = tuple->src_ip;
t->src_port = tuple->dst_port;
t->dst_port = tuple->src_port;
t->dir = CONN_DIR_REPLY;
}
static inline struct tcp_conn *get_new_conn(struct tcp_tuple *tuple)
{
struct tcp_conn *conn;
conn = kmalloc(sizeof(struct tcp_conn), GFP_ATOMIC);
if (conn == NULL)
return NULL;
memset(conn, 0, sizeof(struct tcp_conn));
conn->flags = CONN_F_NEW;
conn_tuple_init(conn, tuple);
setup_timer(&conn->timeout, death_by_timeout, (unsigned long)conn);
return conn;
}
static int is_syn_pkt(struct sk_buff *skb)
{
unsigned int nhoff = skb_network_offset(skb);
struct iphdr *iph, _iph;
struct tcphdr *tcph, _tcph;
iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
if (iph == NULL)
return 0;
tcph = skb_header_pointer(skb, nhoff + (iph->ihl << 2), sizeof(_tcph), &_tcph);
if (tcph == NULL)
return 0;
return (tcph->syn && !tcph->ack);
}
static int tcp_get_tuple(struct sk_buff *skb, struct tcp_tuple *tuple)
{
struct iphdr _iph, *iph;
struct tcphdr _hdr, *hp;
unsigned int nhoff = skb_network_offset(skb);
unsigned int thoff;
memset(tuple, 0, sizeof(struct tcp_tuple));
iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
if (iph == NULL)
return 0;
tuple->src_ip = iph->saddr;
tuple->dst_ip = iph->daddr;
thoff = nhoff + (iph->ihl << 2);
hp = skb_header_pointer(skb, thoff, 8, &_hdr);
if (hp == NULL)
return 0;
tuple->src_port =