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

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

难度不大。

我又一次拿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本身并没有太大的关系,
+ * 只是想做一个简单的OpenVPN短路hack,仅此而已,我使用tun做修改是因为简单,
+ * 毕竟我只是需要将一个socket和tun联系起来,仅此而已,我需要做的就是短接
+ * UDP socket和tun网卡,仅此而已....  :)
+ *
+ * 数据通道进入内核的好处是显而易见的,多处理操作的效率由softirq分发系统决定,
+ * 而这个是简单的,在8核心处理器上,经过测试,使用Intel 82583多队列卡,按照
+ * tuple做hash中断分发,保持cache活性的基础上,也能首先OpenVPN协议的高速解析,
+ * 任何用户态的多线程架构与之相比都爆弱。但是此时问题浮现:
+ *
+ *  1.不是说内核态处理控制面而用户态处理数据面吗?对于OpenVPN,怎么反过来了啊,
+ *    有点懵了!是的,数据面放到用户态只善作个幻象,现如今不是还没有很好的实例嘛...
+ *    我并非说用户态多线程不好,只是对OpenVPN而言的,不信你试试。好了,在PF RING
+ *    还玩不转的时候,我只能这样,也不容易。
+ *  2.这里没有使用加密,接口是有了,但是没有高效的实现,我可不想OpenVPN成为Yet 
+ *    Another IPSec
+ * 
+ * 问题多多,[email protected],还是这个邮箱
+ *
+ **/
+
 #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

+/* 定义一个OpenVPN封装类型 */
+#define UDP_ENCAP_OVPN 20
+/* 连接一个UDP套接字和TUN网卡的ioctl命令 */
+#define TUNLINKOVPN   _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 OVPN_OPT_DEC    0
+#define OVPN_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作为一个例子的实现 */
+
+/*
+ * OpenVPN的常量定义,我是不是该准备一个头文件和C文件呢?
+ * 借用tun.c总不是什么长久之事!tun又不是只用于OpenVPN啊,
+ * 然而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];
+};
+
+/*
+ * 用于实现OpenVPN的防重放机制 
+ **/
+struct packet_id_send
+{
+   packet_id_type id;
+   time_t time;
+};
+
+/*
+ * 用于实现OpenVPN的防重放机制,但是天啊...里面的字段在协议移植阶段
+ * 是没有任何用武之地的,是的,没有用...
+ * */
+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;
+};
+
+/*
+ * 用于实现OpenVPN的防重放机制,目前的版本仅仅是为了例行公事,发送前
+ * 在OpenVPN头中封装一个递增的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 *);
+};
+
+/*
+ * 我的本意并不是移植OpenVPN,而是实现一个新的协议,but,but,but,but
+ * 苦于没有客户端,我为何不使用现成的OpenVPN呢??它的协议足够简单啊足够简单!
+ * */
+struct encap_context {
+   struct hlist_head hash[MAX_HASH_BUCKETS];
+   struct hlist_head vhash[MAX_HASH_BUCKETS];
+    /* 最终还是说服了自己,解除了OpenVPN和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);
+};
+
+/*
+ * 就是它!这就是OpenVPN协议的本质!瞧瞧看吧,你仅仅需要设置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 == OVPN_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 == OVPN_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 == OVPN_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 == OVPN_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(OVPN_OPT_DEC, tun, skb, mi)) {
+       skb_push(skb, sizeof(struct udphdr));
+        goto out;
+    }
+   
+    /* 实际的解密操作,注意在内部可能要进行skb的realloc操作 */
+    if (ovpn_endecrypt(OVPN_OPT_DEC, tun, skb, mi)) {
+       skb_push(skb, sizeof(struct udphdr));
+        goto out;
+    }
+
+    /* 参考OpenVPN的post decrypt操作 */
+    if (ovpn_post_endecrypt(OVPN_OPT_DEC, tun, skb, mi)) {
+       skb_push(skb, sizeof(struct udphdr));
+        goto out;
+    }
+
+    /* 解密完成,推进一个OpenVPN头的长度 */
+   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);
+            /* 是时候丢掉西装外衣了,口袋里的通行证会将你引入深渊,
+             * 不信的话,注释此言,在OpenVPN客户端机器上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(OVPN_OPT_ENC, tun, skb, mi)) {
+        ret = -1;
+        goto out;
+    }
+   
+    /* 实际的解密操作,注意在内部可能要进行skb的realloc操作 */
+    if (ovpn_endecrypt(OVPN_OPT_ENC, tun, skb, mi)) {
+        ret = -1;
+        goto out;
+    }
+
+    /* 如影随形 */
+   skb_push(skb, sizeof(struct ovpnhdr));
+    *pdlen += sizeof(struct ovpnhdr);
+
+    /* 参考OpenVPN的post decrypt操作 */
+    if (ovpn_post_endecrypt(OVPN_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) {
+        /* 这里的几个命令都是OpenVPN相关的 */
+        /* 但是我并不知道怎么将这些独立出去!*/
+   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 TUNLINKOVPN:
+   {
+       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_OVPN;
+       (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后编译加载,然后修改OpenVPN代码:
1.在确保tun字符设备被打开以及socket创建之后触发TUNLINKOVPN命令;
2.在multi_create_instance的时候触发TUNADDMILTI命令增加一个instance;
3.在向OpenVPN客户端推送TUN虚拟IP地址后触发TUNSETMIVIP;
4.在密钥协商完成后触发TUNSETMKEY命令。
注意,由于实际的加密/解密处理放在了第二部分,因此此时只能使用cipher none,auth none来进行测试。

时间: 2024-09-30 20:08:05

OpenVPN的Linux内核版,鬼魅的残缺 part I:The PROTOCOL的相关文章

OpenVPN的Linux内核版,鬼魅的残缺 part III rework with Netfilter

哥们儿拿到了juniper的offer,由衷祝福,酒足饭饱后的我,在Netfilter的路上却根本停不下来.已经是深夜,回忆这些年在Netfilter上的探索,结合目前的一些状况,突然觉得,既然我已经想把OpenVPN弄到内核了,那为何上一个鬼魅的实现没有使用Netfilter呢?当时我就觉得采用UDP的encap_rcv HOOK这种方式太鬼魅,又有些残缺,缺了什么却不知道,谁说没钱就不能任性,我当时可能就觉得:我就是不用Netfilter,我就是要用另一个方法!就好像上高中的时候,数学课上,

OpenVPN的Linux内核版,鬼魅的残缺 part II:The encrypt engine

一夜入冬啊一夜入冬,医院回来的路上已经困得不行了,不过还是仔细思考了我的OpenVPN内核版.和那些天天在微信朋友圈晒工作的相比我简直弱爆了,我 本应该说今天没有去上班,然后心里多么多么内疚的,什么耽误工作之类的,不过那不是我的style,就像我同样不喜欢在工位上贴大量的任务计划一样...       把本已不堪的OpenSSL移植到Linux Kernel,这真是一个疯狂的想法,极其疯狂的想法,过于疯狂的想法,以至于作为一个还算正常的人只好作罢!我需要的是使用现有的内核中的加密引擎或者 简单的

OpenVPN的Linux内核版,鬼魅的残缺 part IV:Normal Method

谁能在上海请我吃一顿烤全羊!又折腾了几乎个通宵...对于将OpenVPN弄进内核这件事,我已经找到了两个思路:1.使用UDP的encap rcv HOOK/tun xmit HOOK:2.使用Netfilter的PREROUTING HOOK分离控制通道和数据通道:但 是有个问题,那就是它们都仅仅对UDP有效,而OpenVPN虽不建议但是也是对TCP提供支持的.然而如果实现TCP模式的内核OpenVPN数据通道 处理,就必须在传输层之上来做,因为TCP必须要完成它自己的协议事务,比如确认,重传等

OpenVPN的Linux内核版,鬼魅的残缺 Prelude

首先向James yonan致敬!其次吐个槽,这都12月份了,还打雷,看来这股冷空气威力不是一般的大啊,大气环流和气压都被它改变了,我仿 佛看到了热空气和雾霾被这股异常强大的寒潮挤压得像难民一样背井离乡....这对于我而言,意义十分重大,从下周一开始,我将告别短袖T恤,穿上衬衫 了...再次重申,我这个时候穿短袖真的不冷,我能坚持到5摄氏度穿短袖,真的不冷,我在7摄氏度的时候偶尔穿一次长袖是因为压力实在太大... OpenVPN不好,代码简单却不好读,逻辑清晰却不好改,你明明知道该怎么改了,却迟

OpenVPN的Linux内核版,鬼魅的残缺 View

是时候给出一个总的图景了,是时候了.我的意思是说,为什么非要将OpenVPN移植到内核,而不是在用户态,即在它本身优化它.为什么呢?事实上我已经在用户态优化了它,虽然有些难以定位的segment fault,但是并不是说我没有时间没有能力搞定这些,知难而退到内核(退到内核碰到panic岂不是更难搞),不是这样的.我的本意是,且一直都是,我要缩短处理路径的长度,我从来都不相信什么软件神话,事实上我憎恨这些神话.正如一位网友所说,干嘛非要区分什么内核态,用户态!关键点不在哪个态处理效率高,关键在处理

关于linux内核2.4.xx 直接升级到 2.6.xx 版出现的问题

2.4.x 与 2.6.x 是两个具有相当大差异的核心版本, 两者之间使用到的函式库基本上已经不相同了,所以在升级之前,如果你的核心原本是 2.4.xx 版,那么就升级到 2.4.xx 版本的最新版,不要由 2.4.xx 直接升级到 2.6.xx 版,否则会出现很多未知的错误,直接导致系统不能使用. 关于linux内核2.4.xx 直接升级到 2.6.xx 版出现的问题,布布扣,bubuko.com

linux发行版和内核的关系

转自:http://m.blog.csdn.net/article/details?id=50595230 Linux内核是计算机操作系统的核心.一个完整的 Linux发行版包括了内核与一些其他与文件相关的操作,用户管理系统,和软件包管理器等一系列软件.每个工具都是整个系统的一小部分.这些工具通常都是一个个独立的项目,有相应的开发者来开发及维护. 前面提到的Linux内核,包括现行版本,以及历史版本(即更早发布的版本)都可以在 www.kernel.org 找到.Linux的众多发行版可能是基于

Linux内核LTS长期支持版生命周期

Longterm release kernels Version Maintainer Released Projected EOL 4.9 Greg Kroah-Hartman 2016-12-11 Jan, 2019 4.4 Greg Kroah-Hartman 2016-01-10 Feb, 2018 4.1 Sasha Levin 2015-06-21 Sep, 2017 3.18 Sasha Levin 2014-12-07 Jan, 2017 3.16 Ben Hutchings 2

Linux内核版本 uname命令 GNU项目 Linux发行版

1.内核版本由linux内核社区统一编码和发布,格式如下图: major.minor.patch-build.desc 主版本号.次版本号.对次版本号的修订次数-编译次数.当前版本的特殊信息 次版本号有奇数和偶数之分,奇数表示开发版,偶数表示稳定版 desc:常见参数EL(Red Hat企业版Enterprise Linux).pp(Red Hat测试版).fc(Red Hat中Fedorca Core).smp(对称多处理器).rc/r(候选版本,后面的数字越大越接近正式版) 2.uname