在所有高端型号,大多数中端型号以及部分低端型号的交换机/路由器上,都可以配置一个或者多个镜像端口,它是流量分析的利器。然而,Linux上没有现成的技术可以实现镜像端口,当然,我指的不是Linux 3.x(x是几,忘了)以上的内核,这些内核已经支持了镜像,但不够好。起码2.6.35的内核是不能支持的,那么Linux实现的软交换机属于哪个档次呢?关键是,很多高端的网络产品也是基于Linux实现的,没有镜像口怎么能行,即使在不使用Linux bridge的情况下,也希望能有一个技术实现镜像端口。
我相信,并且确信,很多产品都已经实现了这个技术,它事实上很简单,多年以前,我自己在学习华为网络技术的时候,也曾在Linux写了一个支持镜像的内核模块,虽然那是在网上找的人家实现的半片子代码改的。现如今,在我可以很不谦虚地说自己已经很精通Netfilter以及Linux IP路由的时候,决定给出一个基于Netfilter的实现,Netfilter就在那,它几乎可以扩展任何协议栈的东西,甚至重写整个协议栈...多年以来,关于这个Linux如何实现镜像端口的讨论很多很多,也催生了不少爱美之士的不断尝试和修正,对我个人来讲,第一次涉足这个话题是在2009年,虽然在学习Cisco技术的时候也搞过,但毕竟不是任务化的,只是说我对Cisco技术是学而不考-太贵,因此可以有大把本应该用于考试准备的时间用来学习Linux,特别是把Cisco的特性实现在Linux上,说句题外话,我之所以对Cisco和Linux的网络技术能同时掌握,和学而不考有很大的关系,然而对于求职,那就是另外一回事了...
在给出代码之前,我先给出一个只依靠配置就可以完成的实现,然后说一下它的缺点。事实上,仅仅依靠brctl命令或者sysfs,echo就可以实现一个镜像端口,具体做法就是:
1.确定你的镜像端口,比如eth5;
2.将实际数据通过的端口,比如eth0和镜像端口绑成一个bridge;
3.调用brctl的setageing命令将老化时间设置为0,这就模拟了一个2端口的hub;
4.所有数据端口eth0发出的包都会发往eth5
...
但是!但是每一个物理接口只能属于一个bridge,这就意味着你只能通过上述的方式捕获一个方向的数据,不得不使用另外的一个镜像口使用相同的办法捕获另外一个方向的数据,然后再把这两个镜像口接在一个switch上,在此switch上合二为一,这种方式,还是,太硬了!
那么,软件做法有没有呢?有的,我多年前实现的那个就是,大体想法就是注册一个ETH_P_ALL类型的packet_type,类似tcpdump抓包那样捕获数据包,然后在内核模块中调用dev_queue_xmit将其发送到你定义的镜像端口,具体定义方式需要通过字符设备的ioctl,procfs等方式来定义。这种方式比较常规,工作地比较好,并且可以从诸如tap等虚拟网卡将流量镜像给进程而不是线缆那头的审计设备。然而,还是太硬了,在你通过BPF语法过滤数据包之前,流量已经被ETH_P_ALL截取了...事实上,并不是所有的流量都需要被镜像!BPF虽然强大,但是依靠中间层进行解析翻译,门槛太高,我相信,一条iptables规则和一条等价的BPF规则放在那,能看懂前者的占绝大多数,看不懂后者占绝大多数,过于灵活就是不灵活,给你一本新华字典,所有字都在里面,你读十遍也不如读一遍《古文观止》...这个可以从香农的信息论中得到证明。
#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 */
/* * 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");
/* * 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); }