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
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。