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

OpenVPN以及其它IP层VPN的完全链路层处理的实现

 
阅读更多
如果OpenVPN也能实现传输模式VPN该有多好,如果基于OpenVPN实现的VPN产品能仅仅作为一根昂贵的网线串接在用户网络环境,自动捕获感兴趣流量该有多好;如果它能做到只需要配置一个IP即可工作而无需配置任何路由该有多好。
我们知道OpenVPN是一个用户态的程序,靠字符设备接收从虚拟网卡进来的以太帧或者IP数据报后,作为套接字buffer从本地发送出去,对于返回的数据,写入字符设备,模拟虚拟网卡接收动作,然后再通过路由结果发送数据到下一跳,这一切的重中之重就是路由,也就是说,如果你部署了一个基于OpenVPN的VPN,那么该VPN上配置路由是在所难免的,而我们知道,路由配置基本就是一个体力活儿,不难,但是一旦弄错,后果很严重,于是避免配置路由就成了一个重要需求,能不能利用一种机制,让系统自动识别下一跳呢?在回答这个问题之前,我们必须弄明白“下一跳”这个概念的本质,它有两个层面的含义:
1.它将IP数据报继续推向目标,因为IP数据报是逐跳发送的;
2.它为链路层实际发送数据提供了一个依据。
如果我们不考虑虚拟的东西,仅仅从实际发送数据这个角度来看,对于以太网,所谓的下一跳的作用仅仅就是获得一个目标MAC地址而已,然后链路层以该MAC地址作为目标MAC,之后将数据包仍出去就完事了。
到此,一切变得简单了。我们能在original方向获得一个数据帧的源MAC,将其缓存在ip_conntrack结构中,然后对于reply的数据,用该缓存的MAC作为目标MAC封装数据帧,之后直接发送,不再经过路由层。这个设想由于Netfilter的存在使其实际实现变得简单。
先来看一个图,图示上给出了一个实际的需求:

VPN endpoint已经做成了网桥,既然做成了网桥,当然是不希望在它上面配置路由了,那么就需要程序可以自动存储和识别要发送数据帧的目标MAC地址,然后直接用该MAC封装数据,以下的代码实现了这一点:

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/types.h>
#include <linux/netfilter.h>
#include <linux/module.h>
#include <linux/sysctl.h>
#include <net/dst.h>

#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>

struct gwinfo {
    //自动保存回复帧的目标MAC地址
    unsigned char reply_gw_mac[ETH_ALEN];
    //自动保存正向帧的原始目标MAC地址  
    unsigned char orig_gw_mac[ETH_ALEN];
    unsigned int local_flag;
    //自动保存设备
    struct net_device *dev;
};

static struct nf_ct_ext_type sggw_extend __read_mostly = {
    .len        = sizeof(struct gwinfo),
    .align        = __alignof__(struct gwinfo),
    .id        = NF_CT_EXT_SGGW,
};

struct gwmark {
    __u32 reply_mark;
    __u32 orig_mark;
};

struct gwmark g_mark = {0x32, 0x32};

static unsigned int  
gwmark_target(struct sk_buff *skb, const struct xt_target_param *par)  
{  
    struct gwmark *gm = (struct gwmark*)par->targinfo;  
    g_mark.reply_mark = gm->reply_mark;
    g_mark.orig_mark = gm->orig_mark;
    return NF_ACCEPT;
}  

static bool gwmark_check(const struct xt_target_param *par)
{
    //TODO
    return true;
}

static struct xt_target gwmark_tg __read_mostly = {  
    .name           = "GWMARK",  
    .family         = NFPROTO_IPV4,  
    .target         = gwmark_target,  
    .targetsize     = sizeof(struct gwmark),  
    .table          = "mangle",  
    .hooks          = (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_OUT),  
    .checkentry     = gwmark_check,  
    .me             = THIS_MODULE,  
};


static unsigned int ipv4_conntrack_get(unsigned int hooknum,
                      struct sk_buff *skb,
                      const struct net_device *in,
                      const struct net_device *out,
                      int (*okfn)(struct sk_buff *))
{
    enum ip_conntrack_info ctinfo;
    struct gwinfo *ginfo = NULL;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    if (!ct) {
        return NF_ACCEPT;
    }
    //仅仅针对正方向的包进行ginfo管理
    if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL || ctinfo == IP_CT_NEW) {
        //如果是一个流的头包,那么设置该流的ginfo
        if(ctinfo == IP_CT_NEW) {
             ginfo = nf_ct_ext_add(ct, NF_CT_EXT_SGGW, GFP_ATOMIC);
        } else { //否则直接取出
            ginfo = nf_ct_ext_find(ct, NF_CT_EXT_SGGW);
        }
        if (ginfo) {
            struct ethhdr *eth = (struct ethhdr*)(skb->data - ETH_HLEN);
            //保存数据包的源MAC地址,用以自动封装返回包的目标MAC
            memcpy(ginfo->reply_gw_mac, eth->h_source, ETH_ALEN);
            //保存数据包的目标,以求在数据被用户态(比如OpenVPN)重新封装后直接封装目标MAC地址
            memcpy(ginfo->orig_gw_mac, eth->h_dest, ETH_ALEN);
            //保存设备变量,因为想直接发送一个数据而不经路由,必须指定一个设备
            ginfo->dev = skb->dev;
            //默认数据不是本地用户态发出的
            ginfo->local_flag = 0;
        }
    }
    return NF_ACCEPT;
}

//这个HOOK函数没什么大不了的,旨在设置local标志
static unsigned int ipv4_conntrack_get_local(unsigned int hooknum,
                      struct sk_buff *skb,
                      const struct net_device *in,
                      const struct net_device *out,
                      int (*okfn)(struct sk_buff *))
{
    struct gwinfo *ginfo = NULL;
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    if (!ct) {
        return NF_ACCEPT;
    }
    ipv4_conntrack_get(hooknum, skb, in, out, okfn);
    if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL || ctinfo == IP_CT_NEW) {
        ginfo = nf_ct_ext_find(ct, NF_CT_EXT_SGGW);
        if (ginfo) {
            ginfo->local_flag = 1;
        }
    }
    return NF_ACCEPT;
}

//以下的HOOK函数实现自动封装以太帧而不经路由的逻辑
static unsigned int ipv4_conntrack_set(unsigned int hooknum,
                     struct sk_buff *skb,
                     const struct net_device *in,
                     const struct net_device *out,
                     int (*okfn)(struct sk_buff *))
{
    enum ip_conntrack_info ctinfo;
    struct gwinfo *ginfo = NULL;
    struct net_device *dev = NULL;
    unsigned int local = 0;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    if (skb->mark == g_mark.reply_mark &&
        CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY &&
        hooknum == NF_INET_PRE_ROUTING) {
        ginfo = nf_ct_ext_find(ct, NF_CT_EXT_SGGW);
        if (ginfo) {
            dev = ginfo->dev;
        }
    } else if (skb->mark == g_mark.orig_mark &&
            CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL &&
            hooknum == NF_INET_LOCAL_OUT) {
        ginfo = nf_ct_ext_find(ct, NF_CT_EXT_SGGW);
        if (ginfo) {
            struct dst_entry * dst = skb_dst(skb);
            dev = (struct net_device*)dst->dev;
            local = 1;
        }
    }
    if (dev) {
        //这里的逻辑再明显不过了,直接在PREROUTING这个HOOK点上执行XMIT...
        struct ethhdr *eth = (struct ethhdr*)skb->data - ETH_HLEN;
        if (local) {
            memcpy(eth->h_dest, ginfo->orig_gw_mac, ETH_ALEN);
        } else {
            memcpy(eth->h_dest, ginfo->reply_gw_mac, ETH_ALEN);
        }
        skb->dev = dev;
        dev_queue_xmit(skb);
        return NF_STOLEN;
    }
    return NF_ACCEPT;
}

static struct nf_hook_ops ipv4_conntrack_gwops[] __read_mostly = {
    {
        .hook        = ipv4_conntrack_get,
        .owner        = THIS_MODULE,
        .pf        = NFPROTO_IPV4,
        .hooknum    = NF_INET_PRE_ROUTING,
        .priority    = NF_IP_PRI_CONNTRACK+1,
    },
    {
        .hook        = ipv4_conntrack_get_local,
        .owner        = THIS_MODULE,
        .pf        = NFPROTO_IPV4,
        .hooknum    = NF_INET_LOCAL_OUT,
        .priority    = NF_IP_PRI_CONNTRACK+1,
    },
    {
        .hook        = ipv4_conntrack_set,
        .owner        = THIS_MODULE,
        .pf        = NFPROTO_IPV4,
        .hooknum    = NF_INET_PRE_ROUTING,
        .priority    = NF_IP_PRI_MANGLE+1,
    },
};

//init函数为例行注册
static int __init nf_conntrack_sggw_init(void)
{
    int ret = 0;

    ret = nf_register_hooks(ipv4_conntrack_gwops,
                ARRAY_SIZE(ipv4_conntrack_gwops));
    if (ret < 0) {
        printk("nf_conntrack_ipv4: can't register gw hooks.\n");
    }
    ret = nf_ct_extend_register(&sggw_extend); //注册
    if (ret < 0) {
        printk(KERN_ERR "sggw: Unable to register extension\n");
        return ret;
    }
    ret = xt_register_target(&gwmark_tg);
    if (ret < 0) {
        printk(KERN_ERR "sggw: Unable to register target\n");
        return ret;
    }
    return ret;
}

//fini函数为例行解注册
static void __exit nf_conntrack_sggw_fini(void)
{
    synchronize_net();
    nf_unregister_hooks(ipv4_conntrack_gwops, ARRAY_SIZE(ipv4_conntrack_gwops));
    nf_ct_extend_unregister(&sggw_extend);
    xt_unregister_target(&gwmark_tg);
}

module_init(nf_conntrack_sggw_init);
module_exit(nf_conntrack_sggw_fini);

MODULE_ALIAS("sggw");
MODULE_LICENSE("GPL");


除了以上的内核模块代码,还需要一个iptables程序,用以设置两个mark,该用户态模块我没有写,而是使用了别的方式达到了目标。近期实在太忙了,做事不再有始有终...以上的模块自己测试了一下,运行得还不错,不过有一点我没有考虑到,那就是以上的代码没有实现任何的MAC地址变更的Notify机制,因此只适合于物理位置稳定的以太网环境,遇到上游路由器的热备切换,可能会有问题,然而如果热备组中的它们共享一个MAC地址,那敢情好了,可是VRRP/HSRP志不在此!!
在产品实施中受辱,一时难以释怀,近期志在实现基于OpenVPN的传输模式的VPN,任何人可以侮辱我这个人,然而不能侮辱我的产品!

分享到:
评论

相关推荐

    基于openvpn的web管理系统,前后端分离设计。

    基于openvpn的web管理系统,前后端分离设计。

    openvpn的几种组网方式

    openvpn的几种组网方式

    openvpn 2.5.10版本

    openvpn 2.5.10版本,通过三板斧即可安装,证书生成需要的easy-rsa3.1.5版本也在压缩包里面。

    ciscovpn完全配置指南.pdf

    ciscovpn完全配置指南.pdf

    二层vpn,运营商二层vpn常用技术,跨域方案分享给大家

    比较热门的一种MPLS二层VPN技术。业界有两个标准,一个标准以LDP作为信令协议,由Alcatel发起,得到业界大部分厂家的支持(包括Cisco);另一个标准由Juniper发起,以BGP作为信令协议,只有Juniper和华为支持。

    H3CMSR系列路由器二层vpn接入功能典型配置案例归纳.pdf

    H3CMSR系列路由器二层vpn接入功能典型配置案例归纳.pdf

    易语言获取外网ip5种方法

    P2P技术中,节点之间可以直接通信,可以通过向其他节点询问自身的IP来获取公网IP。在易语言中实现P2P通信可能需要使用到第三方库,如libp2p。不过这种方法较为复杂,一般不适合初学者。 5. **NAT穿透** 如果在NAT...

    网络系统管理赛项软件包

    它们负责网络中的命名解析、IP地址分配以及文件共享等功能。通过这些服务模块,管理员可以有效地管理和维护网络环境,确保服务的稳定性和安全性。 2. **普通PC软件包**:这部分可能包含常用的操作系统(如Windows或...

    网络工程师培训 运维工程师培训 系统工程师技术培训 LNPU 04 数据链路层与交换机 共43页.pptx

    掌握数据链路层的理论基础与相关设备 掌握网络层的理论基础与相关设备 掌握传输控制层的理论基础与相关设备 掌握应用层的理论基础与相关应用 掌握网线制作的线序 掌握光纤技术,识别相关光纤接口 了解广域网技术与...

    利用公网IP搭建端口映射.zip

    本教程将详细讲解如何利用公网IP搭建端口映射,实现内网穿透。 首先,我们需要理解什么是公网IP。公网IP是全球唯一、可以直接通过互联网访问的IP地址,而私网IP则是局域网内部使用的地址,不能直接被外部网络访问。...

    e语言-易语言IP自动更新

    IP自动更新程序能够帮助用户在IP地址发生变化时,自动获取并更新新的IP地址,确保远程访问或其他依赖IP的服务能够正常工作。 描述中的“程序结合易语言互联网支持库实现IP自动更新”揭示了实现这一功能的关键技术:...

    softether-open-vpnserver-vpnbridge-v4.43-9799.exe

    其他各种强大的网络搭建都能做 截止到发贴日(2024-5-13),最新的版本是该版本 v4.43-9799 另附网盘下载地址 https://pan.baidu.com/s/1K3hqYkBp_gtMTkxVd21csQ?pwd=8888 使用教程:...

    IP网络业务关键承载技术探讨

    越来越多的业务通过IP网络承载已经成为不可阻挡的发展趋势,运营商运营的关键NGN、3G、IPTV业务,企业VPN互连、视频会议/ 监控、大客户专线/IDC等业务,对IP网络提出了更高的要求。通过实施网络组织、网络自愈和保护...

    open-build-master+Open虚拟专网2.5源代码+依赖项源代码

    与原版open-build-master相比,我做了稍稍修改,已经包含Open虚拟专网2.5源代码和依赖项源代码,需要VS2019、ActivePerl、WDK10,可以直接按照我写的教程进行编译,100%可编译。

    Huawei NGFW BGP-MPLS IP VPN .docx

    Huawei NGFW BGP-MPLS IP VPN基本概念和基本模型,路由发布过程和报文转发过程

    关于VPN加密技术的不同技术应用

    文章还详细阐述了VPN的定义、分类、关键技术及配置,包括GRE VPN、IPSec VPN、L2TP VPN和SSL VPN的原理和应用场景。 重点内容: 1. 加密技术保证数据安全性和完整性。 2. VPN定义和分类。 3. GRE VPN的原理和应用...

    南方电网SSL VPN系统案例

    同时,一些广域网加速的新技术也在不断向深信服 SSL VPN 移植,例如 Flash-Link 技术、TCP 协议代理技术、流缓存技术等,这些在其他领域获得突破的技术都在不断改变 SSL VPN 的用户体验。 深信服 SSL VPN 设备支持...

    谈IP化对传送及承载网演进理念带来的变化

    IP领域技术发展带有明显的技术驱动的特点,新技术层出不穷:基于IP网络的快速转发技术MPLS,基于IP网络的业务组网技术MPLSVPN,基于IP网络的QoS技术IntServ/Diffserv,基于IP网络的流量工程和保护技术 MPLSTE/...

    一个IP 修改器,可以更改ip

    如其名,可以改变本机ip,可以借此浏览一些教育网,或对ip段有要求的网站

    rmi 连接多 IP 多服务端自动路由

    "rmi 连接多 IP 多服务端自动路由"这个主题涉及到如何在RMI环境中实现高可用性和容错性,当服务端在一个IP地址上不可用时,客户端能自动切换到其他IP上的服务实例。 首先,我们要理解RMI的基本工作原理。RMI系统由...

Global site tag (gtag.js) - Google Analytics