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必须要完成它自己的协议事务,比如确认,重传等,所以就不能在传输层之下甚至PREROUTING上来做了。这
个想法和事实简直就是歌德尔的不完备性一样...但是在彻底否定之前,我想到,就算使用传输层以上的实现,也是可以利用PREROUTING
HOOK的成果的,当初这么做的时候,不就是想用PREROUTING来做短路的么?短在哪里?短在省略了路由查询,socket查
询,multi_instance查询,三个查询归为一个conntrack查询,这个成果难道不可以用吗?即便在BSD
socket层上处理逻辑,难道不可以将sk保存在conntrack里面吗?不要忘了tproxy就是这么个思路。
      
于是,一个统一的框架就此成型。使用PREROUTING
HOOK进行短路并不是将skb给STOLEN实现短路,而是对关键信息,即那些需要查找的信息进行record,保存在conntrack中,将来便于
restore,就像CONNMARK的save/restore一样,这也是一种短路方式,可能叫bypass比较合适。于是这就是一个基于传输层的统
一框架,可以同时支持TCP和UDP!现在的问题是,如何印证这种想法的可行性。这个时候能想到一个足够简化的方案是最好不过的,所有人都知道,我并没有
什么属于自己的时间,正如没有多少属于自己的钱一样...有了足够简化的模型,才能在忙里偷闲的夹缝时间片内完成所有的工作,要知道这种事一旦被打断就几
乎不可能再接上了。
       折腾了几个小时,出炉了一个基本实现,绝对简单,分为三个部分:
1.内核模块:运行一个内核模式的TCP服务器,收到数据后按照数据包的第一个字节的值区分是控制通道数据还是数据通道数据,如果是控制通道数据,则通过一个字符设备路由给用户态的一个进程,如果是数据通道数据,则直接在内核处理;
2.SSL服务端
将SSL协议作为控制通道数据,于是必须首先完成SSL握手,然后再在SSL纪录协议传输数据。我的这个SSL服务端并没有和socket有任何关联(如
果你认为SSL是安全socket层的话),也不和传输层有任何关系(如果你认为TLS是传输层安全的话),它只是一个运行在一对memory
BIO上面的SSL协议处理层,而这个memory下面则是一个pipe或者一个misc charactor device。
3.SSL客户端
来这个SSL客户端可以之间将SSL构建在socket之上的,但是我没有那么做,而是和SSL服务端一样,构建于一对memory
BIO之上,这是为了对SSL协议进行再封装,比如将SSL协议包前面加一个0x00字节就说明它是一个控制通道数据包,将一段裸数据前面加一个0x01
就说明它是一个数据通道数据包,此举用于让内核模块来区分。
这就是全部了!是的,这就是全部。代码应该可以体现出一切了,和往常一样,我将注释溶于代码,是因为代码没法自解释:

内核模块代码(处理数据通道数据,向用户态路由控制通道数据):

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/miscdevice.h>
#include <net/sock.h>

#define PORT 1234

static struct socket *kern_listen_socket;
static struct socket *kern_client_socket;
static int can_read = 0;
static int can_write = 0;
static unsigned char write_tmp[2048];
static unsigned char read_tmp[2048];
static unsigned char tmp[2048];

int receive_socket_data_in(int len)
{
    struct msghdr msg;
    struct kvec iov;
    int size = 0;

    msg.msg_name = 0;
    msg.msg_flags = 0;
    msg.msg_namelen = 0;
    msg.msg_controllen = 0;
    msg.msg_control = NULL;

    iov.iov_len = len;
    iov.iov_base = &read_tmp[0];

    size = kernel_recvmsg(kern_client_socket, &msg, &iov, 1, len, msg.msg_flags);

    return size;
}

int sent_socket_data_out(int len)
{
    struct msghdr msg;
    struct kvec iov;
    int size;

    msg.msg_name = 0;
    msg.msg_flags = 0;
    msg.msg_namelen = 0;
    msg.msg_controllen = 0;
    msg.msg_control = NULL;

    iov.iov_base = &write_tmp[0];
    iov.iov_len = len;

    size = kernel_sendmsg(kern_client_socket, &msg, &iov, 1, len);

    return size;
}

int data_channel_process(int size)
{
    /* 显示一下,然后返回 */
    /*
     * 正常的数据通道处理流程比这复杂得多
     */
    printk("DATA Channel recv:%s\n", read_tmp);
    memset(read_tmp, 0, sizeof(read_tmp));
    return 0;
}

int control_channel_process(int size)
{
    can_read = size;
    /*
     * 等到控制通道处理完毕
     */
    while (!can_write) {
        schedule_timeout(100);
    }

    /*
     * 控制通道数据由用户态产生
     */
    sent_socket_data_out(can_write);

    /*
     * 控制通道处理后处理
     */
    can_write = 0;
    memset(write_tmp, 0, sizeof(write_tmp));
    return 0;
}

/*
 * 如果仔细看下面的这段代码,会发现:
 * 它和用户态的socket编程是多么的相似!
 * 事实上,它们是一样的!
 *
 * 但是,我的本意并不是在内核用socket来编程的。
 * 那是什么呢?
 * 我的意思是直接截获带有socket数据的skb,这是为什么呢?
 * 这和直接socket编程有什么区别呢?
 * 答案很简单:消除copy的代价!
 * 如果截获skb,那么对于数据通道而言,就不必copy数据了,
 * 直接将skb进行加密/解密/封装...等操作,然后直接xmit或者
 * 别的什么都可以!
 *
 * 这种在传输层/BSD socket之间进行截获skb的风格和我之前的udp encap hook
 * 以及Netfilter的实现有什么区别呢?答案:
 * 1.如此实现可以实现TCP相关的部分,不再仅仅支持UDP;
 *   因为TCP只有到达传输层才能完成其协议事务。
 * 2.完全可以在PREROUTING HOOK上查找路由,socket;
 *   将socket在PREROUTING上设置给skb并和conntrack关联,这样同样可以避免传输层
 *   上面的socket查找,进而实现仅有的一次conntrack查找的优化。
 *
 *
 * 值得注意的是:
 *          我在内核中使用阻塞模式,因此顺序很重要,否则会造成挂起,但是我没有处理,
 *          因此只支持一种顺序,这个处理顺序和用户态的ssl_client的发送顺序是告诉相关的。
 *          比如,必须先发送数据通道的数据然后再发送控制通道数据。
 *          再次声明,我只是为了测试!我没有足够的时间...
 *
 */
static int main_loop(void)
{
    int ret = 0, size;
    struct sockaddr_in sin;

    /* 创建侦听socket */
    ret = sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &kern_listen_socket);
    if(ret < 0) {
        printk("Create socket error\n");
        goto out;
    }

    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);

    ret = kernel_bind(kern_listen_socket, (struct sockaddr*)&sin, sizeof(sin));
    if(ret < 0) {
        goto out;
    }

    ret = kernel_listen(kern_listen_socket, 5);
    if(ret < 0) {
        goto out;
    }

    kern_client_socket = kmalloc(sizeof(struct socket), GFP_KERNEL);
    while (1) {
        ret = kernel_accept(kern_listen_socket, &kern_client_socket, 0);
        if(ret < 0) {
            if (kern_client_socket) {
                kfree(kern_client_socket);
            }
            /* 
             * 如果模块卸载,listen socket会被release,
             * accept会返回,所以无需对kernel task作任何
             * kill动作,while循环也不需要设置条件变量
             */
            goto out;
        }
        printk("New client coming ......\n");

        /* 等待原有的用户态控制进程关闭misc设备 */
        while(can_read == -1 || can_write == -1) {
            schedule_timeout(100);
        }
        can_read = 0;
        can_write = 0;

        /*
         *  协议格式很简单:
         *  buff[0]:0-控制通道包
         *  buff[0]:1-数据通道包
         */
        printk("ready to receive data from client.\n");
        while ((size = receive_socket_data_in(sizeof(read_tmp))) > 0) {
            unsigned char type = 0;
            type = read_tmp[0];
            //type = 0;  //when test!! bypass
            /* 去掉协议头 */
            /*
             * 非得去掉协议头吗??
             * 最好的办法是:
             *              内核路径仅仅执行数据通道和控制的通道的执行流分离
             *              所有路由到用户态的控制数据封装/解封装操作均在用户
             *              完成,这样保持了最好的兼容性!
             *              当然了,数据通道的封装/解封装操作必须在内核态进行!
             */
            // 我能使用memmove吗?
            memcpy(tmp, &read_tmp[1], size-1);
            memset(read_tmp, 0, sizeof(read_tmp));
            memcpy(read_tmp, tmp, size-1);
            size = size - 1;
            if (type == 0x00) {
                /*
                 * 控制通道数据包通过字符设备/管道/Netlink等路由到用户态去处理
                 */
                if (!control_channel_process(size)) {
                    continue;
                }
            } else if (type == 0x01) {
                /*
                 * 数据通道数据包直接在内核被处理,它享有的大餐包括但不限于:
                 * 丰富的内核各个层次的网络包API,Netfilter,直接的硬件操作...
                 */
                if (!data_channel_process(size)) {
                    continue;
                }
            } else {
                /*
                 * 简单的echo??
                 */
            }
        }
        can_read = -1;
        can_write = -1;
        sock_release(kern_client_socket);
    }
    kfree(kern_client_socket);
out:
    return ret;
}

static ssize_t ctrl_chr_aio_write(struct kiocb *iocb, const struct iovec *iv,
                          unsigned long count, loff_t pos)
{
    ssize_t len = iov_length(iv, count);
    if (can_write == -1) {
        can_write = 0;
        return can_write;
    }
    if (memcpy_fromiovecend((void *)&write_tmp[0], iv, 0, len)) {
        return -EFAULT;
    }
    can_write = len;
    return len;
}

static ssize_t ctrl_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
                        unsigned long count, loff_t pos)
{
    ssize_t len, ret = 0;
    len = iov_length(iv, count);

    while(!can_read) {
        schedule_timeout(100);
    }

    if (can_read > 0) {
        memcpy_toiovecend(iv, (void *)&read_tmp[0], 0, can_read);
    }
    ret = can_read;
    can_read = 0;
    memset(read_tmp, 0, sizeof(read_tmp));

    return ret;
}

static int ctrl_chr_close(struct inode *inode, struct file *file)
{
    can_read = 0;
    can_write = 0;
    return 0;
}

static const struct file_operations ctrl_fops = {
    .owner  = THIS_MODULE,
    .read  = do_sync_read,
    .aio_read  = ctrl_chr_aio_read,
    .write = do_sync_write,
    .aio_write = ctrl_chr_aio_write,
    .release = ctrl_chr_close,
};

static struct miscdevice ctrl_miscdev = {
    .minor = 235,
    .name = "ctrl",
    .nodename = "ctrl",
    .fops = &ctrl_fops,
};

static int __init two_paths_init(void)
{
    int ret = 0;
    struct task_struct *tsk;
    ret = misc_register(&ctrl_miscdev);
    if (ret) {
        printk(KERN_ERR "misc: Can‘t register device %d\n", 235);
        goto out;
    }
    tsk = kthread_run((void *)main_loop, NULL, "IN-KERNEL-2-Path-Server");
    if (IS_ERR(tsk)) {
        ret = -1;
        goto out_unregister;
    }
    printk("Ready GO!\n");
    return ret;

out_unregister:
    misc_deregister(&ctrl_miscdev);
out:
    return ret;
}

static void __exit two_paths_exit(void)
{
    /* 
     * 难道不需要停止tsk吗?是的,不需要!
     *
     * 如果模块卸载,listen socket会被release,
     * accept会返回,所以无需对kernel task作任何
     * kill动作,while循环也不需要设置条件变量
     */
    if (kern_listen_socket != NULL) {
        kernel_sock_shutdown(kern_listen_socket, SHUT_RDWR);
        kern_listen_socket->ops->release(kern_listen_socket);
    }

    misc_deregister(&ctrl_miscdev);
    printk("Bye ...\n");
}

module_init(two_paths_init);
module_exit(two_paths_exit);

MODULE_AUTHOR("Marywangran <[email protected]>");
MODULE_DESCRIPTION("In kernel TCP server separating data/control channel");
MODULE_LICENSE("GPL");

SSL服务端代码(仅仅处理控制通道数据):

#define _GNU_SOURCE 

#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>

#define SO_REUSEPORT 15

static BIO *
getbio (BIO_METHOD * type, const char *desc)
{
    BIO *ret;
    ret = BIO_new (type);
    if (!ret) {
        printf("Error creating %s BIO", desc);
    }
    return ret;
}

static int
get_under_fd(short port)
{
    int sd, cli_sd;
    int ret = -1;
    int val = 1;
    struct sockaddr_in addr;
    struct sockaddr_in cliaddr;
    unsigned int clisize;

    if (port < 0) {
        /*
         * 首先你需要创建这个字符设备:
         * mknod /dev/ctrl c 10 235 
         */
        sd = open("/dev/ctrl", O_RDWR);
        if (sd == -1) {
            printf("Open MISC device error;\n");
            exit(1);
        }
        return sd;
    }

    /*
     * 直接使用socket的情况,在本例中不会用到,
     * 这只是我起初用于调试BIO时遗留的代码。
     */
    sd = socket(PF_INET, SOCK_STREAM, 0);
    if (sd == -1) {
        printf("Create socket error;\n");
        exit(1);
    }

    addr.sin_family = PF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(addr.sin_zero),0);

    setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));

    ret = bind(sd, (struct sockaddr *)&addr,sizeof(struct sockaddr));
    if (ret == -1) {
        printf("Bind error;\n");
        exit(1);
    }

    ret = listen(sd, 10);
    if (ret == -1) {
        printf("Listen error;\n");
        exit(1);
    }

    cli_sd = accept(sd,(struct sockaddr *)&cliaddr,&clisize);
    if (cli_sd == -1) {
        printf("Accept error\n");
        exit(1);
    }

    return cli_sd;
}

static SSL *get_ssl()
{
    SSL_CTX *ctx;
    SSL *ssl;
    DH *dh;

    SSL_library_init();
    SSL_load_error_strings();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();

    ctx = SSL_CTX_new(TLSv1_server_method());
    if(!ctx) {
        printf("Create SSL CTX error\n");
        exit(1);
    }

    /*
     * 我只希望SSL握手可以快速通过,所以采用了DH,因为我并不
     * 希望在生成证书等和本例不相关的事情上花费太多时间。  
     */
    dh = DH_new();
    DH_generate_parameters_ex(dh, 256, 2, NULL);
    DH_generate_key(dh);

    /*
     * 当然,我更不想在什么乱七八糟的算法上浪费哪怕一秒!  
     */
    SSL_CTX_set_cipher_list(ctx, "EXP-ADH-RC4-MD5");
    SSL_CTX_set_tmp_dh(ctx, dh);

    ssl = SSL_new(ctx);
    if(!ssl) {
            printf("Create ssl error\n");
            exit(1);
    }
    return ssl;
}

int main(int argc, char **argv)
{
    int ret = 0;
    int fd = -1;
    SSL *ssl;
    BIO *ct_in, *ct_out, *ssl_bio;
    /*
     * mode-1:使用内核socket处理I/O
     * mode-0:使用用户态socket处理I/O(调试时的代码,本例不采用)
     */
    int mode = 1;//atoi(argv[1]);
    char buffer[2048];
    int pipe_socket_to_mem[2];
    int pipe_mem_to_socket[2];
    unsigned short port;

    ct_in = getbio (BIO_s_mem (), "ct_in");
    ct_out = getbio (BIO_s_mem (), "ct_out");
    ssl_bio = getbio (BIO_f_ssl (), "ssl_bio");

    ssl = get_ssl();
    SSL_set_accept_state(ssl);

    /* 连接SSL和bio */
    SSL_set_bio (ssl, ct_in, ct_out);
    BIO_set_ssl (ssl_bio, ssl, BIO_NOCLOSE);

    port = mode?-1:1234;
    fd = get_under_fd(port);

    ret = pipe(pipe_socket_to_mem);
    if (ret == -1) {
        printf("Pipe error\n");
        exit(1);
    }
    ret = pipe(pipe_mem_to_socket);
    if (ret == -1) {
        printf("Pipe error\n");
            exit(1);
    }
    /*
     * 到此为止,SSL和底层的IO通道的连接已经建立好了,根据mode参数分为两种方式:
     * 用户态socket:
     *      WRITE:
     *      -->User Control data:产生控制通道数据
     *          -->SSL Object:例行SSL/TLS握手/记录协议封装
     *              -->memory BIO:给一个封装SSL/TLS的机会(类似OpenVPN协议)
     *                  -->pipe1:中间层处理(解除socket和memory BIO的耦合)
     *                      -->socket:例行socket send/sendto API调用
     *      ---------------Kernel/User bondary-------------
     *                      BSD socket layer
     *                      Network stack
     *                      .............
     *      READ:
     *                      .............   
     *                      Network stack
     *                      BSD socket layer
     *      ---------------Kernel/User bondary-------------
     *                      <--socket:例行socket recv/recvfrom API调用
     *                  <--pipe2:中间层处理(同WRITE)
     *              <--memory BIO:解除SSL/TLS外层封装
     *          <--SSL Object:例行SSL/TLS握手/记录协议解封装
     *      <--User Control data:获取控制通道数据
     *
     *
     * 内核态socket:
     *      WRITE:
     *      -->User Control data:产生控制通道数据
     *          -->SSL Object:例行SSL/TLS握手/记录协议封装
     *              -->memory BIO:给一个封装SSL/TLS的机会(类似OpenVPN协议)
     *                  -->pipe1:中间层处理(解除socket和memory BIO的耦合)
     *                      -->misc dev:写数据到一个字符设备
     *      ---------------Kernel/User bondary-------------
     *                          -->misc dev write
     *                          -->socket:例行socket send/sendto API调用
     *                      BSD socket layer
     *                      Network stack
     *                      .............
     *      READ:
     *                      .............   
     *                      Network stack
     *                      BSD socket layer
     *                          <--socket:例行socket recv/recvfrom API调用
     *                          <--misc dev read
     *      ---------------Kernel/User bondary-------------
     *                      <--misc dev:从一个字符设备读数据 
     *                  <--pipe2:中间层处理(同WRITE)
     *              <--memory BIO:解除SSL/TLS外层封装
     *          <--SSL Object:例行SSL/TLS握手/记录协议解封装
     *      <--User Control data:获取控制通道数据
     */

    /*
     * 下面就是依照以上示意图的I/O流程
     */
    bzero(buffer, 2048);
    ret = 1;
    while(1) {
        /* 从socket接收数据 */
        if (ret > 0) {
            ret = read(fd, buffer, sizeof(buffer));
            if (ret == 0 || ret == -1) {
                break;
            }
            printf("read %d bytes data from socket or misc device.\n", ret);
        }

        /* 写入SSL的read memory BIO */
        if (ret > 0) {
            ret = BIO_write(ct_in, buffer, ret);
            printf("write %d bytes data to memory in BIO direct(TEST! bypass the pipe).\n", ret);
        }
        bzero(buffer, sizeof(buffer));

        /* 从SSL读取数据 */
        ret = BIO_read(ssl_bio, buffer, sizeof(buffer));
        if (ret > 0) {
            printf("read %d bytes data from SSL Object.\n", ret);
            printf("Control channel data:%s.\n", buffer);

            /* echo回从SSL读到的数据 */
            ret = BIO_write(ssl_bio, "echo reply", 10);
            printf("write %d bytes data to SSL Object.\n", ret);
        }
        bzero(buffer, sizeof(buffer));

        /* 从SSL的write memory BIO读取数据 */
        ret = BIO_read(ct_out, buffer, sizeof(buffer));
        if (ret > 0) {
            printf("read %d bytes data from memory out BIO.\n", ret);
        }

        /* 写入pipe */
        if (ret > 0) {
            ret = write(pipe_mem_to_socket[1], buffer, ret < 0?0:ret);
            printf("write %d bytes data to pipe between memory out BIO and socket or misc device.\n", ret);
        }
        bzero(buffer, sizeof(buffer));

        /* 从pipe的另一端读取数据 */
        if (ret > 0) {
            ret = read(pipe_mem_to_socket[0], buffer, sizeof(buffer));
            printf("read %d bytes data from pipe between memory out BIO and socket or misc device.\n", ret);
        }

        /* 将数据写入socket */
        if (ret > 0) {
            ret = write(fd, buffer, ret < 0?0:ret);
            if (ret == 0 || ret == -1) {
                break;
            }
            printf("write %d bytes data to socket or misc device.\n", ret);
        } else {
            ret = 0;
        }
    }
    SSL_shutdown(ssl);
    close(fd);
    return 0;
}

SSL客户端代码(发送数据通道和控制通道数据):

#define _GNU_SOURCE 

#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>

#define SO_REUSEPORT 15

static BIO *
getbio (BIO_METHOD * type, const char *desc)
{
    BIO *ret;
    ret = BIO_new (type);
    if (!ret) {
        printf("Error creating %s BIO", desc);
    }
    return ret;
}

static int
get_under_fd()
{
    int sd;
    int val = 1;
    int ret = 0;
    struct sockaddr_in addr;

    sd = socket(PF_INET, SOCK_STREAM, 0);
    if (sd == -1) {
        printf("Create socket error;\n");
        exit(1);
    }
    setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));

    addr.sin_family = PF_INET;
    addr.sin_port = htons(1234);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    bzero(&(addr.sin_zero),0);

    ret = connect(sd, (struct sockaddr *)&addr,sizeof(struct sockaddr));
    if (ret == -1) {
        printf("Connect error;\n");
        exit(1);
    }

    return sd;
}

static SSL *get_ssl()
{
    SSL_CTX *ctx;
    DH *dh;
    SSL *ssl;

    SSL_library_init();
    SSL_load_error_strings();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();

    ctx = SSL_CTX_new(TLSv1_client_method());
    if(!ctx) {
        printf("Create SSL CTX error\n");
        exit(1);
    }

    SSL_CTX_set_cipher_list(ctx, "EXP-ADH-RC4-MD5");

    ssl = SSL_new(ctx);
    if(!ssl) {
            printf("Create ssl error\n");
            exit(1);
    }
    return ssl;
}

int main(int argc, char **argv)
{
    int ret = 0;
    int fd = -1;
    SSL *ssl;
    BIO *ct_in, *ct_out, *ssl_bio;
    int pipe_socket_to_mem[2];
    int pipe_mem_to_socket[2];
    char buffer[2048];
    char ctrl_buff[32];

    strcpy(ctrl_buff, "This is control data.");

    ct_in = getbio (BIO_s_mem (), "ct_in");
    ct_out = getbio (BIO_s_mem (), "ct_out");
    ssl_bio = getbio (BIO_f_ssl (), "ssl_bio");

    ssl = get_ssl();
    SSL_set_connect_state(ssl);

    SSL_set_bio (ssl, ct_in, ct_out);
    BIO_set_ssl (ssl_bio, ssl, BIO_NOCLOSE);

    fd = get_under_fd();
    ret = pipe(pipe_socket_to_mem);
    if (ret == -1) {
        printf("Pipe error\n");
        exit(1);
    }
    ret = pipe(pipe_mem_to_socket);
    if (ret == -1) {
        printf("Pipe error\n");
            exit(1);
    }

    bzero(buffer, sizeof(buffer));
    int i = 10;
    while(i) {
        /* 写入数据到SSL(第一次则触发SSL握手) */
        ret = BIO_write(ssl_bio, ctrl_buff, strlen(ctrl_buff));
        if (ret > 0) {
            printf("write %d bytes data to SSL Object.\n", ret);
        }

        /* 从SSL的write memory BIO读取数据 */
        ret = BIO_read(ct_out, buffer, sizeof(buffer));
        printf("read %d bytes data from memory out BIO.\n", ret);

        /*-----------封装控制报文协议头-----------*/
        {
            char tmp[2048] = {0};
            memcpy(tmp, buffer, ret);
            bzero(buffer, sizeof(buffer));
            // 控制报文 
            buffer[0] = 0x00;
            memcpy(&buffer[1], tmp, ret);
            ret = ret + 1;
        }

        /* 将数据写入pipe */
        ret = write(pipe_mem_to_socket[1], buffer, ret>0?ret:1);
        printf("write %d bytes data to pipe between memory out BIO and socket.\n", ret);
        bzero(buffer, sizeof(buffer));

        /* 从pipe的另一端读取数据 */
        ret = read(pipe_mem_to_socket[0], buffer, sizeof(buffer));
        printf("read %d bytes data from pipe between memory out BIO and socket.\n", ret);

        /* 首先发送一段数据通道的数据 */
        /*-----------封装数据报文协议头-----------*/
        {
            char tmp[32] = {0};
            unsigned int len, rv;
            strcpy(&tmp[1], "This is a datachannel DATA");
            // 数据报文 
            tmp[0] = 0x01;
            len = strlen(tmp);

            rv = write(fd, tmp, len);
            if (rv == 0 || rv == -1) {
                goto out;
            }
            printf("write %d bytes data(DATA Channel!) to socket.\n", rv);
        }

        /* 然后再发送控制通道的数据 */
        /* 将数据写入socket */
        ret = write(fd, buffer, ret);
        if (ret == 0 || ret == -1) {
            goto out;
        }
        printf("write %d bytes data to socket.\n", ret);
        bzero(buffer, sizeof(buffer));

        /* 从socket读取数据 */
        ret = read(fd, buffer, sizeof(buffer));
        if (ret == 0 || ret == -1) {
            goto out;
        }
        printf("read %d bytes data from socket.\n", ret);

        /* 将数据写入SSL read memory BIO */
        /* 
         * 这里缺失的是协议解封装,类似OpenVPN那样。由于我的内核模块仅仅
         * 用于测试目的,故没有执行协议封装的过程,因此这里也不再解封装
         *
         * 更好的办法是:
         *              内核路径仅仅执行数据通道和控制的通道的执行流分离
         *              所有路由到用户态的控制数据封装解封装操作均在用户
         *              完成,这样保持了最好的兼容性!
         */
        ret = BIO_write(ct_in, buffer, ret);
        printf("write %d bytes data to memory in BIO direct(TEST! bypass the pipe).\n", ret);
        bzero(buffer, sizeof(buffer));

        /* 从SSL读取数据 */
        ret = BIO_read(ssl_bio, buffer, sizeof(buffer));
        if (ret > 0) {
            printf("read %d bytes data from SSL Object.\n", ret);
        }
        i--;
    }
out:
    SSL_shutdown(ssl);
    close(fd);
    return 0;
}

这是一个Makefile

obj-m = kserv.o

all: client server kserver

runc: client                ./client

runs: server                ./server 

client: ssl_client.c                gcc ssl_client.c -o client -L/usr/local/ssl/lib -lssl -lcrypto -ldl

server: ssl_server.c                gcc ssl_server.c -o server -L/usr/local/ssl/lib -lssl -lcrypto -ldl

kserver: kserv.c        make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules

clean:                rm -rf server client kserv.ko modules.order .kserv.mod.o.cmd \                        kserv.mod.* .tmp_versions *.o .kserv.ko.cmd Module.symvers .kserv.o.cmd

时间: 2024-10-10 09:01:28

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

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内核版,鬼魅的残缺 Prelude

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

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

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

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

OpenVPN的多处理一直都是问题,但是作为轻量级VPN,这无所谓,但是如果你要将其作为重量级VPN来用,那就必须考虑了.       之前,我将OpenVPN分裂成了多线程版本,但是由于OpenVPN原本的buffer管理粒度就很粗,以至于我很难在多个线程中能够同时处理一个 multi_instance,所以我不得不采用一个multi_instance绑定一个线程的做法,为此还特意实现了一个自己版本的多队列TUN网卡 已经UDP的hash reuseport机制,这一切耦合太紧密,以至于牵一发

关于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