在所有高端型號,大多數中端型號以及部分低端型號的交換機/路由器上,都可以配置一個或者多個鏡像端口,它是流量分析的利器。然而,Linux上沒有現成的技術可以實現鏡像端口,當然,我指的不是Linux 3.x(x是幾,忘了)以上的內核,這些內核已經支持了鏡像,但不夠好。起碼2.6.35的內核是不能支持的,那麼Linux實現的軟交換機屬於哪個檔次呢?關鍵是,很多高端的網絡產品也是基於Linux實現的,沒有鏡像口怎麼能行,即使在不使用Linux bridge的情況下,也希望能有一個技術實現鏡像端口。
1.確定你的鏡像端口,比如eth5;
xt_TEE的實現
在xtables-addons中,已經有了一個xt_TEE的實現,在其manual中,有一個一目了然的配置:
-t mangle -A PREROUTING -i eth0 -j TEE --gateway 2001:db8::1
-j TEE --dev ethX,ethY,ethZ
前傳
起初,寫這個模塊的目標並不是為了做鏡像端口,而是為了將一個數據包復制兩份,僅此而已,其實本意就是一個Netfilter實現的抓包模塊,和使用pcap抓包相比,它的優勢在於可以去除很多不相關數據包的干擾,它只能抓取確實是發往本機的數據包,雖然這也許違背的抓包的原本的意義,但是那只是一個詞匯而已!我以及很多人大多數情況下抓包並不是為了嗅探別人的數據,而是為了解決和自己相關的問題,這就需要過濾掉那些不小心到來的由於交換機MAC映射到期導致的發往所有端口的數據,而這需要寫一大堆tcpdump規則。
不能做到包嗅探!
實現
本實現由4部分,其中包含一個內核模塊文件,一個用戶態的iptables庫文件,一個結構體定義頭文件,一套Makefile。代碼完全按照xtables-addons的規范制作。
結構體定義頭文件:xt_CLONE.h
#ifndef _LINUX_NETFILTER_XT_CLONEMARK_H #define _LINUX_NETFILTER_XT_CLONEMARK_H 1 struct xt_clonemark_tginfo { __u32 mark; }; #endif /* _LINUX_NETFILTER_XT_CLONEMARK_H */
內核模塊:xt_CLONE.c
/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License; either * version 2 of the License, or any later version, as published by the * Free Software Foundation. */ #include <linux/module.h> #include <linux/skbuff.h> #include <linux/netfilter/x_tables.h> #include <net/ip6_route.h> #include "xt_CLONE.h" #include <net/ip.h> #include "compat_xtables.h" struct sk_buff_head clq; static struct tasklet_struct clone_xmit_tasklet; static void clone_xmit_work(unsigned long data) { struct sk_buff_head *pclq = (struct sk_buff_head *)data; struct net_device *old_dev = NULL; struct net_device *new_dev = NULL; do { struct sk_buff * skb = skb_dequeue_tail(pclq); old_dev = skb_dst(skb)->dev; if (ip_route_me_harder(&skb, RTN_UNSPEC)) { kfree_skb(skb); } new_dev = skb_dst(skb)->dev; if (old_dev != new_dev) { ip_local_out(skb); } else { kfree_skb(skb); } } while (!skb_queue_empty(pclq)); } static unsigned int clone_tg6(struct sk_buff **poldskb, const struct xt_action_param *par) { // TODO return XT_CONTINUE;; } static unsigned int clone_tg4(struct sk_buff **poldskb, const struct xt_action_param *par) { const struct xt_clonemark_tginfo *markinfo = par->targinfo; struct sk_buff *newskb; __u32 mark; __u32 qlen; qlen = skb_queue_len (&clq); // 控制總量! if (qlen > 1000/*sysctl參數控制*/) { return XT_CONTINUE; } mark = markinfo->mark; newskb = pskb_copy(*poldskb, GFP_ATOMIC); if (newskb == NULL) return XT_CONTINUE; // 在FORWARD鏈上做的目的是可以放心reroute,關鍵在re前綴 // skb_dst_drop(newskb); // 丟棄連接跟蹤,但是要為之初始化一個notrack的偽連接跟蹤 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) #include <net/netfilter/nf_conntrack.h> nf_conntrack_put(newskb->nfct); newskb->nfct = &nf_conntrack_untracked.ct_general; newskb->nfctinfo = IP_CT_NEW; nf_conntrack_get(newskb->nfct); #endif newskb->mark = mark; skb_queue_head(&clq, newskb); tasklet_schedule(&clone_xmit_tasklet); return XT_CONTINUE; } static struct xt_target clone_tg_reg[] __read_mostly = { { .name = "CLONE", .revision = 0, .family = NFPROTO_IPV6, .table = "filter", .target = clone_tg6, .targetsize = sizeof(struct xt_clonemark_tginfo), .me = THIS_MODULE, }, { .name = "CLONE", .revision = 0, .family = NFPROTO_IPV4, .table = "filter", .target = clone_tg4, .targetsize = sizeof(struct xt_clonemark_tginfo), .me = THIS_MODULE, }, }; static int __init clone_tg_init(void) { skb_queue_head_init(&clq); tasklet_init(&clone_xmit_tasklet, clone_xmit_work, (unsigned long)&clq); return xt_register_targets(clone_tg_reg, ARRAY_SIZE(clone_tg_reg)); } static void __exit clone_tg_exit(void) { tasklet_kill(&clone_xmit_tasklet); return xt_unregister_targets(clone_tg_reg, ARRAY_SIZE(clone_tg_reg)); } module_init(clone_tg_init); module_exit(clone_tg_exit); MODULE_AUTHOR("Wangran <[email protected]>"); MODULE_DESCRIPTION("Xtables: CLONE packet target"); MODULE_LICENSE("GPL"); MODULE_ALIAS("ip6t_CLONE"); MODULE_ALIAS("ipt_CLONE");
iptables模塊:libxt_CLONE.c
/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License; either * version 2 of the License, or any later version, as published by the * Free Software Foundation. */ #include <stdio.h> #include <getopt.h> #include <xtables.h> #include "xt_CLONE.h" #include "compat_user.h" enum { FL_MARK_USED = 1 << 0, }; static const struct option clonemark_tg_opts[] = { {.name = "mark", .has_arg = true, .val = '1'}, {NULL}, }; static void clonemark_tg_init(struct xt_entry_target *t) { struct xt_clonemark_tginfo *info = (void *)t->data; info->mark = ~0U; } static void clone_tg_help(void) { printf("CLONE --mark mark\n\n"); } static int clone_tg_parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_target **target) { struct xt_clonemark_tginfo *info = (void *)(*target)->data; unsigned int n; switch (c) { case '1': xtables_param_act(XTF_ONLY_ONCE, "CLONE", "--mark", *flags & FL_MARK_USED); xtables_param_act(XTF_NO_INVERT, "CLONE", "--mark", invert); if (!xtables_strtoui(optarg, NULL, &n, 0, ~0U)) xtables_param_act(XTF_BAD_VALUE, "CLONE", "--mark", optarg); info->mark = n; *flags |= FL_MARK_USED; return true; } return false; } static void clone_tg_check(unsigned int flags) { //TODO } static void clonemark_tg_save(const void *entry, const struct xt_entry_target *target) { const struct xt_clonemark_tginfo *info = (const void *)target->data; printf(" --mark 0x%x ", (__u32)info->mark); } static struct xtables_target clone_tg_reg = { .version = XTABLES_VERSION, .name = "CLONE", .family = NFPROTO_UNSPEC, .size = XT_ALIGN(sizeof(struct xt_clonemark_tginfo)), .userspacesize = XT_ALIGN(sizeof(struct xt_clonemark_tginfo)), .init = clonemark_tg_init, .save = clonemark_tg_save, .help = clone_tg_help, .parse = clone_tg_parse, .final_check = clone_tg_check, .extra_opts = clonemark_tg_opts, }; static __attribute__((constructor)) void clone_tg_ldr(void) { xtables_register_target(&clone_tg_reg); }
編譯:
obj-${build_CLONE} += xt_CLONE.o
obj-${build_CLONE} += libxt_CLONE.so
build_CLONE=m
make && make install即可,
說明 為何要在filter表做呢?因為filter表都在路由之後執行,這是為了調用reroute接口函數ip_route_me_harder的方便,該函數導出為一個內核接口,可以直接調用。在這麼做之前,我嘗試過直接調用ip_queue_xmit函數,然而發現只有在本機出發的包才會經過該路徑,因此需要為skb綁定一個socket才可以,而這無疑是工作量加大了;後來,我想到了直接調用ip_rcv_finish函數,可以該函數並未導出,需要在加載模塊前先去procfs裡面查一下該函數的地址,然後傳入模塊,這種做法並不標准;再往後,自然而然就是調用ip_route_me_harder接口函數了,然而該函數需要skb已經有了一個dst_entry(這很正常,reroute中的re前綴表明skb已經被路由過一次了),因此必然要在路由之後調用,那麼顯然處理位置就落到了Netfilter的HOOK點和路由構成的馬鞍面的中間位置了,只能在filter表來做,重新路由之後,直接調用ip_local_out從第三層發出即可。
協議棧本身來完成,即調用協議棧的函數,因為協議棧本身就是干這個的,決不要在自己的代碼中實現,如果你覺得自己可以實現一個更妙的,那就直接改掉協議棧。
局限
該實現還是有一定局限的,畢竟該實現的做法太高層,它會改變數據包的MAC頭,但是這對於針對應用層內容的深度解析,無所謂了。另外需要注意的是,需要在本機做三件工作,第一就是設置CLONE規則及確定mark,第二是根據mark設置策略路由,第三就是將策略路由指向的出口設備的arp禁用掉。除了本機做的工作之外,還要在接收鏡像數據的機器的接收接口上開啟混雜模式。