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的发送进行短接。这就是全部的框架,总图如下:
难度不大。
我又一次拿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#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include @@ -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(o***_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 o***hdr {+ 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 o***_destroy(struct multi_instance *mi)+{+ return;+}++/*+ * 根据一个IP地址和端口删除一个multi_instance+ **/+static void o***_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(&o***_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(&o***_lock);+ return ;+ }+ hlist_del(&mi->rhnode);+ hlist_del(&mi->vhnode);+ spin_unlock_bh(&o***_lock);+ kfree(mi);+}++/*+ * 添加一个multi_instance+ **/+static struct multi_instance *o***_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(&o***_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(&o***_lock);+ return tmi;+ }+ }+ ret = kzalloc(sizeof(struct multi_instance), GFP_ATOMIC);+ if (!ret) {+ spin_unlock_bh(&o***_lock);+ return NULL;+ }+ ret->dport = port; + ret->real_daddr = real_addr;+ ret->sk = tun->encap_sock;+ ret->mi_destroy = o***_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(&o***_lock);+ return ret;+}++/*+ * 为一个multi_instance添加一个虚拟IP地址,这个本来应该实现成一个虚拟交换机的+ * BUT对于TUN模式而言,我采用了替换模式,也就是说,我的这个版本并不支持iroute+ * 不支持又怎么样呢?早晚的事吧。希望,真心希望James Yonan不要打我哦。。。+ **/+static int o***_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(&o***_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(&o***_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(&o***_lock);+ return 0;+}++static int o***_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 o***_endecrypt(int mode, + struct tun_struct *tun, + struct sk_buff *skb,+ struct multi_instance *mi)+{+ + /* return tun->ctx.endecrypt(tun, skb); */+ return 0;+}++struct o***hdr *o***_hdr(struct sk_buff *skb)+{+ return (struct o***hdr*)(skb->data);+}++static int o***_post_endecrypt(int mode, + struct tun_struct *tun, + struct sk_buff *skb,+ struct multi_instance *mi)+{+ int ret = 0;+ struct o***hdr *ohdr;+ if (mode == O×××_OPT_ENC) {+ ohdr = o***_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 o***_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(&o***_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(&o***_lock);+ if (!mi) {+ goto out;+ }++ skb_pull(skb, sizeof(struct udphdr));+ + /* decrypt + * 很显然,这是关键!数据解密!+ * 但是谁能告诉我内核中怎么高效使用加解密,如果不能高效,+ * 那么起码保证灵活,就像OpenSSL那样!进入了内核态,我突然+ * 突然想到了OpenSSL的好,人,不能忘本啊 :<+ */++ /* 首先,判断是否是数据通道,进行例行检查,获取必要的密钥套件 */+ if (o***_pre_endecrypt(O×××_OPT_DEC, tun, skb, mi)) {+ skb_push(skb, sizeof(struct udphdr));+ goto out;+ }+ + /* 实际的解密操作,注意在内部可能要进行skb的realloc操作 */+ if (o***_endecrypt(O×××_OPT_DEC, tun, skb, mi)) {+ skb_push(skb, sizeof(struct udphdr));+ goto out;+ }++ /* 参考Open×××的post decrypt操作 */+ if (o***_post_endecrypt(O×××_OPT_DEC, tun, skb, mi)) {+ skb_push(skb, sizeof(struct udphdr));+ goto out;+ }++ /* 解密完成,推进一个Open×××头的长度 */+ skb_pull(skb, sizeof(struct o***hdr));+ 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 o***hdr));+ 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_o***(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 (o***_pre_endecrypt(O×××_OPT_ENC, tun, skb, mi)) {+ ret = -1;+ goto out;+ }+ + /* 实际的解密操作,注意在内部可能要进行skb的realloc操作 */+ if (o***_endecrypt(O×××_OPT_ENC, tun, skb, mi)) {+ ret = -1;+ goto out;+ }++ /* 如影随形 */+ skb_push(skb, sizeof(struct o***hdr));+ *pdlen += sizeof(struct o***hdr);++ /* 参考Open×××的post decrypt操作 */+ if (o***_post_endecrypt(O×××_OPT_ENC, tun, skb, mi)) {+ ret = -1;+ skb_pull(skb, sizeof(struct o***hdr));+ goto out;+ }++out:+ return ret; +}++/*+ * hard_xmit中的封装函数,用于短路处理+ **/+static int o***_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 o***hdr));++ 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(&o***_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(&o***_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_o***(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 (!o***_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;+ }+ o***_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;+ }+ o***_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 = o***_data_channel_decap_recv;+ /* link tun and sock ?? */+ tun->encap_sock = sk;+ sk->sk_user_data = tun; + tun->ctx.encap_xmit = o***_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来进行测试。