来源:笔不敌剑!
在所有高端型号,大多数中端型号以及部分低端型号的交换机/路由器上,都可以配置一个或者多个镜像端口,它是流量分析的利器。然而,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 <marywangran@126.com>"); 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 marknn"); } 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禁用掉。除了本机做的工作之外,还要在接收镜像数据的机器的接收接口上开启混杂模式。