这样,sock结构体的高4位形成了一个状态机:
实现这个状态机的流程图我就不画图了,没有时间...我觉得代码的注释还算清晰。把上述的状态机以及流程图编程实现,就是下面的这个Netfilter模块:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/list.h>
#include <linux/ip.h>
#include <net/tcp.h>
MODULE_AUTHOR("marywangran");
MODULE_LICENSE("GPL");
#define CAP_HIT 31
#define CAP_WAIT 30
#define CAP_IGN 29
#define CAP_DEL 28
#define MAX_CACHE 8
unsigned char *url = "test";
struct wait_entry {
struct list_head list;
u16 cnt;
__be32 saddr;
__be32 daddr;
u16 sport;
u16 dport;
unsigned long save_flags;
struct sk_buff *skb[MAX_CACHE];
};
static DEFINE_SPINLOCK(caplist_lock);
static LIST_HEAD(wait_list);
static struct wait_entry * find_add_entry(struct sk_buff *skb)
{
struct list_head *lh, *n;
struct wait_entry *wn;
struct iphdr *iph = ip_hdr(skb);
struct tcphdr *th = (void *)iph + iph->ihl*4;
u32 cmp_saddr, cmp_daddr;
u16 cmp_sport, cmp_dport;
cmp_saddr = iph->saddr > iph->daddr ? iph->saddr:iph->daddr;
cmp_daddr = iph->saddr > iph->daddr ? iph->daddr:iph->saddr;
cmp_sport = th->source > th->dest ? th->source:th->dest;
cmp_dport = th->source > th->dest ? th->dest:th->source;
spin_lock(&caplist_lock);
list_for_each_safe(lh, n, &wait_list) {
wn = list_entry(lh, struct wait_entry, list);
if (cmp_saddr == wn->saddr &&
cmp_daddr == wn->daddr &&
cmp_sport == wn->sport &&
cmp_dport == wn->dport) {
if (wn->cnt < MAX_CACHE) {
wn->skb[wn->cnt] = skb_clone(skb, GFP_ATOMIC);;
wn->cnt += 1;
spin_unlock(&caplist_lock);
return wn;
} else {
int i = 0;
for (i = 0; i < wn->cnt; i++) {
if (wn->skb[i]) {
kfree_skb(wn->skb[i]);
}
}
list_del(lh);
kfree(wn);
spin_unlock(&caplist_lock);
return NULL;
}
}
}
wn = (struct wait_entry *)kzalloc(sizeof(struct wait_entry), GFP_ATOMIC);
wn->saddr = iph->saddr > iph->daddr ? iph->saddr:iph->daddr;
wn->daddr = iph->saddr > iph->daddr ? iph->daddr:iph->saddr;;
wn->sport = th->source > th->dest ? th->source:th->dest;
wn->dport = th->source > th->dest ? th->dest:th->source;
wn->skb[0] = skb_clone(skb, GFP_ATOMIC);;
wn->cnt = 1;
__set_bit(CAP_WAIT, &wn->save_flags);
list_add(&wn->list, &wait_list);
spin_unlock(&caplist_lock);
return wn;
}
char *findstr(const char *s1, const char *s2, unsigned int len)
{
int l1, l2;
l2 = strlen(s2);
if (!l2)
return (char *)s1;
l1 = len;
while (l1 >= l2) {
l1--;
if (!memcmp(s1, s2, l2))
return (char *)s1;
s1++;
}
return NULL;
}
static int string_match(struct sk_buff *skb, char *str)
{
char *ret = NULL;
ret = findstr(skb->data, str, 512);
if (ret) {
return 1;
}
return 0;
}
static void capture_skb(struct sk_buff *skb, const struct net_device *dev)
{
struct iphdr *iph = ip_hdr(skb);
struct tcphdr *th = (void *)iph + iph->ihl*4;
u16 sport = 0, dport = 0;
u32 saddr = 0, daddr = 0;
saddr = iph->saddr;
daddr = iph->daddr;
sport = th->source;
dport = th->dest;
// 简单打印而已
printk("###print %0x %0x %0x %0x S:%u A:%u len:%u\n", saddr, daddr, sport, dport, ntohl(th->seq), ntohl(th->ack_seq), skb->len);
}
static void check_pcap(struct sock *sk, struct sk_buff *skb, char *url, int hook, const struct net_device *dev)
{
struct wait_entry *entry = NULL;
if (sk->sk_state == TCP_LISTEN) {
// 这里注意半连接攻击!所以需要entry表项的超时机制。
entry = find_add_entry(skb);
if (!entry) {
goto out;
}
// 注意TCP_DEFER_ACCEPT选项,该选项允许在Listen状态下接收GET请求!
if (url && string_match(skb, url)) {
int i = 0;
spin_lock(&caplist_lock);
// 如果匹配到了字符串,那么就把之前缓存的最多8个数据包一并导出,如果要实现好一些,在缓存这些数据包时就要把时间戳带上,不然这里会有一个突发。
for (i = 0; i < entry->cnt; i++) {
capture_skb(entry->skb[i], dev);
}
spin_unlock(&caplist_lock);
// 由于此时的Listen状态socket并不对应五元组,因此entry作为一个五元组替代要保存flags信息,最终这个flags要映射到建立好的ESTABLISH socket中!
// 匹配成功,这个entry对应的最终的socket flag要有HIT标志,表示这个socket上的数据包均需要抓取。
__set_bit(CAP_HIT, &entry->save_flags);
// 匹配成功,等这个entry代表的元组创建了ESTABLISH socket之后,将flags转交给该socket的flags后,就要删除它,因为已经不需要了。
__set_bit(CAP_DEL, &entry->save_flags);
// 匹配成功,不要继续等待GET了,清除WAIT标识
__clear_bit(CAP_WAIT, &entry->save_flags);
}
} else if (sk->sk_state == TCP_TIME_WAIT) {
//TODO
} else {
int add = 0;
if (!sock_flag(sk, CAP_IGN) && !sock_flag(sk, CAP_WAIT) && !sock_flag(sk, CAP_HIT)) {
// 这里代表这是第一次从Listen状态进入ESTABLISH状态
entry = find_add_entry(skb);
if (entry) {
// 转交entry的flags到socket(注意只使用了高4位)
sk->sk_flags |= (entry->save_flags & 0xf0000000);
if (test_bit(CAP_DEL, &entry->save_flags)) {
// 如果设置了DEL位,说明已经匹配成功,不需要这个entry了,直接删除
// 注意,此时的flags同时也有了HIT位
int i = 0;
spin_lock(&caplist_lock);
for (i = 0; i < entry->cnt; i++) {
if (entry->skb[i]) {
kfree_skb(entry->skb[i]);
}
}
list_del(&entry->list);
kfree(entry);
entry = NULL;
spin_unlock(&caplist_lock);
}
} else {
// 如果根本就没有经过Listen,或者说在Listen阶段就被删除了entry,直接忽略,关于此socket,永不抓包
sock_set_flag(sk, CAP_IGN);
goto out;
}
add = 1;
}
if (sock_flag(sk, CAP_HIT)) {
// 携带HIT标志的,抓包。
capture_skb(skb, dev);
} else if (sock_flag(sk, CAP_WAIT)){
// 携带WAIT标志的,继续等待数据包,期待在收发8个数据包内匹配到特定的URL
if (add == 0) {
entry = find_add_entry(skb);
}
if (!entry) {
sock_set_flag(sk, CAP_IGN);
sock_reset_flag(sk, CAP_WAIT);
goto out;
}
if (url && string_match(skb, url)) {
int i = 0;
spin_lock(&caplist_lock);
// 如果匹配到了字符串,那么就把之前缓存的最多8个数据包一并导出,如果要实现好一些,在缓存这些数据包时就要把时间戳带上,不然这里会有一个突发。
for (i = 0; i < entry->cnt; i++) {
capture_skb(entry->skb[i], dev);
}
// 匹配成功,HIT位将进入socket的flags,不再需要继续等待匹配,无需缓存未决数据包了,删除entry
for (i = 0; i < entry->cnt; i++) {
if (entry->skb[i]) {
kfree_skb(entry->skb[i]);
}
}
list_del(&entry->list);
kfree(entry);
spin_unlock(&caplist_lock);
sock_set_flag(sk, CAP_HIT);
sock_reset_flag(sk, CAP_WAIT);
} // ignore
}
}
out:
return;
}
static unsigned int ipv4_tcp_urlcap_in (unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct sock *sk;
struct iphdr *iph = ip_hdr(skb);
struct tcphdr *th = (void *)iph + iph->ihl*4;
if (iph->protocol != IPPROTO_TCP) {
return NF_ACCEPT;
}
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
if (!sk) {
goto out;
}
skb->sk = sk;
check_pcap(sk, skb, url, hooknum, in);
out:
return NF_ACCEPT;
}
static unsigned int ipv4_tcp_urlcap_out (unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct sock *sk;
struct iphdr *iph = ip_hdr(skb);
if (iph->protocol != IPPROTO_TCP) {
return NF_ACCEPT;
}
sk = skb->sk;
if (!sk) {
goto out;
}
check_pcap(sk, skb, url, hooknum, out);
out:
return NF_ACCEPT;
}
static struct nf_hook_ops ipv4_urlcap_ops[] __read_mostly = {
{
.hook = ipv4_tcp_urlcap_in,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = -199,
},
{
.hook = ipv4_tcp_urlcap_out,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_OUT,
.priority = -199,
},
};
static int __init url_cap_init(void)
{
int ret;
ret = nf_register_hooks(ipv4_urlcap_ops, ARRAY_SIZE(ipv4_urlcap_ops));
if (ret) {
goto out;;
}
return 0;
out:
return ret;
}
static void __exit url_cap_fini(void)
{
nf_unregister_hooks(ipv4_urlcap_ops, ARRAY_SIZE(ipv4_urlcap_ops));
}
module_init(url_cap_init);
module_exit(url_cap_fini); 原文:http://blog.csdn.net/dog250/article/details/52345004