当前位置 : 主页 > 操作系统 > centos >

Open×××的Linux内核版,鬼魅的残缺 part I:The PROTOCOL

来源:互联网 收集:自由互联 发布时间:2022-06-20
Open×××的多处理一直都是问题,但是作为轻量级×××,这无所谓,但是如果你要将其作为重量级×××来用,那就必须考虑了。 之前,我将Open×××分裂成了多线程版本,但是由于Open××

Open×××的多处理一直都是问题,但是作为轻量级×××,这无所谓,但是如果你要将其作为重量级×××来用,那就必须考虑了。
       之前,我将Open×××分裂成了多线程版本,但是由于Open×××原本的buffer管理粒度就很粗,以至于我很难在多个线程中能够同时处理一个 multi_instance,所以我不得不采用一个multi_instance绑定一个线程的做法,为此还特意实现了一个自己版本的多队列TUN网卡 已经UDP的hash reuseport机制,这一切耦合太紧密,以至于牵一发可动全身!
       内核中的多处理是原生态的,没有被污染!原本现代千兆/万兆卡就能很好的利用多个处理器核心,这不是本文的重点,详情请参考Intel e1000e或者ixgb驱动的README。如果协议栈的softirq处理被分发到了多个CPU核心,那么在其本身来解析Open×××的协议将是一 个不错的选择,事实上我也可以在耗时的操作上去触发一个新类型的tasklet,总之能玩的东西太多,以至于我必须做出选择!
       What?在协议栈去处理Open×××协议?是的!想法是单纯的,但是如何实现呢?Netfilter上挂HOOK是一个自然而然的想法,但是这次我想 试试不同的方案。我参考了IPSec的实现,但是决不是FreeSWAN的那个实现,因为它也是基于Netfilter的!我又想参考XFRM,但是由于 IPSec的ESP/AH本身就是独立的传输层协议,因此你可以直接在第四层挂协议,而Open×××则完全不同,它是一个UDP上层的协议(我在此并没 有考虑Open××× over TCP的方案),而Linux的协议栈处理中,UDP上面就直接传给socket了,直接进入用户态了,只好作罢。
       还好,Linux的UDP处理中有一个encap机制,也就是说你可以去注册一个encap_type,然后挂一个encap hook,叫做encap_rcv的回调函数,在里面去做掉一切。规范如下:

int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) {     struct udp_sock *up = udp_sk(sk);     int rc;     int is_udplite = IS_UDPLITE(sk);     /*      *    Charge it to the socket, dropping if the queue is full.      */     if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))         goto drop;     nf_reset(skb);     if (up->encap_type) {         /*          * This is an encapsulation socket so pass the skb to          * the socket's udp_encap_rcv() hook. Otherwise, just          * fall through and pass this up the UDP socket.          * up->encap_rcv() returns the following value:          * =0 if skb was successfully passed to the encap          *    handler or was discarded by it.          * >0 if skb should be passed on to UDP.          * <0 if skb should be resubmitted as proto -N          */         /* if we're overly short, let UDP handle it */         if (skb->len > sizeof(struct udphdr) &&             up->encap_rcv != NULL) {             int ret;             ret = (*up->encap_rcv)(sk, skb);             if (ret <= 0) {                 UDP_INC_STATS_BH(sock_net(sk),                          UDP_MIB_INDATAGRAMS,                          is_udplite);                 return -ret;             }         }         /* FALLTHROUGH -- it's a UDP Packet */     } ...//常规处理 }

好的,就是它了!也就是说,我可以在udp_queue_rcv_skb的encap_rcv中将处理流程短路,短接到哪里呢?当然 是tun网卡的netif_rx了...现在考虑反向的处理,即tun网卡的xmit到字符设备的处理,也需要类似的短路,直接将tun的xmit处理和 UDP的发送进行短接。这就是全部的框架,总图如下:


Open×××的Linux内核版,鬼魅的残缺 part I:The PROTOCOL_Linux


难度不大。

       我又一次拿tun.c开刀了,代码直接修改了tun.c,在xmit中调用了我挂的xmit HOOK,并且通过增加一个新的ioctl命令来为一个UDP socket挂一个encap_rcv HOOK,完成两个方向的短路处理。所有的思路都以注释的方式散在代码中,补丁如下:

--- tun.c.orig  2013-11-30 13:17:30.000000000 +0800 +++ tun.c   2014-02-08 18:44:34.000000000 +0800 @@ -34,6 +34,28 @@   *    Modifications for 2.3.99-pre5 kernel.   */ +/* + * 我,又一次自私地使用了tun.c,不过这次的工作和tun本身并没有太大的关系, + * 只是想做一个简单的Open×××短路hack,仅此而已,我使用tun做修改是因为简单, + * 毕竟我只是需要将一个socket和tun联系起来,仅此而已,我需要做的就是短接 + * UDP socket和tun网卡,仅此而已....  :) + * + * 数据通道进入内核的好处是显而易见的,多处理操作的效率由softirq分发系统决定, + * 而这个是简单的,在8核心处理器上,经过测试,使用Intel 82583多队列卡,按照 + * tuple做hash中断分发,保持cache活性的基础上,也能首先Open×××协议的高速解析, + * 任何用户态的多线程架构与之相比都爆弱。但是此时问题浮现: + * + *  1.不是说内核态处理控制面而用户态处理数据面吗?对于Open×××,怎么反过来了啊, + *    有点懵了!是的,数据面放到用户态只善作个幻象,现如今不是还没有很好的实例嘛... + *    我并非说用户态多线程不好,只是对Open×××而言的,不信你试试。好了,在PF RING + *    还玩不转的时候,我只能这样,也不容易。 + *  2.这里没有使用加密,接口是有了,但是没有高效的实现,我可不想Open×××成为Yet  + *    Another IPSec + *  + * 问题多多,marywangran@126.com,还是这个邮箱 + * + **/ +  #define DRV_NAME   "tun"  #define DRV_VERSION    "1.6"  #define DRV_DESCRIPTION    "Universal TUN/TAP device driver" @@ -64,7 +86,19 @@  #include <net/net_namespace.h>  #include <net/netns/generic.h>  #include <net/rtnetlink.h> +#include <net/checksum.h>  #include <net/sock.h> +#include <net/udp.h> +#include <linux/socket.h> +#include <net/inet_sock.h> +#include <linux/udp.h> +#include <linux/ip.h> +#include <linux/net.h> +#include <linux/file.h> +#include <linux/jhash.h> +#include <linux/netfilter.h> +#include <linux/netfilter_ipv4.h> +  #include <asm/system.h>  #include <asm/uaccess.h> @@ -82,6 +116,31 @@  #define DBG1( a... )  #endif +/* 定义一个Open×××封装类型 */ +#define UDP_ENCAP_O××× 20 +/* 连接一个UDP套接字和TUN网卡的ioctl命令 */ +#define TUNLINKO×××   _IOW('T', 216, int) +/* 添加一个multi_instance的ioctl命令 */ +#define TUNADDMILTI   _IOW('T', 217, int) +/* 为一个multi_instance添加一个虚拟地址的ioctl命令 */ +#define TUNSETMIVIP   _IOW('T', 218, int) +/* 删除一个multi_instance的ioctl命令 */ +#define TUNDELMILTI   _IOW('T', 219, int) +/* 设置密钥的ioctl命令 */ +#define TUNSETMKEY   _IOW('T', 220, int) +/* 获取密钥的ioctl命令 */ +#define TUNGETMKEY   _IOW('T', 221, int) + +#define O×××_OPT_DEC    0 +#define O×××_OPT_ENC    1 + +/* + * 用于封装ioctl命令,但不经常,也不绝对...  + **/ +struct sockfd { +   int fd; +}; +  #define FLT_EXACT_COUNT 8  struct tap_filter {     unsigned int    count;    /* Number of addrs. Zero means disabled */ @@ -97,6 +156,147 @@  struct tun_sock; + +/* UDP的encap返回正常路径 */ +#define UDP_DECAP_PASS     1 +/* UDP的encap自己消费了数据包 */ +#define UDP_DECAP_STOLEN   0 +/* 以上的规范详细情况自行看UDP处理以及IPSec/L2TP作为一个例子的实现 */ + +/* + * Open×××的常量定义,我是不是该准备一个头文件和C文件呢? + * 借用tun.c总不是什么长久之事!tun又不是只用于Open×××啊, + * 然而tun.c确实该加一个HOOK机制了...  + **/ +#define MAX_HASH_BUCKETS   256 +/* 暂时先这么多 */ +#define MAX_KEY_LENGTH      512 +#define P_DATA_V1                      6 +#define P_OPCODE_SHIFT                 3 + +/* 这个锁的粒度有点粗 */ +DEFINE_SPINLOCK(ovpn_lock); + +typedef u32 packet_id_type; +typedef u32 net_time_t ; + +/* + * 使用IP地址/端口对建立multi_instance  + **/ +struct instance_req { +    u32 real_addr; +    __be16 port; +}; + +/* + * 为一个multi_instance添加一个虚拟IP地址,此结构体目前仅适用于 + * TUN模式。因为对于TAP模式需要实现一个列表,基于该列表实现一个 + * 虚拟交换机,哦,是的,虚拟交换机... + * */ +struct instance_vreq { +    u32 real_addr; +    u32 vaddr; +    __be16 port; +}; + +/* 用于向内核传递密钥或者反过来传递密钥 */ +/* 是不是应该用PF_KEY啊,小小说不能,我就不用了 */ +struct key_block { +    struct instance_req ir; +    unsigned char key1[MAX_KEY_LENGTH]; +    unsigned char key2[MAX_KEY_LENGTH]; +    unsigned char key3[MAX_KEY_LENGTH]; +    unsigned char key4[MAX_KEY_LENGTH]; +}; + +/* + * 用于实现Open×××的防重放机制  + **/ +struct packet_id_send +{ +   packet_id_type id; +   time_t time; +}; + +/* + * 用于实现Open×××的防重放机制,但是天啊...里面的字段在协议移植阶段 + * 是没有任何用武之地的,是的,没有用... + * */ +struct packet_id_rec +{ +   time_t last_reap;           /* last call of packet_id_reap */ +   time_t time;                /* highest time stamp received */ +   packet_id_type id;          /* highest sequence number received */ +   int seq_backtrack;          /* set from --replay-window */ +   int time_backtrack;         /* set from --replay-window */ +   int max_backtrack_stat;     /* maximum backtrack seen so far */ +   int initialized;           /* true if packet_id_init was called */ +   struct seq_list *seq_list;  /* packet-id "memory" */ +   const char *name; +   int unit; +}; + +/* + * 用于实现Open×××的防重放机制,目前的版本仅仅是为了例行公事,发送前 + * 在Open×××头中封装一个递增的packet ID,但是注意,不支持LONG FORM!! + * */ +struct packet_id +{ +   struct packet_id_send send; +   struct packet_id_rec rec; +}; + +/* + * 万恶又万能的multi_instance,是不是有点熟悉呢??对!This is it! + * */ +struct multi_instance { +   struct list_head list; +   struct hlist_node rhnode; +   struct hlist_node vhnode; +   struct sock *sk; +   struct packet_id packet_id; +   u32 saddr; +   u32 daddr; +   unsigned char hsaddr[ETH_ALEN]; +   /* for a learning Vswitch , it is a list! TODO */ +   unsigned char hdaddr[ETH_ALEN]; +   u32 real_saddr; +   u32 real_daddr; +   __be16 dport; +    void (*mi_destroy)(struct multi_instance *); +}; + +/* + * 我的本意并不是移植Open×××,而是实现一个新的协议,but,but,but,but + * 苦于没有客户端,我为何不使用现成的Open×××呢??它的协议足够简单啊足够简单! + * */ +struct encap_context { +   struct hlist_head hash[MAX_HASH_BUCKETS]; +   struct hlist_head vhash[MAX_HASH_BUCKETS]; +    /* 最终还是说服了自己,解除了Open×××和tun之间的耦合 :) */ +   int (*encap_xmit)(struct tun_struct *tun, struct sk_buff *skb); +    /* 我并没有区分cipher和auth,也就是说,我把加密运算和HMAC统一使用一套回调函数完成 :>| */ +   int (*cipher_init)(void *arg); +   int (*cipher_enc)(struct sk_buff *skb, void *arg); +   int (*cipher_fini)(void *arg); +}; + +/* + * 就是它!这就是Open×××协议的本质!瞧瞧看吧,你仅仅需要设置3个字段足矣! + * ocode:这个字段其实包含以下两个部分 + *      opt     :很显然,我在内核中只处理数据通道,那么它是P_DATA_V1常量 + *      key_id  :这个keyid用于切换密钥。目前使用定值0,即版本0.1不支持密钥重协商, + *              然则这只是个开始... + * id: 此字段用于封装将要发送的数据包的ID,防重放攻击 + * 可见,关键的关键就是如何填充以下结构体的问题...对了,我可以说填充UDP头和IP头不是个事儿 + * 吗?如果它们都成了事儿,还怎么好意思说自己比较喜欢折腾内核协议栈呢... :( + **/ +struct ovpnhdr { +   u8 ocode; +   packet_id_type id; +    /* 注意,不要按照最长字段自然对齐,这是在玩网络,而不是内存! */    +} __attribute__((packed)); +  struct tun_struct {     struct tun_file     *tfile;     unsigned int        flags; @@ -108,6 +308,11 @@     struct tap_filter       txflt;     struct socket       socket; +   struct sock     *encap_sock; +   /* pass THIS into encap_xmit like OO ?? */ +    /* 对于这个回调函数,我该说些什么呢?实际上,我真的该将其放在encap_context里面 */ +   /* int (*encap_xmit)(struct tun_struct *tun, struct sk_buff *skb);*/ +   struct encap_context ctx;  #ifdef TUN_DEBUG     int debug; @@ -119,6 +324,497 @@     struct tun_struct   *tun;  }; +/* + * 这个destroy函数用于清理一个multi_instance,一个析构  + **/ +void ovpn_destroy(struct multi_instance *mi) +{ +    return; +} + +/* + * 根据一个IP地址和端口删除一个multi_instance + **/ +static void ovpn_del_real_instance(    struct tun_struct *tun,  +                   u32 real_addr, +                   __be16 port) +{ +   struct multi_instance *tmi; +   struct multi_instance *mi; +   struct hlist_node *node; +   unsigned int hash = jhash_2words(real_addr, port, 0); + +   spin_lock_bh(&ovpn_lock); +   hlist_for_each_entry(tmi, node, &tun->ctx.hash[hash % MAX_HASH_BUCKETS], rhnode) { +       if (real_addr == tmi->real_daddr && +           port == tmi->dport) { +           mi = tmi; +       } +   } +   if (!mi) { +       spin_unlock_bh(&ovpn_lock); +       return ; +   } +   hlist_del(&mi->rhnode); +   hlist_del(&mi->vhnode); +   spin_unlock_bh(&ovpn_lock); +   kfree(mi); +} + +/* + * 添加一个multi_instance + **/ +static struct multi_instance *ovpn_add_real_instance(  struct tun_struct *tun,  +                   u32 real_addr, +                   __be16 port) +{ +   struct multi_instance *ret = NULL; +   struct multi_instance *tmi; +   struct hlist_node *node; +   unsigned int hash = jhash_2words(real_addr, port, 0); + +   spin_lock_bh(&ovpn_lock); +   hlist_for_each_entry(tmi, node, &tun->ctx.hash[hash % MAX_HASH_BUCKETS], rhnode) { +       if (real_addr == tmi->real_daddr && +           port == tmi->dport) { +           spin_unlock_bh(&ovpn_lock); +           return tmi; +       } +   } +   ret = kzalloc(sizeof(struct multi_instance), GFP_ATOMIC); +   if (!ret) { +       spin_unlock_bh(&ovpn_lock); +       return NULL; +   } +   ret->dport = port;   +   ret->real_daddr = real_addr; +   ret->sk = tun->encap_sock; +   ret->mi_destroy = ovpn_destroy; +   ret->real_saddr = inet_sk(ret->sk)->saddr; +   hash = jhash_2words(ret->real_daddr, ret->dport, 0); +    INIT_HLIST_NODE(&ret->rhnode); +    INIT_HLIST_NODE(&ret->vhnode); +   hlist_add_head(&ret->rhnode, &tun->ctx.hash[hash % MAX_HASH_BUCKETS]); +   spin_unlock_bh(&ovpn_lock); +   return ret; +} + +/* + * 为一个multi_instance添加一个虚拟IP地址,这个本来应该实现成一个虚拟交换机的 + * BUT对于TUN模式而言,我采用了替换模式,也就是说,我的这个版本并不支持iroute + * 不支持又怎么样呢?早晚的事吧。希望,真心希望James Yonan不要打我哦。。。 + **/ +static int ovpn_add_virtual_instance(  struct tun_struct *tun,  +                   u32 real_addr, +                   __be16 port, +                   u32 addr) +{ +   struct multi_instance *mi; +   struct multi_instance *tmi; +   struct hlist_node *node; +   unsigned int hash = jhash_2words(real_addr, port, 0); + +   spin_lock_bh(&ovpn_lock); +   hlist_for_each_entry(tmi, node, &tun->ctx.hash[hash % MAX_HASH_BUCKETS], rhnode) { +       if (real_addr == tmi->real_daddr && +           port == tmi->dport) { +           mi = tmi; +           break; +       } +   } +   if (!mi) { +       spin_unlock_bh(&ovpn_lock); +       return -1; +   } +   hlist_del_init(&mi->vhnode); +   mi->daddr = addr; +   hash = jhash_1word(mi->daddr, 0); +   hlist_add_head(&mi->vhnode, &tun->ctx.vhash[hash % MAX_HASH_BUCKETS]); +   spin_unlock_bh(&ovpn_lock); +   return 0; +} + +static int ovpn_pre_endecrypt(int mode,  +                            struct tun_struct *tun,  +                            struct sk_buff *skb, +                            struct multi_instance *mi) +{ +   u8 *data; +   u8 ocode = 0; +    int ret = 0; +   int op; +    if (mode == O×××_OPT_DEC) { +       data = skb->data; +       ocode = data[0]; +       op = ocode >> P_OPCODE_SHIFT; +       if (op != P_DATA_V1) { +            ret = -1; +           goto out;        +       } +    } else if (mode == O×××_OPT_ENC){ + +    } else { +        ret = -1; +        goto out; +    } +out: +    return ret; +} + +static int ovpn_endecrypt(int mode,  +                            struct tun_struct *tun,  +                            struct sk_buff *skb, +                            struct multi_instance *mi) +{ +     +    /* return tun->ctx.endecrypt(tun, skb); */ +    return 0; +} + +struct ovpnhdr *ovpn_hdr(struct sk_buff *skb) +{ +   return (struct ovpnhdr*)(skb->data); +} + +static int ovpn_post_endecrypt(int mode,  +                                struct tun_struct *tun,  +                                struct sk_buff *skb, +                                struct multi_instance *mi) +{ +    int ret = 0; +    struct ovpnhdr *ohdr; +    if (mode == O×××_OPT_ENC) { +       ohdr = ovpn_hdr(skb); +       ++mi->packet_id.send.id; +       ohdr->id = htonl(mi->packet_id.send.id); +       ohdr->ocode = (P_DATA_V1 << P_OPCODE_SHIFT) | 0x0; +    } else if (mode == O×××_OPT_DEC) { +    } else { +        ret = -1; +        goto out; +    } +out: +    return ret; +} + +/* + * 真正的亡灵序曲在这里大肆打折! + * 它截取了UDP的receive处理流程,它可以自行处理数据包,也可以将数据包返回给正常的UDP receive流程 + * 点赞的说,它就是一个UDP Netfilter,或者叫做UDPFilter更好!它也有自己的规范: + * +         * This is an encapsulation socket so pass the skb to +         * the socket's udp_encap_rcv() hook. Otherwise, just +         * fall through and pass this up the UDP socket. +         * up->encap_rcv() returns the following value: +         * =0 if skb was successfully passed to the encap +         *      handler or was discarded by it. +         * >0 if skb should be passed on to UDP. +         * <0 if skb should be resubmitted as proto -N +         *  + * 有点蹩脚,但是毕竟是一种HOOK机制,实用主义者会说,就是它了!                                                              + */ +static int ovpn_data_channel_decap_recv(struct sock *sk, struct sk_buff *skb) +{ +   struct tun_struct *tun = NULL; +    struct multi_instance *mi = NULL; +   struct multi_instance *tmi; +    struct hlist_node *node; +   struct iphdr *hdr = ip_hdr(skb); +   struct udphdr *ud = udp_hdr(skb); +   int ret = UDP_DECAP_PASS; +   u32 addr = hdr->daddr; +    __be16 port = ud->source; +   unsigned int hash = jhash_2words(addr, port, 0); + +   tun = (struct tun_struct *)sk->sk_user_data; +    + +    spin_lock_bh(&ovpn_lock); +   hlist_for_each_entry(tmi, node, &tun->ctx.hash[hash % MAX_HASH_BUCKETS], rhnode) { +       if (addr == tmi->real_daddr && +                port == tmi->dport) { +           mi = tmi; +           break; +       } +   } +   spin_unlock_bh(&ovpn_lock); +    if (!mi) { +        goto out; +    } + +   skb_pull(skb, sizeof(struct udphdr)); +    +    /* decrypt  +     * 很显然,这是关键!数据解密! +     * 但是谁能告诉我内核中怎么高效使用加解密,如果不能高效, +     * 那么起码保证灵活,就像OpenSSL那样!进入了内核态,我突然 +     * 突然想到了OpenSSL的好,人,不能忘本啊  :< +     */ + +    /* 首先,判断是否是数据通道,进行例行检查,获取必要的密钥套件 */ +    if (ovpn_pre_endecrypt(O×××_OPT_DEC, tun, skb, mi)) { +       skb_push(skb, sizeof(struct udphdr)); +        goto out; +    } +    +    /* 实际的解密操作,注意在内部可能要进行skb的realloc操作 */ +    if (ovpn_endecrypt(O×××_OPT_DEC, tun, skb, mi)) { +       skb_push(skb, sizeof(struct udphdr)); +        goto out; +    } + +    /* 参考Open×××的post decrypt操作 */ +    if (ovpn_post_endecrypt(O×××_OPT_DEC, tun, skb, mi)) { +       skb_push(skb, sizeof(struct udphdr)); +        goto out; +    } + +    /* 解密完成,推进一个Open×××头的长度 */ +   skb_pull(skb, sizeof(struct ovpnhdr)); +   switch (tun->flags & TUN_TYPE_MASK) { +       case TUN_TUN_DEV: +            switch (skb->data[0] & 0xf0) { +                /* 当前只支持IPv4 */ +                case 0x40: +                    break; +                default: +                   skb_push(skb, sizeof(struct ovpnhdr)); +                   skb_push(skb, sizeof(struct udphdr)); +                   goto out; +                     +           } +           skb_reset_mac_header(skb); +            /* 是时候丢掉西装外衣了,口袋里的通行证会将你引入深渊, +             * 不信的话,注释此言,在Open×××客户端机器上ping一下 +             * 服务端的虚拟IP试一试  +             **/ +            skb_dst_drop(skb); +           skb->protocol = htons(ETH_P_IP);; +           skb->dev = tun->dev; +           ret = UDP_DECAP_STOLEN; +           break; +       case TUN_TAP_DEV: +           // TODO +           goto out; +           break; +   } +    /* 模拟TUN虚拟网卡接收,此时截获处理正式完成, +     * 告诉UDP,嗨,你的数据我已经帮你处理了  +     **/ +   netif_rx_ni(skb); +     +out: +   return ret; +} + +/* + * 封装UDP + * 本来想直接调用socket的sendto/sendmsg的,然而太过恶心与繁琐,加之需要skb和msg之间的拷贝 + * 为了省事而影响效率这样不值!还是自己封装吧,反正也不难 + **/ +static int encap_udp(struct sk_buff *skb, struct multi_instance *mi, unsigned int *pdlen) +{ +   struct udphdr *uh; +   struct inet_sock *inet = inet_sk(mi->sk); +   int len = *pdlen + sizeof(struct udphdr); +    +   skb_push(skb, sizeof(struct udphdr)); +   skb_reset_transport_header(skb); +    +   uh = udp_hdr(skb); +   uh->source = htons(inet->num); +   uh->dest = mi->dport; +   uh->len = htons(len); +   uh->check = 0; +    +    /* 注意这里有优化空间,ufo是否启用,硬件是否能帮我计算checksum呢?? */ +    uh->check = 0; +   uh->check = csum_tcpudp_magic(mi->real_saddr, mi->real_daddr, len, +                     mi->sk->sk_protocol, csum_partial(uh, +                                                        len,  +                                                        0)); +    +   return 0;    +} + +/*  + * IP层的封装与发送函数,注意,这里很不方便使用ip_queue_xmit  + **/ +static int encap_ip_xmit(struct sk_buff *skb, struct multi_instance *mi, struct iphdr *old) +{ +   struct iphdr *iph; +   struct dst_entry *dst; + +   skb_push(skb, sizeof(struct iphdr)); +    /* 如影随形 */ +   skb_reset_network_header(skb); +    +   iph = ip_hdr(skb); +   iph->version        =   4; +   iph->ihl        =   sizeof(struct iphdr)>>2; +   iph->frag_off       =   old->frag_off; +   iph->protocol       =   IPPROTO_UDP; +   iph->tos        =   old->tos; +   iph->daddr      =   mi->real_daddr; +   iph->saddr      =   mi->real_saddr; +   iph->ttl        =   old->ttl; +    /* 这个reroute频繁用于OUTPUT Netfilter HOOK,但问Rusty本人, +     * Netfilter的OUTPUT设计为何如何之好 */ +   if (ip_route_me_harder(skb, RTN_LOCAL)!= 0) { +       return -1; +   } +   dst = skb_dst(skb);  + +   ip_select_ident(iph, dst, NULL); +   return ip_local_out(skb); +} + + +static int encap_ovpn(struct sk_buff *skb, struct multi_instance *mi, int *pdlen) +{ +    struct tun_struct *tun; +    int ret = 0; + +    +    if (!mi) { +        ret = -1; +        goto out; +    } + +    tun = mi->sk->sk_user_data; +    if (!tun) { +        ret = -1; +        goto out; +    } + +    /* encrypt  +     * 很显然,这是关键!数据解密! +     * 但是谁能告诉我内核中怎么高效使用加解密,如果不能高效, +     * 那么起码保证灵活,就像OpenSSL那样!进入了内核态,我突然 +     * 突然想到了OpenSSL的好,人,不能忘本啊  :< +     */ + +    /* 首先,判断是否是数据通道,进行例行检查,获取必要的密钥套件 */ +    if (ovpn_pre_endecrypt(O×××_OPT_ENC, tun, skb, mi)) { +        ret = -1; +        goto out; +    } +    +    /* 实际的解密操作,注意在内部可能要进行skb的realloc操作 */ +    if (ovpn_endecrypt(O×××_OPT_ENC, tun, skb, mi)) { +        ret = -1; +        goto out; +    } + +    /* 如影随形 */ +   skb_push(skb, sizeof(struct ovpnhdr)); +    *pdlen += sizeof(struct ovpnhdr); + +    /* 参考Open×××的post decrypt操作 */ +    if (ovpn_post_endecrypt(O×××_OPT_ENC, tun, skb, mi)) { +        ret = -1; +       skb_pull(skb, sizeof(struct ovpnhdr)); +        goto out; +    } + +out: +   return ret;  +} + +/* + * hard_xmit中的封装函数,用于短路处理 + **/ +static int ovpn_data_channel_encap_xmit(struct tun_struct *tun, struct sk_buff *skb) +{ +   unsigned int max_headroom; +    int ret = 0; +   struct sock *sk; +   struct multi_instance *mi = NULL; +   struct hlist_node *node; +   struct iphdr *old_iphdr = NULL; +    unsigned int dlen = skb->len; + +   sk = tun->encap_sock; +   if (!sk) { +        ret = -1; +       goto out; +   } +   if (sk->sk_protocol != IPPROTO_UDP) { +        ret = -1; +       goto out; +   } +    +#define I_THINK_THIS_LENGTH_ENOUGH_BECAUSE_OF_XXX  40     +   max_headroom = (I_THINK_THIS_LENGTH_ENOUGH_BECAUSE_OF_XXX +  +                    LL_RESERVED_SPACE(tun->dev)         +  +                   sizeof(struct iphdr)                + +                   sizeof(struct udphdr)               + +                   sizeof(struct ovpnhdr)); + +   switch (tun->flags & TUN_TYPE_MASK){ +   case TUN_TUN_DEV: +   { +       struct iphdr *hdr = ip_hdr(skb); +       u32 addr = hdr->daddr; +       struct multi_instance *tmi; +       unsigned int hash = jhash_1word(addr, 0); + +       old_iphdr = hdr; +       spin_lock_bh(&ovpn_lock); +       hlist_for_each_entry(tmi, node, &tun->ctx.vhash[hash % MAX_HASH_BUCKETS], vhnode) { +           if (addr == tmi->daddr) { +               mi = tmi; +               break; +           } +       } +       spin_unlock_bh(&ovpn_lock); +   } +       break; +   case TUN_TAP_DEV: +   { +       // TODO +        ret = -1; +        +   } +       break; + +   }    +   if (!mi) { +        ret = -1; +       goto out; +   } +   if (skb_headroom(skb) < max_headroom || !skb_clone_writable(skb, 0)) { +       struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom); +       if (!new_skb) { +            ret = -1; +           goto out; +       } +        skb_dst_set(new_skb, skb_dst(skb)); + +       dev_kfree_skb(skb); +       skb = new_skb; +   } + +   if (encap_ovpn(skb, mi, &dlen)) { +        ret = 1; +        dev_kfree_skb(skb); +        goto out; +    } +    +   if (encap_udp(skb, mi, &dlen)) { +        dev_kfree_skb(skb); +        ret = 1; +        goto out; +    } +   /* GO AWAY?? 注意返回值转换 */ +   ret = encap_ip_xmit(skb, mi, old_iphdr); +    if (ret < 0) { +        ret = 1; +    } +out: +   return ret; +} +  static inline struct tun_sock *tun_sk(struct sock *sk)  {     return container_of(sk, struct tun_sock, sk); @@ -155,8 +851,38 @@  static void __tun_detach(struct tun_struct *tun)  { +    struct sock *sk;     /* Detach from net device */     netif_tx_lock_bh(tun->dev); +    /**/ +    sk = tun->encap_sock; +    if (sk) { +        int i; +       /* 重置操作 */ +       (udp_sk(sk))->encap_type = 0; +       (udp_sk(sk))->encap_rcv = NULL; +       sk->sk_user_data = NULL;     +       tun->encap_sock = NULL; +       tun->ctx.encap_xmit = NULL; +        for (i = 0; i < MAX_HASH_BUCKETS; i++) { +            struct multi_instance *mi; +            struct hlist_head *head; +            struct hlist_node *node, *tmp; +            head = &tun->ctx.hash[i]; +            hlist_for_each_entry_safe(mi, node, tmp, head, rhnode) { +                hlist_del(node); +               hlist_del(&mi->vhnode); +                if (mi->mi_destroy) { +                    mi->mi_destroy(mi/* THIS ? self ? Okey,thinking in JAVA */); +                } +                kfree(mi); +            } +        } +        /* 这里才减少引用计数!因为你并不晓得且不能假设tun和socket的关闭顺序 */ +        if (sk) { +            sockfd_put(sk->sk_socket); +        } +    }     tun->tfile = NULL;     netif_tx_unlock_bh(tun->dev); @@ -364,6 +1090,21 @@     if (!check_filter(&tun->txflt, skb))         goto drop; +   /* ?? */ +   if (tun->ctx.encap_xmit) { +         +       int ret = tun->ctx.encap_xmit(tun/*this就是那个叫做JAVA编程思想的!GEB之大成*/, skb); +        /* Is this Okay?I don't known */ +        /* Refer to the return value of UDP encap_rcv callback!*/ +       if (ret == 0) { +           /* encap_xmit drop skb*/ +           goto out; +       } else if (ret > 0) { +            goto out; +        } +       /* fall through */ +   } +     if (skb_queue_len(&tun->socket.sk->sk_receive_queue) >= dev->tx_queue_len) {         if (!(tun->flags & TUN_ONE_QUEUE)) {             /* Normal queueing mode. */ @@ -393,6 +1134,7 @@  drop:     dev->stats.tx_dropped++;     kfree_skb(skb); +out:     return NETDEV_TX_OK;  } @@ -467,6 +1209,7 @@         dev->tx_queue_len = TUN_READQ_SIZE;  /* We prefer our own queue length */         break;     } +    dev->priv_flags     &= ~IFF_XMIT_DST_RELEASE;  }  /* Character device part */ @@ -1140,7 +1883,7 @@     if (cmd == TUNSETIFF && !tun) {         ifr.ifr_name[IFNAMSIZ-1] = '\0'; -       ret = (tfile->net, file, &ifr); +       ret = tun_set_iff(tfile->net, file, &ifr);         if (ret)             goto unlock; @@ -1158,6 +1901,98 @@     ret = 0;     switch (cmd) { +        /* 这里的几个命令都是Open×××相关的 */ +        /* 但是我并不知道怎么将这些独立出去!*/ +   case TUNADDMILTI: +        { +            struct instance_req ir; +           if (copy_from_user(&ir, argp, sizeof(ir))) { +               ret = -EFAULT; +               break; +           } +            if (!ovpn_add_real_instance(tun, ir.real_addr, ir.port)) { +                ret = -EFAULT; +                break; +            } +        } +        break; +   case TUNSETMIVIP: +        { +            struct instance_vreq vir; +           if (copy_from_user(&vir, argp, sizeof(vir))) { +               ret = -EFAULT; +               break; +           } +            ovpn_add_virtual_instance(tun, vir.real_addr, vir.port, vir.vaddr); +        } +        break; +   case TUNDELMILTI: +        { +            struct instance_req ir; +           if (copy_from_user(&ir, argp, sizeof(ir))) { +               ret = -EFAULT; +               break; +           } +            ovpn_del_real_instance(tun, ir.real_addr, ir.port); +        } +        break; +   case TUNSETMKEY: +        { +            struct key_block *kb; +            /* 这里为何非要不在栈上分配呢? +             * 因为这里是内核,内核栈的大小是有限的,鉴于kb空间较大 +             * 因此采用了动态分配,用后释放 +             **/ +            kb = kmalloc(sizeof(struct key_block), GFP_KERNEL); +            if (!kb) { +                ret = -ENOMEM; +                break; +            } +           if (copy_from_user(kb, argp, sizeof(kb))) { +               ret = -EFAULT; +               break; +           } +            // TODO waht? find_set_key(tun, kb); +            kfree(kb); +        } +        break; +   case TUNGETMKEY: +        // TODO +        break; +   case TUNLINKO×××: +   { +       struct sockfd sfd; +       struct socket *sock; +       struct sock *sk; +       int err; +        int i; +       if (copy_from_user(&sfd, argp, sizeof(sfd))) { +           ret = -EFAULT; +           break; +       } +       sock = sockfd_lookup(sfd.fd, &err); +       if (!sock) { +           ret = -EFAULT; +           break; +       } +       sk = sock->sk; +       if (sk->sk_protocol != IPPROTO_UDP) { +           ret = -EFAULT; +           break; +       } +       (udp_sk(sk))->encap_type = UDP_ENCAP_O×××; +       (udp_sk(sk))->encap_rcv = ovpn_data_channel_decap_recv; +       /* link tun and sock ?? */ +       tun->encap_sock = sk; +       sk->sk_user_data = tun;  +       tun->ctx.encap_xmit = ovpn_data_channel_encap_xmit; +        for (i = 0; i < MAX_HASH_BUCKETS; i++) { +            INIT_HLIST_HEAD(&tun->ctx.hash[i]); +            INIT_HLIST_HEAD(&tun->ctx.vhash[i]); +        } +   }    +       break; +     case TUNGETIFF:         ret = tun_get_iff(current->nsproxy->net_ns, tun, &ifr);         if (ret)

将tun.c打上以上的patch后编译加载,然后修改Open×××代码:
1.在确保tun字符设备被打开以及socket创建之后触发TUNLINKO×××命令;
2.在multi_create_instance的时候触发TUNADDMILTI命令增加一个instance;
3.在向Open×××客户端推送TUN虚拟IP地址后触发TUNSETMIVIP;
4.在密钥协商完成后触发TUNSETMKEY命令。
注意,由于实际的加密/解密处理放在了第二部分,因此此时只能使用cipher none,auth none来进行测试。

上一篇:Linux进程管理优化及性能评估工具介绍
下一篇:没有了
网友评论