1. 程式人生 > >OpenVPN的Linux核心版,鬼魅的殘缺 part III rework with Netfilter

OpenVPN的Linux核心版,鬼魅的殘缺 part III rework with Netfilter

哥們兒拿到了juniper的offer,由衷祝福,酒足飯飽後的我,在Netfilter的路上卻根本停不下來。已經是深夜,回憶這些年在Netfilter上的探索,結合目前的一些狀況,突然覺得,既然我已經想把OpenVPN弄到核心了,那為何上一個鬼魅的實現沒有使用Netfilter呢?當時我就覺得采用UDP的encap_rcv HOOK這種方式太鬼魅,又有些殘缺,缺了什麼卻不知道,誰說沒錢就不能任性,我當時可能就覺得:我就是不用Netfilter,我就是要用另一個方法!就好像上高中的時候,數學課上,我總是能提出一個最簡便但卻多少有些YD的解法,當老師說這種方法沒法得分的時候,我就大喊:我還有一種更復雜的方法...然後那些學習不好但是成績卻很高的人們就有些不高興了,其實哪知道我真的是在有針對地嘲諷他們啊...現在好了,當那些人都穿著西裝不再跟我玩這種幼稚的遊戲的時候,我依然在跟自己玩,只是我是自己嘲諷自己而已。
       整個城市,向黑暗中退去,你我都放棄忍耐!
       我將OpenVPN順利移植進了核心,然而卻對tun.c做了比較大的手術,在那個實現中,我借用了一個udp_encap_rcv鉤子點,然後在tun驅動的xmit函式中增加了一個新的鉤子點,兩個鉤子點對接完成了OpenVPN資料通道處理流程的短路。然而仔細想想,總覺得哪裡不妥,所謂的優化無極限並非僅僅針對性能,還針對美感,如果自己都覺得它醜陋無比,那麼它就一定可以換種方式重新實現。
       在《OpenVPN的Linux核心版,鬼魅的殘缺 part I:The PROTOCOL
》中,那種短路方式確實繞開了OpenVPN這個大電阻,但是在udp的encap_rcv增加了一個通過real source address以及real source port為主鍵查詢multi_instance的過程,我們一向都認為這個過程是繞不開,因此它作為一個小電阻存在在那裡。我們還知道,Linux的核心協議棧將所有的同一種協議的socket鏈在了一張hash表中,每一個到達本地的資料包都要根據4元組在這個hash表中檢索到唯一的一個socket並和skb本身關聯起來,因此這個查詢過程是絕對繞不開的,這不是說我認為它繞不開,而是socket層的例行過程。那麼,資料包是怎麼知道自己要到達本地的呢?很顯然,還有一種絕對繞不開的過程,那就是路由查詢,當然這個絕對也並不絕對,因為存在很多的路由板卡,專門在鏈路層cache路由結果,當然這是後話,也並不是每一臺裝置都擁有這種板卡。到目前為止,我總結一下OpenVPN核心版本的資料通道處理流程的三次查詢操作:
1.路由查詢:
決定了資料包是到達本地的,這是這個資料包是OpenVPN資料通道資料包的前提;
2.UDP的socket查詢:這是這個資料包是OpenVPN資料通道資料包的另一個前提;
3.multi_instance查詢:這是一個短路操作的查詢,原版的OpenVPN的這個查詢在multi.c檔案的一個multi_process_incoming_link函式中。
好了,在我描述了以上的3個所謂的必不可少的查詢之後,接下來就是我的風格展示了,做掉它們!我並不認為會有奇蹟,我也同樣不相信程式可以智慧到自己尋找加密引數,那麼查詢肯定是必不可少的了,問題是,如何將查詢次數降低到最少!我的方式是,我的方式還是Netfilter,一個極端優秀的框架!優秀到多麼極端的程度僅僅和你的想象力有關!我沒有發明什麼新的東西,而是使用了conntrack!然後將所有的資訊都儲存在conntrack的extend中,這種方式是我一直都在思考的,並且還真的用到了工作中,這是絕對值得欣慰的。
       關於ip conntrack,我已經不想再多說,之前的文章已經說了不少,我對它的掌控雖談不上出神入化但也稍微有點說到它就排他了...conntrack可謂說是“一次慢速的多層查詢以建立快速通道”的絕佳例項,因此唯一耗時的操作過程僅僅在第一次建立conntrack結構體的時候,在這個慢速的過程中,所有的查詢結果都可以儲存在conntrack中,雖然Linux的ip conntrack中沒有所謂的private_data之類的void指標,但是卻可以通過extend機制去擴充套件,此後所有的資訊都可以在conntrack結構體的對應extend中直接查到,因此,所有的查詢操作都可以歸結為單一的conntrack查詢操作了。至於conntrack查詢的效率,我可以說它非常不高,也非常影響效能,但是正因為如此,我才會說它還有很大優化空間,至於如何優化,我曾經寫過一個方法,那就是多張conntrack hash表,典型地可以是按照協議來區分不同的conntrack hash表,從一個skb中取到傳輸層協議是再簡單不過的事了,如果每一種協議建立一張表,豈不是很好的思路?!
       好了,現在假設你已經明白我的大致思路,那麼具體的實現該如何呢?那當然最簡單不過了,解密流程如下:

0.conntrack建立:我們要明白,OpenVPN的資料通道和控制通道只是應用層的區分,因此它們共享一個conntrack結構體,在資料通道啟用之前,控制通道已經建立(SSL握手完成),控制通道的建立意味著和它關聯的conntrack結構已經被confirm了。
1.資料包攔截:PREROUTING上在CONNTRACK優先順序之後建立一個OpenVPN HOOK,攔截OpenVPN的資料通道的skb。此時取出skb的conntrack結構體,進一步取出自定義的ovpn extend;
2.資料包解密:所有的資訊都在ovpn extend中,包括加密金鑰和解密金鑰,此時我們需要的是解密金鑰。將資料包進行常規檢查(IP層校驗已經在PREROUTING前做過,因此此處只要校驗UDP相關的即可)之後,脫掉UDP/IP西服,然後對其進行解密解封裝,OpenVPN校驗,包括replay攻擊校驗等,最終露出一個內層被封裝的IP資料報文(目前我沒有實現TAP mode);
3.短路到tun網絡卡:這個步驟比較複雜,分為下面的子步驟:
3.0.提出一個問題:第2步最終的skb此時其實已經可以被tun網絡卡接收了,即模擬一個netif_rx_ni呼叫。但是且慢!人無遠慮必有近憂!我們要考慮屬於此時skb的conntrack的返回包回來的時候如何被加密,即按照我最初的思路,需要一個extend和此時的內層skb的conntrack關聯起來,問題是如何拿到該conntrack,如果在tun的netif_rx之後自然到達PREROUTING之後拿到,我們將丟失內層skb和外層skb之間的關聯。必須另想它法。注意,此時我們依然在外層skb的PREROUTING的OpenVPN HOOK中;
3.1.獲取內層skb的conntrack:在不逃出這個外層skb的OpenVPN HOOK的位置,手動讓內層skb到PREROUTING中溜達一圈,一直溜達到它的conntrack被指定為止。其實懂行的都知道,過了ipv4_conntrack_in這個函式,它的conntrack就被指定了。注意,此時我們依然在外層skb的PREROUTING的OpenVPN HOOK中;
3.2.設定內層skb conntrack的extend:如果發現內層skb的conntrack OpenVPN extend已經被設定,就直接通過,否則就設定它,注意,此時我們依然在外層skb的PREROUTING的OpenVPN HOOK中,所以很容易關聯內層skb conntrack OpenVPN extend以及外層skb conntrack OpenVPN extend;
3.3.模擬tun網絡卡接收:這是真正的短路操作。在tun資料包接收了內層skb之後,總是還是會到PREROUTING的ipv4_conntrack_in中的,但是無所謂,不會被執行的,會直接退出,這是因為它的conntrack已經有了,previous seen??
4.正向短路至此完成:怎麼慶祝一下呢?最好的方法就是順利讓內層skb經過路由,local deliver或者forward,放走它,並等待和它同屬一個conntrack的另一個skb的返回!
緊接著,我們看一下加密流程:
0.反向資料包的返回:我等待的資料包終於返回了,好的,這正是我想要的!
1.資料包攔截:PREROUTING上在CONNTRACK優先順序之後已經建立了一個OpenVPN HOOK,取出skb的conntrack之後,發現其擁有OpenVPN的extend,其中的資訊正是正向解密流程的第3.2步驟建立的,此時我敢保證,這個skb就是需要加密的,好的;但是且慢,如果沒有找到conntrack的OpenVPN extend怎麼辦?這說明這個conntrack是從OpenVPN服務端方向主動發起的,那麼這時就要進入慢速路徑了,因為短路操作所需的資訊不足。所謂的慢速路徑就是依舊和往常一樣經由IP路由將其發往tun虛擬網絡卡,然後是字元裝置,最後被OpenVPN使用者態程序讀取,加密,然後寫入socket...
2.資料包加密:用第1步中的OpenVPN extend資訊中的加密資訊對該skb進行封裝,加密以及HMAC處理。具體過程和OpenVPN中的幾乎一樣;
3.資料包外層封裝:從第1步中查到的OpenVPN extend獲取連線資訊,即外層skb的UDP 4元組資訊,將其進行UDP封裝,IP封裝,然後直接呼叫ip_local_out將其發出,這是反向加密流程的短路操作;
4.反向短路至此完成:怎麼慶祝一下呢?...
至此,核心中的所有的操作均已經介紹完畢,那麼核心中的所謂的OpenVPN extend資訊是什麼時候注入進去的呢?以下是一些個注入點:
1.multi_instance建立的時候:此時OpenVPN已經接收到了RESET,但是SSL握手或者password驗證等還沒有完成,金鑰協商還未完成,但是五元組已經確定,這個point可以注入的資訊有:該conntrack確實是路由到本地OpenVPN程序的;tun網絡卡的資訊;
2.金鑰協商完成:此時的幾個金鑰對均已經協商完成,包括加密金鑰,解密金鑰,HMAC相關的金鑰...這個point可以注入的資訊有:金鑰對資訊;金鑰操作方向資訊等;
所有的注入操作我並沒有用ioctl,因為這樣需要建立一個字元裝置,或者修改tun驅動或者建立一個socket,而我不想汙染tun,也不想汙染devfs,其實ioctl本身就是一種帶有京味的汙染源,於是我採用了netlink,如果是在指令碼中操作,我則更喜歡選擇procfs。
       寫到這裡,發現文字描述真的太累,任何文字在示意圖和程式碼面前都是蒼白的,這就是程式設計師為何喜歡寫程式碼或者畫圖的根本原因,別跟程式設計師扯理論,先把程式碼跑起來再說吧。其實,看了GEB之後明白了一個真理,為何人對圖的敏感度要優於文字,因為圖是一個二維或者三維的being,而文字則是一個抽象的一維線索,必須序列處理,也就是說,理解文字意味著你的大腦需要不斷進行壓棧,彈出操作,而理解圖片則真正的可以動用大腦的並行處理優勢!我將上面的一大堆文字表示成一個圖:


當然了,我在圖上寫下了太多的文字,以至於顯得很亂,但是如果不加那些文字,可能會更亂,因為Netfilter的效果就是你可以“在任何地方將skb帶到任何地方”,兩個任何現實了“亂”的精髓,但是也同樣表達了,真正的出神入化就是“亂得可以”。好了,如果連圖都覺得很亂,那麼就跑程式碼吧,在給出程式碼之前,先給出用法:
1.啟動一個OpenVPN服務端:cipher設定為none,auth設定為none,你知道,如果協議跑通了,這些都不是事兒,畢竟那些演算法只是獨立的另一個庫實現,相當獨立。載入nf_ovpn_helper.ko,即可
2.啟動OpenVPN客戶端:cipher設定為none,auth設定為none
3.在虛擬IP上跑一些資料:比如用iperf試試看...panic?oh no!
4.開啟真正的debug之旅...

程式碼和圖一樣,依然給出了大量的文字說明,其分量和程式碼差不多...程式碼如下:

/*
 * 整個城市向黑暗中退去,你我都放棄忍耐
 */
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/ip.h>
#include <linux/inet.h>
#include <net/net_namespace.h>

#include <net/netfilter/nf_conntrack.h>

#include "ovpn_func.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wangran <[email protected]>");
MODULE_DESCRIPTION("OpenVPN connection helper");
MODULE_ALIAS("ip_conntrack_ovpn");
MODULE_ALIAS_NFCT_HELPER("ovpn");

/* 
 * 此埠難道就這麼寫死成1194嗎?難道不是需要註冊的嗎 :(
 **/
#define OVPN_PORT	1194

struct ovpn_instance {
	__be32 saddr;
	__be32 daddr;
	__be16 sport;
	__be16 dport;
	__be32 packet_id;
    /* 請恕我這麼寫,反正沒有實際實現 */
	unsigned char *info[0];
};

/* fake struct nf_conn_counter*/
struct instance_info {
	u_int64_t i;
	u_int64_t j;
	__be32 saddr;
	__be32 daddr;
	__be16 sport;
	__be16 dport;
	__be32 packet_id;
};

/* 真實的packet_id應該從慢速路徑或者控制路徑的Netlink訊息傳遞到conn的extend中 */
__be32 static_fake_packet_id = 0;

static int ovpn_nf_pre_routing(struct sk_buff *skb)
{
	/* nothing to do */
	return NF_ACCEPT;
}

/* 第二個引數真的需要嗎,因為加密所需要的cipher_info以及封裝需要的udp頭,ip頭資訊全部
 * 而不是部分地儲存在了conntrack info資訊中了啊啊啊
 *
 * 注意:這裡的引數並不符合規範,因為我盜取了heler的結構體,但是思想是一致的!
 **/
static int encap_xmit(struct sk_buff *skb, struct ovpn_instance *ovpn)
{
	/*
	 * 正如我一貫的風格,我總是無情推翻昨天的自大,將歡樂瞬間變成悲哀
	 * 我沒有呼叫ovpn_data_channel_encap_xmit這個tun網絡卡的HOOK函式,因為
	 * 我覺得太垃圾了,其實我正在逐步還原tun.c,就像這次一樣,我力圖使用
	 * Netfilter進行短路操作,而不再觸控tun.c以及UDP socket。就像你看到
	 * 的那樣,你依然可以載入系統自帶的原生態tun.ko
	 **/
	int ret = NF_DROP;	
	int copy = 0;
	unsigned int max_headroom;
	struct sk_buff *skb_to_encap;
	__be32 saddr = ovpn->saddr;
	__be32 daddr = ovpn->daddr;
	__be16 sport = ovpn->sport;
	__be16 dport = ovpn->dport;
	__be32 packet_id = ovpn->packet_id;
	struct iphdr *old = ip_hdr(skb);
	/* but 怎麼判斷OpenVPN是UDP的 
     * 很簡單,一切都在nf_conn的extend中,
     * 只是,我這裡沒有寫而已!
     **/

#define I_THINK_THIS_LENGTH_ENOUGH_BECAUSE_OF_XXX  78    
	max_headroom = (I_THINK_THIS_LENGTH_ENOUGH_BECAUSE_OF_XXX +  
				    sizeof(struct iphdr)                +
				    sizeof(struct udphdr)               +
				    sizeof(struct ovpnhdr));

    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) {
            goto out;
        }
        skb_dst_set(new_skb, skb_dst(skb));

        skb_to_encap = new_skb;
        copy = 1;
    } else {
        skb_to_encap = skb;
	}
	/* ##################### encap OpenVPN #################### */
	{
        struct ovpnhdr *ohdr;
		skb_push(skb_to_encap, sizeof(struct ovpnhdr));
	    ohdr = ovpn_hdr(skb_to_encap);
		/* 慢速路徑的packet_id必須反映到快速路徑中來!! */
	    ohdr->id = htonl(packet_id);
	    ohdr->ocode = (P_DATA_V1 << P_OPCODE_SHIFT) | 0x0;
	}
	/* ##################### encap UDP #################### */
	{
		struct udphdr *uh;
	
		skb_push(skb_to_encap, sizeof(struct udphdr));
		skb_reset_transport_header(skb_to_encap);

		uh = udp_hdr(skb_to_encap);
		uh->source = sport;
		uh->dest = dport;
		uh->len = htons(skb_to_encap->len);
		uh->check = 0;
		uh->check = csum_tcpudp_magic(saddr, daddr, skb_to_encap->len,
								IPPROTO_UDP, csum_partial(uh,
						                                   skb_to_encap->len, 
							                                0));
	}
	/* ##################### encap IP #################### */
	{
		struct iphdr *iph;
		struct dst_entry *dst;

		skb_push(skb_to_encap, sizeof(struct iphdr));
		skb_reset_network_header(skb_to_encap);
		iph = ip_hdr(skb_to_encap);
		iph->version		=	4;
		iph->ihl		=	sizeof(struct iphdr)>>2;
		iph->frag_off		=	0;//old->frag_off;
		iph->protocol		=	IPPROTO_UDP;
		iph->tos		=	old->tos;
		iph->daddr		=	daddr;
		iph->saddr		=	saddr;
		iph->ttl		=	old->ttl;
		/* 這個reroute頻繁用於OUTPUT Netfilter HOOK,但問Rusty本人,
		 * Netfilter的OUTPUT設計為何如何之好 */
		if (ip_route_me_harder(skb_to_encap, RTN_LOCAL)!= 0) {
			/* 無論如何都要STOLEN的 */
            if (copy) {
			    kfree_skb(skb_to_encap);	
            }
			goto out;
		}
		dst = skb_dst(skb_to_encap);	

		ip_select_ident(iph, dst, NULL);
	}
	ip_local_out(skb_to_encap);
    /* 偷走資料包,不再在曾經的路上繼續 */
    ret = NF_STOLEN;
out:
	return ret;

}

static unsigned int ipv4_ovpn_in_local( unsigned int hook,
					struct sk_buff *skb,
					const struct net_device *in,
					const struct net_device *out,
					int (*okfn)(struct sk_buff *))
{
	int ret = NF_ACCEPT;
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
	struct net_device *dev = NULL;
	struct tun_struct *tun = NULL;

	ct = nf_ct_get(skb, &ctinfo);
	if (!ct) {
		goto out;
	}
	if (ct == &nf_conntrack_untracked) {
		goto out;
	}
	/* 注意,這個dev不該這麼寫死,應該從extend中獲取,但是debug之旅如此奇幻,
	 * 你難道不想試試看嗎?
	 */
	dev = dev_get_by_name(&init_net, "tun0");
	if (!dev) {
		goto out_not_put;
	}

	tun = netdev_priv(dev);;
	if (!tun) {
		goto out;
	}

	if (out && dev == out) {	
		/* 
		 * 這裡要取出儲存在conn中的所有資訊,包括加密金鑰
		 */
        /*
		 * cipher_info = (struct instance_info *)nf_conn_acct_find(ct);
		 * if (cipher_info != NULL) {  
		 * 	if (cipher_info->saddr != 0 &&
		 *			cipher_info->daddr != 0) {
         */
				struct ovpn_instance ovpn;
				ovpn.saddr = 0x32c7a8c0;/*cipher_info->daddr;*/
				ovpn.daddr = 0xe9c7a8c0;/*cipher_info->saddr;*/
				ovpn.sport = 0xaa04;/*cipher_info->dport;*/
				ovpn.dport = 0xaa04;/*cipher_info->sport;*/
				++ static_fake_packet_id;/*cipher_info->packet_id;*/
				ovpn.packet_id = static_fake_packet_id;/*cipher_info->packet_id;*/
				ret = encap_xmit(skb, &ovpn);
				goto out;	

	} 
out:
    dev_put(dev);
out_not_put:
    return ret;
}
static unsigned int ipv4_ovpn_in( unsigned int hook,
				struct sk_buff *skb,
				const struct net_device *in,
				const struct net_device *out,
				int (*okfn)(struct sk_buff *))
{
	int ret = NF_ACCEPT;
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
	struct iphdr *hdr = ip_hdr(skb);
	struct udphdr *uh;
	struct net_device *dev = NULL;	
	struct tun_struct *tun = NULL;
	__be32 saddr, daddr;
	__be16 sport, dport;
	int dir;

	ct = nf_ct_get(skb, &ctinfo);
	if (!ct) {
		goto out;
	}
	if (ct == &nf_conntrack_untracked) {
		goto out;
	}
	/* 注意,這個dev不該這麼寫死,應該從extend中獲取,但是debug之旅如此奇幻,
	 * 你難道不想試試看嗎?
	 */
	dev = dev_get_by_name(&init_net, "tun0");
	if (!dev) {
		goto out_not_put;
	}

	if ((in && in == dev) || (in && in == init_net.loopback_dev)) {
		goto out;
	}

	tun = netdev_priv(dev);;
	if (!tun) {
		goto out;
	}

	switch (tun->flags & TUN_TYPE_MASK) {
	case TUN_TAP_DEV:
		goto out;
	}

	saddr = hdr->saddr;
	daddr = hdr->daddr;

	/* 到達此處的資料包有以下幾類:
	 *	1.正方向的UDP到將欲到達OpenVPN的資料包
	 *		1.1.控制通道資料包
	 *			這類資料包將最終穿過INPUT,完成conntrack的confirm,至此conntrack建立
	 *		1.2.資料通道的資料包
	 *			這類資料包就是我要截獲,解密,進而STOLEN的。This is it!!!
	 *	2.從OpenVPN程序socket發出的資料包
	 *		2.1.控制通道資料包
	 *			這類資料包來自OpenVPN程序,用於SSL握手以及PING(keepablive)
	 *		2.2.資料通道資料包
	 *			這類資料包本來來自OpenVPN程序,由其加密,但是由於它們將在tun的xmit中被截獲自行進行OpenVPN/UDP/IP封裝,
	 *			因此並不會到達此處,也可以在OUTPUT/PREROUTING中被識別並自行進行OpenVPN/UDP/IP封裝並被STOLEN到dev_queue_xmit
	 *			......
	 *
	 **/
	dir = CTINFO2DIR(ctinfo);
	if (dir != IP_CT_DIR_ORIGINAL) {
		goto check_encap_xmit;
	}
	/* 此處沒加鎖啊沒加鎖!!! */
	if (hdr->protocol != IPPROTO_UDP) {
	/*
	 * 這裡徹底呈現了UDP的優勢
	 * 你可能不信!但是如果是TCP,你將不能在中間任何地方截獲(STOLEN)資料!
	 * 因為TCP是端到端流協議,你要是截獲了資料,怎麼傳送回執??
	 * 你沒法ACK資料,TCP將不再繼續!除非...
	 * 除非你連ACK也偽造!連帶的,你難道要自己實現TCP的語義?? 
	 **/
		goto check_encap_xmit;
	}

	skb_pull(skb, ip_hdrlen(skb));
	skb_reset_transport_header(skb);
	/* 此處省略了UDP接收的例行校驗檢查 */
	uh = udp_hdr(skb);
			
	if (uh->dest != htons(OVPN_PORT)) {
		skb_push(skb, ip_hdrlen(skb));
		skb_reset_network_header(skb);
		goto check_encap_xmit;
	}
	sport = uh->source;
	dport = uh->dest;
	{
		/*
		 *  這裡要取出儲存在conn中的所有資訊,包括解密金鑰
		 *	ct_inner = nf_ct_get(skb, &ctinfo_inner);
		 *	cipher_info = nf_conn_acct_find((const struct nf_conn *)ct_inner);
		 *	if (cipher_info == NULL) {  
		 *		...
		 *		...
		 *
		 */
	}	
    /* decrypt 
     * 很顯然,這是關鍵!資料解密!
     * 但是誰能告訴我核心中怎麼高效使用加解密,如果不能高效,
     * 那麼起碼保證靈活,就像OpenSSL那樣!進入了核心態,我突然
     * 突然想到了OpenSSL的好,人,不能忘本啊  :<
     */
	/*
	 *  以上是我在udp_encap_rcv版本中的註釋!!但是,但是
	 *  天啊!饒恕我的貪婪吧!
	 *  在nf_conntrack_helper版本中,我連封裝的力氣都沒有了,為了儘快驗證,
	 *  我將程式碼寫死!
	 *  解密演算法:AES-128-ECB
	 *  解密金鑰:128位的0!
	 */
	/* ################################################################### */
	/* 驗證伊始,推進一個udp頭 */
	skb_pull(skb, sizeof(struct udphdr));
	{
		/* PRE Decrypt--對齊資料,驗證操作碼 */
	    u8 *data = skb->data;
	    u8 ocode = data[0];
	    int op = ocode >> P_OPCODE_SHIFT;
	    if (op != P_DATA_V1) {
			skb_push(skb, sizeof(struct udphdr));
			skb_push(skb, ip_hdrlen(skb));
			skb_reset_network_header(skb);
			skb_reset_transport_header(skb);
		    goto out;		
	    }
	}
	/* ################################################################### */
	{
		/* Decrypt--呼叫核心介面解密資料 */
        /*int i;
		struct crypto_cipher *tfm;
		unsigned char key1[16] = {0};
        unsigned char *data;

		tfm = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC); 
		if (!tfm) {
			return NF_DROP;
		}
		crypto_cipher_setkey(tfm, (const u8 *)&key1[0], 16);
        data = skb->data + 1;
        for (i = 0; i < skb->len - 1; i += crypto_cipher_blocksize(tfm)) {
            crypto_cipher_decrypt_one(tfm, data + i, data + i);
        }       
		crypto_free_cipher(tfm);
		*/
		/* 解密完成,推進一個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:
			/* 解密發現不是IPv4,不再恢復skb指標 */
			ret = NF_DROP;
            goto out;
                    
        }
        skb_reset_mac_header(skb);
		skb_reset_network_header(skb);
		skb_reset_transport_header(skb);
            /* 是時候丟掉西裝外衣了,口袋裡的通行證會將你引入深淵,
             * 不信的話,註釋此言,在OpenVPN客戶端機器上ping一下
             * 服務端的虛擬IP試一試 
             **/
        skb_dst_drop(skb);
        skb->protocol = htons(ETH_P_IP);;
        skb->dev = dev;
        ret = NF_STOLEN;
        break;
	}

    /* 模擬TUN虛擬網絡卡接收,此時截獲處理正式完成,
     * 告訴UDP,嗨,你的資料我已經幫你處理了 
     **/
	/*	遍歷PREROUTING旨在建立被OpenVPN封裝流量的conntrack,
	 *	因為只有在這裡才能從OpenVPN資料通道的conntrack中的info資訊得到加密金鑰:
	 *	1.該類流量在netif_rx_ni->netif_receive_skb->ip_rcv...路徑中徑直通過PREROUTING;
	 *	2.該類流量的reply流量直接使用其conntrack info中的加密金鑰進行加密
	 **/
	nf_reset(skb);
    /* 溜達溜達,一直溜達到skb的conntrack被設定,所以我使用了帶有condition的版本 */
	NF_HOOK_COND(PF_INET, NF_INET_PRE_ROUTING, skb, skb->dev, NULL,
						ovpn_nf_pre_routing, skb->nfct != NULL);
	{
		/*struct nf_conn *ct_inner;*/
		/*enum ip_conntrack_info ctinfo_inner;*/
		/*struct instance_info *cipher_info; */
		/* OK! 此時的ct應該就是OpenVPN裸skb的ct了! */
		/*
		 *  注意,這裡可能比較繞!對於ct_inner,很顯然它是OpenVPN資料協議封裝的內部skb的ct,那麼
		 *  它的方向有兩個,一個是正一個反,
		 *  1.對於正方向,很顯然它是我們在上面的ovpn_nf_pre_routing
		 *	  這個fake HOOK中建立的,理所當然它的cipher_info就是在這裡建立的
		 *	2.對於反方向,它走的是慢速路徑,即它走的是OpenVPN程序(這是為什麼呢?為什麼呢?
		 *			因為:
		 *				skb來自某個物理網口,顯然最終它要從tun0中xmit出去,這一路上它是不可能獲得
		 *				任何關於multi_instance的資訊的,所以只好走入慢速路徑中,由OpenVPN程序從字元
		 *				裝置讀取該資料包,然後由OpenVPN程序加密,封裝,傳輸之)
		 *	  只要有反向發起的資料包的正向(即從OpenVPNclient到OpenVPNserver方向)返回包經由此處,它將
		 *	  建立cipher_info。
		 *	因此,此處並不區分對待ct的方向!
		 **/

        /*
		ct_inner = nf_ct_get(skb, &ctinfo_inner);
		cipher_info = (struct instance_info *)nf_conn_acct_find((const struct nf_conn *)ct_inner);
		if (cipher_info == NULL) {  
			cipher_info = (struct instance_info *)nf_ct_acct_ext_add(ct_inner, GFP_ATOMIC);  
			if (cipher_info == NULL) {
				ret = NF_DROP;
				goto out;
			}  
			goto alloc_info;
		} else {
			// 注意:最終的成型info extend中,需要在destroy裡面釋放 JUST test!!
			if (cipher_info->saddr == 0 && cipher_info->daddr == 0) {
alloc_info:
				cipher_info->saddr = saddr;
				cipher_info->daddr = daddr;
				cipher_info->sport = sport;
				cipher_info->dport = dport;
				// info 就是cipher 
			}
		}
        */    
	}
    /* 真是謝天謝地!謝什麼?答曰:
     * 在呼叫netif_rx的時候竟然還能保留nf資訊,比如保留nf_conn...
     * 其實這也沒什麼大不了的,難道bridge模組沒有這麼玩嗎?難道bonding,vlan沒有這麼玩嗎?
     * 如果你不懂,沒關係,試試看: 
     * sysctl -w net.bridge.bridge-nf-call-iptables=1
     * 然後跟一下程式碼...
     **/
	netif_rx_ni(skb);
    goto out;

check_encap_xmit:
    /* 此處find conntrack的info資訊,如果資料包從物理網絡卡接收,最終需要通過tun網絡卡發出進行加密,那麼:
     * 1.該資料包所屬的流在從OpenVPN客戶端過來的時候在PREROUTING中被解密,然後在PREROUTING中溜達到conn建立,
     *   此時,該流可以查到,直接取出info資訊,呼叫encap_xmit進行加密;
     * 2.該資料包所屬的流是主動從OpenVPN服務端發往OpenVPN客戶端方向的,那麼它在這個HOOK就應該直接返回,進入
     *   OpenVPN這個慢速路徑進行加密,如果有從OpenVPN客戶端回來的包,那麼在這個HOOK中就會被在conntrack的info
     *   中設定info資訊。
     * :)也已經深了,我以上如此清晰的思路想必可以代替程式碼吧,此處我就直接通過了。
     **/
    {
        int check = 0; /* 真正的check! */
        if (check) {
			struct ovpn_instance ovpn;
            ret = encap_xmit(skb, &ovpn);
        }
    }

out:
	dev_put(dev);
out_not_put:
    return ret;
}

static struct nf_hook_ops ipv4_ovpn_ops[] __read_mostly = {
	{	.hook		= ipv4_ovpn_in,
		.owner		= THIS_MODULE,
		.pf		=	NFPROTO_IPV4,
		.hooknum	= NF_INET_PRE_ROUTING,
		.priority	= NF_IP_PRI_CONNTRACK + 1,
	},
	{	.hook		= ipv4_ovpn_in_local,
		.owner		= THIS_MODULE,
		.pf		=	NFPROTO_IPV4,
		.hooknum	= NF_INET_LOCAL_OUT,
		.priority	= NF_IP_PRI_CONNTRACK + 1,
	},
};


static void nf_conntrack_openvpn_fini(void)
{
	nf_unregister_hooks(ipv4_ovpn_ops, ARRAY_SIZE(ipv4_ovpn_ops));
}

static int __init nf_conntrack_openvpn_init(void)
{
	int ret = 0;

	ret = nf_register_hooks(ipv4_ovpn_ops, ARRAY_SIZE(ipv4_ovpn_ops)); 
	if (ret) {
		printk("nf_ct_ovpn: failed to register\n");
		return ret;
	}
		printk("nf_ct_ovpn: OKOK\n");
	return 0;
}

module_init(nf_conntrack_openvpn_init);
module_exit(nf_conntrack_openvpn_fini);


相關推薦

OpenVPN的Linux核心鬼魅殘缺 part III rework with Netfilter

哥們兒拿到了juniper的offer,由衷祝福,酒足飯飽後的我,在Netfilter的路上卻根本停不下來。已經是深夜,回憶這些年在Netfilter上的探索,結合目前的一些狀況,突然覺得,既然我已經想把OpenVPN弄到核心了,那為何上一個鬼魅的實現沒有使用Netfilte

對Yii2中 yiiwebUser的理解和自建的appmodelsUser(基礎)frontendmodelsUser的應用原理

end his iat getter authent property 用戶id tails uniq yii\web\User 是一個統稱,為用戶,沒有具體實例,只能管理; 此處以app\models\User為基準; app\models\User 是映射數據表us

數據挖掘十大算法總結--核心思想算法優缺點應用領域

data- 文本分類 target apr 排名 ans kmean 全部 等等 --------------------------

asp.net幾種開源上傳控件flash,ajax支持多文件

控件 custom add into ive select arch asp.net zed 原文發布時間為:2010-03-18 —— 來源於本人的百度文章 [由搬家工具導入]1、AspnetUpload地址

Mockplus推出真正無限制終身做原型就要一輩子!

陌生 團隊 蓮花 自行車 免費 簡單 觀察 nts 一個 如今提到原型工具,各位設計師和PM爸爸們一定不會對Mockplus感到陌生吧?事實上,從一開始的默默無聞,到在UXPA大賽上嶄露頭角,再到被Adobe XD 列為主要競品,如今,摩客君已經在全球範圍內贏得了50萬+用

Visual Studio 2017 Enterprise 發布 15.3.2 附離線安裝包下載。

安裝 net 離線 地址 2017年 out 全量 全部 lock Visual Studio 2017 Enterprise 更新至 15.3.2 ,本安裝包使用微軟原版安裝文件,配合layout指令全量下載後制作,內置中文語言包,包含 Visual Studio 201

ahjesus wp-autopost破解親測可用

blog name png where 專業 idt mage pack value 在funtion.php裏 把fetchUrl 這個函數的判斷去掉 直接執行判斷為真的結果下面是修改後的函數function fetchUrl($_var_22){ glob

使用GitHub進行項目創建——初級非指令純軟件操作

位置 開發 cnblogs 輸入 創建倉庫 des 結束 idt 根據 主要步驟如下: 1、申請一個GitHub賬號,官網按照步驟來就行 2、下載一個GitHub DeskTop(https://desktop.github.com/),命令什麽的以後說不定會寫把 3、創建

房價預測《進階測試》

rest 哪些 tle blog model lln one atp feature #coding=utf8 import numpy as np import pandas as pd from sklearn.linear_model import Ridge f

SOAP協議是RPC協議的升級出現了WSDL

rpc soap 應用層協議背景關於RPC協議的思考RPC協議的組成部分RPC協議=服務端+服務註冊管理中心+客戶端,構成客戶端可遠程調用服務端的方法,就跟調用本地方法一樣方便。Java實現RPC常用的框架RMI服務註冊管理中心常用的框架是ZooKeePerRPC服務端和客戶端獨立部署RPC客戶端必須包含服務

Win10專業CMake3.8.2opencv3.3.0自編譯ffmpeg文件

鏈接 編譯 tro 一個 了解 mpeg baidu 找不到 程序 編譯是真的麻煩,到處都在出問題,還找不到是哪兒的問題,自己真是太垃圾了。 目的是在CLion上編寫一個opencv的小程序。以前是學JAVA的,對C++了解的不是很多,遇到了不少的麻煩。 重點:在使用CMa

最強的核心競爭力是執行力

職場 執行力 升職 余世維在《贏在執行》裏提到,什麽是執行力?執行力就是按時、按質、按量,不折不扣地完成工作任務的能力!執行力不但直接反應了一個團隊的管理水平,還體現出團隊成員的精神面貌,它又無時無刻不在影響著我們的工作和生活。拖延和推諉是執行過程中的最大阻礙,絕大部分都有拖延的習慣。截至時間的最

java處理大數據量任務時的可用思路--未驗證具體實現方法有待實踐

mapr 正向 碰撞 並且 aggregate pear 因此 mapreduce and 1.Bloom filter適用範圍:可以用來實現數據字典,進行數據的判重,或者集合求交集基本原理及要點:對於原理來說很簡單,位數組+k個獨立hash函數。將hash函數對應的值的

ORM單表查詢

req val oct 文件 tle eth set one count mysql 建學生表及課程表 添加內容 view.py from django.shortcuts import render,HttpResponse,redirect from . i

ORM學生管理系統02

-i cname 圖片 nts lin hid ... 提交 white 學生管理系統 urls.py url(r‘^student_list/$‘,views.student_list,name="student_list"), url(r‘^dele

1月28日周日更新ruby到2.5.0rvm更新。

一課 比較 全棧 body 安裝 auto 文檔 code post 在學習Array的方法的時候,發現文檔concat方法可以進行多個數組的添加,而我的不行,猜測是ruby版本沒有更新。 查詢2.31ruby版本的concat方法,果然和2.5版本的不一樣。 於是準備更

新百度Q4營收236億信息流增長搶眼成核心業務短視頻表現出彩

上線 地產 創始人 年度 吸引 旅遊行業 什麽 都是 畫像 2月14日,由李彥宏與女兒Brenda共同“出演”的新春“賀歲片”通過百度App發布。眾網友驚呼國民嶽父好帥,國民閨女好漂亮! 當我們在為民嶽父,國民閨女叫好的時候,也不禁為他們的“敬業精神”鼓掌。 “賀歲片”中除

訂閱Unity Plus加強三重好禮等待您!

min 藝術 精品 com 可視化編程 優惠價 效果 dmi 停止 2018年第一次訂閱Unity Plus加強版的促銷活動現在開啟了,本次活動我們精心的為新訂閱Unity Plus加強版的用戶準備了三重好禮,幫助大家更快速的進入遊戲開發的世界。 促銷內容 促銷時間:截

星球大戰中修理機器人的真實原來是這樣子

星球大戰隨著技術的發展,機器人可以實現與人類對話,能夠代替人類勞動,能夠提高決策效率,機器人不再是想象中未來的一個符號,而是一個真實存在的夥伴。如今,機器人不僅可以在地球上幫助人類,還可以在太空幫助人類。 美國宇航局機器人工程師說:“‘星球大戰’機器人R2-D2和BB-8是美國宇航局正在尋找的機器人,因為它可

WinSCP(5.11.2)綠色便攜開源SCP/SFTP客戶端

文件中 nor 分享圖片 技術 http .html 用戶 sftp 傳輸 WinSCP是一款可以在微軟 Windows 系統環境下使用的支持 SSH 命令的開源圖形化 SFTP 客戶端工具。同時也支持 SCP 文件傳輸協議,它的主要功能是在本地與遠程計算機間安全地復制文件