`
hulianwang2014
  • 浏览: 690076 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

Linux如何实现镜像端口

 
阅读更多
在所有高端型号,大多数中端型号以及部分低端型号的交换机/路由器上,都可以配置一个或者多个镜像端口,它是流量分析的利器。然而,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规则放在那,能看懂前者的占绝大多数,看不懂后者占绝大多数,过于灵活就是不灵活,给你一本新华字典,所有字都在里面,你读十遍也不如读一遍《古文观止》...这个可以从香农的信息论中得到证明。

xt_TEE的实现

在xtables-addons中,已经有了一个xt_TEE的实现,在其manual中,有一个一目了然的配置:
-t mangle -A PREROUTING -i eth0 -j TEE --gateway 2001:db8::1
即将数据包克隆一份,然后发往一个IP地址,该IP地址可以配置。我为何觉得它不好呢?第一,我认为依基于IP而不是基于端口来镜像数据包可能需要额外太多的配置,比如你事先要有一个接收端的明确IP地址;第二我觉得它的实现不是很好,它的实现阻碍了原始数据包的快速通过,而我比较倾向于用“下半部”的思想解决克隆包的发送问题,即先将其排入一个队列,然后让系统调度其发送,而不是强制在代码中调用发送代码。除了这两点,TEE的实现真的不错。看了TEE的实现之后,我在想,为何:
-j TEE --dev ethX,ethY,ethZ
这种设置就不行呢?当然,肯定不行,因为TEE target没有--dev参数,可是为何没有人实现呢?...难道仅仅是内核缺少由dev自动封装以太头的接口?也许是吧,毕竟,所有的dev_queue_xmit调用都是从路由层一路下来的...
现在该给出我最新的实现了。这个实现很简单,和TEE一样,写了一个新的iptables target,即CLONE。克隆一个数据包并且打上标签,然后如何处理该数据包呢?很显然是根据标签来查找策略路由表了,你可以在策略路由表中将所有克隆的数据包发到任何一个网卡中,这不就是镜像口的含义么?
要说明的是,虽然你可以通过reroute的方式将带有标签的克隆数据包发往一个网卡,但是由于网卡在发包前需要对目标或者对下一跳进行ARP,那么可能导致由于ARP没有回应而发包失败,幸运的是,ifconfig命令可以禁用网卡的ARP,这不正是为镜像端口准备的么??

前传

起初,写这个模块的目标并不是为了做镜像端口,而是为了将一个数据包复制两份,仅此而已,其实本意就是一个Netfilter实现的抓包模块,和使用pcap抓包相比,它的优势在于可以去除很多不相关数据包的干扰,它只能抓取确实是发往本机的数据包,虽然这也许违背的抓包的原本的意义,但是那只是一个词汇而已!我以及很多人大多数情况下抓包并不是为了嗅探别人的数据,而是为了解决和自己相关的问题,这就需要过滤掉那些不小心到来的由于交换机MAC映射到期导致的发往所有端口的数据,而这需要写一大堆tcpdump规则。
使用Netfilter配合iptables来做这件事,优势在于不需要把全部的规则写在一条命令里面,你完全可以在PREROUTING的mangle表用mark过滤掉那些你不感兴趣的包,然后在FORWARD的filter上对感兴趣的包实施包克隆,然后将克隆到的数据包通过策略路由发往任何你希望它到达的地方。你可以再写一个模块,用以决定是对包进行完全的记录呢,还是对仅仅像LOG target那样只记录协议元数据-这很重要,大多数时候,我们并不关心载荷内容,除非你做深度分析。
总之,我不喜欢那种包揽一切的程序,抓包也是如此,一个ETH_P_ALL将所有数据不问青红皂白全部截取,这是不合适的,当然它更加符合抓包的原本含义,但是谁在乎呢?也许是UNIX哲学在作崇,但也只是也许而已。实证主义并不在任何地方都有效。
在我的实现中,和TEE的实现不同,我只是克隆数据包,然后为其打上一个标签,至于说接下来怎么做,后续的HOOK来决定,你甚至都可以用我的CLONE target和TEE target结合在一起,形成一个packet fork。
下面给出实现,注意,该实现不能做到包嗅探!

实现

本实现由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 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);
}


编译
建议编译时将c代码全部放入xtables-addons的extensions目录,然后修改该目录下的Kbuild文件,加入以下一行:
obj-${build_CLONE} += xt_CLONE.o
修改该目录下的Mbuild文件,加入下面一行:
obj-${build_CLONE} += libxt_CLONE.so
修改该目录上级目录的mconfig文件,加入下面一行:
build_CLONE=m
在extensions目录下执行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从第三层发出即可。
此时又有问题了,既然已经重路由了,为了不直接从第二层路由结果的dev中发出呢,也就是调用dev_queue_xmit函数。实际上是完全可以的,然而工作量也会加大,比如你要自行增加MAC头封装等。在一个成型的实现中,所有的封装都必须由协议栈本身来完成,即调用协议栈的函数,因为协议栈本身就是干这个的,决不要在自己的代码中实现,如果你觉得自己可以实现一个更妙的,那就直接改掉协议栈。

局限

该实现还是有一定局限的,毕竟该实现的做法太高层,它会改变数据包的MAC头,但是这对于针对应用层内容的深度解析,无所谓了。另外需要注意的是,需要在本机做三件工作,第一就是设置CLONE规则及确定mark,第二是根据mark设置策略路由,第三就是将策略路由指向的出口设备的arp禁用掉。除了本机做的工作之外,还要在接收镜像数据的机器的接收接口上开启混杂模式。
毕竟这只是一个试验,并非成型的解决方案,能做到这一点我已经很满足了。
分享到:
评论

相关推荐

    linux镜像,端口

    主要用于红帽子LINUX的系统镜像,主要用于红帽子LINUX的系统镜像,主要用于红帽子LINUX的系统镜像。

    Docker 开启远程链接(2375端口)提供外部访问【附:Docker远程链接操作工具 for windows x64】

    Docker 开启远程链接(2375端口)提供外部访问1. 登陆Docker所在服务器,编辑docker.service文件2. 利用Docker Client远程工具进行docker操作(无需登陆服务器)2.1 下载安装Dcoker远程链接工具3.软件声明 1. 登陆...

    vlmcsd服务器软盘镜像(自己搭建kms服务器2018版)

    基于vlmcsd-1112-2018-10-20-Hotbird64的软盘镜像 使用方法:使用VMware Player创建一个linux虚拟机,然后设置软盘加载floppy144.img即可。 之前在csdn上看到过2015版本的,这个是目前2018的最新版本。 并提供...

    渗透测试新手练习及高手深入挖掘专用Linux及Windows靶机(官方原版镜像)

    metasploitable-linux为常用的Linux靶机,系统包含了很多高风险漏洞、弱口令及容易利用的端口、服务,是渗透测试初学者练习技术的不二之选。 Windows_xp_pro_with_sp2就更不用说了,都被人挖烂了,sp2版本相比sp3更...

    kali镜像文件

    linux kali 是一个高级渗透测试和安全审计Linux发行版,设计用于数字取证和渗透测试,Kali Linux预装了许多渗透测试软件,包括nmap (端口扫描器)、Wireshark (数据包分析器)、John the Ripper (密码破解器),以及...

    nginx(1-9-8).syno.tar官方镜像

    它获得了两节式BSD许可,并在Linux,BSD变体,Mac OS X,Solaris,AIX,HP-UX以及其他* nix版本上运行。它还具有用于Microsoft Windows的概念证明端口。 托管一些简单的静态内容 $ docker run --name some-nginx -...

    docker-windows:在 Linux Docker 容器中运行 Windows GUI 应用程序

    泊坞窗 在 Linux Docker 容器中运行 Windows GUI 应用程序 图片来源: 特征 在 Linux 上 Dockerize Microsoft Windows 应用...docker 镜像有两种版本控制模式: XY (X &gt;=1 ):这是针对葡萄酒版本的。 zixia/window

    集群好书《高性能Linux服务器构建实战》 试读章节下载

    2.5.3 通过端口管理Varnish 2.5.4 管理Varnish缓存内容 2.6 Varnish优化 2.6.1 优化Linux内核参数 2.6.2 优化系统资源 2.6.3 优化Varnish参数 2.7 Varnish的常见应用实例 2.7.1 利用Varnish实现图片...

    Docker容器端口映射后突然无法连接的排查过程

    启动容器后一段时间内都是可以正常工作的,但在不定时间间隔后,外部主机就会出现无法从仓库中拉取镜像的情况,提示TimeOut: 然而在Docker宿主机上访问仓库则可以正常访问: 至于这个问题,只有手动重启出问题的D

    UNIX/Linux系统取证之信息采集案例

    在UNIX/Linux系统取证中,及时收集硬盘的信息至关重要,《Unix/Linux网络日志分析与流量监控》一书中,将详细讨论各种常见系统进程系统调用及镜像文件获取方法。下面简单举几个例子。在UNIX/Linux取证时很多系统和...

    docker-activemq:Apache ActiveMQ 的 Docker 镜像

    - 5.9.0样板文件基于 Ubuntu 14.04 将自动更新和升级 Linux 软件包Dockerfile 构建应用程序细节自动安装以下内容: 卷曲默认 Java JRE 从 Apache 下载档案中提取 ActiveMQ 5.9.0 版公开默认端口: ActiveMQ Web 前端...

    serialport-rs:Rust中的跨平台串行端口库

    介绍 serialport-rs是Rust的通用跨平台串行端口库。 它在POSIX和Windows系统上提供了阻塞的I / O接口和端口枚举。... 在Linux上使用glibc的实现依赖于libudev , libudev是一个外部动态库,在运行最终二进制文

    docker-rust-bechof:构建一个高级docker镜像,该镜像可以在Linux和Wine上运行Rust专用服务器

    构建一个高级docker映像,该映像可以在带有WineLinux下运行Rust专用服务器。 信息: Rust的默认端口是28015 ,RCON端口是28016 。 日志和配置路径为/ var / rust / rustserver /。 在Docker主机上的准备工作 对于...

    华为HCIP-RS培训视频教程【共56集】.rar

    08-端口镜像 09-vlan高级配置之基于Mac地址划分vlan 10-vlan高级配置之基于Ip子网划分vlan 11-supervlan和端口隔 12-vlan高级配置之vlanmapping 13-hybrid(上) 14-hybrid(下) 15-ssh 16-mstp 17-vrrp_...

    WindTerm2.5.0_x64_windows

    支持直接/本地端口转发、反向/远程端口转发和动态端口转发。 支持 XModem、YModem 和 ZModem。 集成 sftp、scp 客户端,支持下载、上传、删除、重命名、新建文件/目录等。 集成本地文件管理器,支持移动到、复制到、...

    Seafile服务器docker一键部署脚本

    # Seafile 镜像制作及使用说明 1. 从阿里云申请SSL证书并放置到image/cert目录下,更改pem和key名称为server; 2. 制作docker镜像 ```shell # 切换到Dockerfile目录,执行如下命令 docker build -t 你的ID/...

    Docker如何使用Dockerfile构建镜像

    Dockfile是一种被Docker程序解释的脚本,Dockerfile由一条一条的指令组成,每条指令对应Linux下面的一条命令。Docker程序将这些Dockerfile指令翻译真正的Linux命令。Dockerfile有自己书写格式和支持的命令,Docker...

    NetCore_HelloworldForLinuxDocker.zip

    用于练习docker打包生成netcore的docker镜像,运行起来映射容器的80端口即可。比如docker run -d yourcontainername -p 10080:80 youreimagesname,那么你访问这个项目的地址就是http://你的物理机ip:10080

    Linux服务器下利用Docker部署.net Core项目的全过程

    FROM microsoft/dotnet//基于'microsoft/dotnet' 来构建镜像 COPY . /app //拷贝项目文件夹中的所有文件到docker容器中的app文件夹 这里是两个参数 WORKDIR /app //设置工作目录为 '/app' 文件夹,即容器启动默认的...

Global site tag (gtag.js) - Google Analytics