Netfilterによるパケットのキャプチャの基礎知識と実現

12280 ワード

自分が狂った青春を記録することに基づいて、netfilterの応用学習に対して少し記録をします.
0 x 00前提
デュアルNICハードウェアデバイスはミドルウェアとして、1つのNICが別のNICへの転送を受信したときに操作パケットを行う.以下では、例えばeth 0が入力であり、eth 1が発行であり、相対的に.今回の開発はnetfilterフレームワークを用いて完全な開発を行い,パケットの任意の操作を基本的に実現した.
0 x 01 netfilterとは
Netfilterはlinux 2です.4.xが導入したサブシステムは、汎用的で抽象的なフレームワークとして、パケットフィルタリング、ネットワークアドレス変換、プロトコルタイプに基づく接続追跡などのHook関数管理メカニズムのセットを提供している.一般的には、netfilterは、ホストに到達したパケットまたはホストを介して転送されたパケットフローに、HOOKポイントと呼ばれるユーザが操作できるポイントを追加する一連のインタフェースを提供している.
Netfilterには5つのhookポイントがあり、それぞれ:
【1】NF_IP_PRE_ROUTING:ネットワーク層に入ったばかりで、まだルーティングされていないパッケージは、ここを通ります.
【2】NF_IP_POST_ROUTING:ネットワーク層に入るとルーティングされ、転送が決定され、本装置のパケットから離れる.ここを通過する.
【3】NF_IP_LOCAL_IN:ルーティングにより,本機へのパケットを特定し,ここを通過する.
【4】NF_IP_LOCAL_OUT:本機プロセスから出たばかりのパッケージは、ここを通ります.
【5】NF_IP_FORWARD:経路検索後、転送するパケットはPOST_ROUTINGの前に.
今回の実践応用では、主に本機が受信、送信したすべてのパケット(TCP、UDP、ICMPプロトコルを含む)をブロックし、実行したい操作を実行した後に放行(暗号化、情報修正、情報偽造などのいたずら行為)し、本機開発時に使用したHOOKポイントはNF_IP_LOCAL_OUTとNF_IP_LOCAL_IN,ただしARMではルーティング機能として機能するため、eth 0で受信したパケットを転送するには、HOOKポイントをNF_に変更するIP_PRE_ROUTINGは、受信したパケットをルーティングする前にすべての操作を行い、逆にeth 1が受信したのも同様である.
0 x 02開発の先行知識
1.hook関数の登録と登録解除
netfilterフレームワークを使用して目的の操作を行う場合は、まずカーネルに自分のフック関数を登録する必要があります.カーネルに今処理することを伝えるには、このフック関数にはどんな情報が含まれていますか?
static struct nf_hook_ops nfho = {  
    .hook = my_func,  
    .pf = PF_INET,  
    .hooknum =NF_INET_LOCAL_IN ,  
    .priority = NF_IP_PRI_FIRST,  
    .owner = THIS_MODULE,  
}; 

上記の例ではnf_が定義されています.hook_opsタイプの構造体nfhoで、構造体のメンバーは次のとおりです.
    .hook;あなたが定義したhook関数の名前を表します.
    .pf;プロトコルクラスタ、例ではPF_INETはIPv 4を表します.
    .hooknum;このフックのパケット処理フローにおける位置を示し、例ではルーティングによって自機が受信したことを確認し、自機プロセスに入る前に示す.
    .prioity;あなたが登録したフック関数の優先度、例ではNF_IP_PRI_FIRSTは最も優先度が高いことを示す.
    .owner;属するモジュールを示します.例ではこのモジュールに属します.
このフック関数は定義されています.モジュールのロード時に登録するだけです.失敗した場合、カーネルレベルのエラー情報を印刷します.
static int __init http_init(void)  
{  
	if (nf_register_hook(&nfho)) 
	{  
		printk(KERN_ERR"nf_register_hook() failed
"); return -1; } return 0; }

同様に、モジュールの終了時に、登録されたフック関数を解登録します.
static void __exit http_exit(void)  
{  
	nf_unregister_hook(&nfho);
}  

モジュールのロードと終了については、カーネルプログラミングhello wordを参照してください.分かりやすい.
2.skbとは
到着したデバイスのパケットをカーネル内で操作するには、次のことを理解する必要があります.
(1)パケットがデバイスに到着したらどこに格納されますか?
(2)パケットのヘッダ構造を1層ずつ除去し,データ部分を得るにはどうすればよいか.
(3)処理後に直接送ることは可能でしょうか.
(4)できない場合は、どのような操作が必要ですか?
まず問題(1)を見ると,skbの基本構造と次に使用する一連の構造体を理解する必要がある.
skbの構造体のソースコードは置いていません.大きく探してみると、主に私たちが使う必要があるいくつかの部分について簡単に紹介します.
union {  
                 struct tcphdr   *th;  
                 struct udphdr   *uh;  
                 struct icmphdr  *icmph;  
                 struct igmphdr  *igmph;  
                 struct iphdr    *ipiph;  
                 struct ipv6hdr  *ipv6h;  
                 unsigned char   *raw;  
         } h;  

ここのunion h;完全なパケットの様々なヘッダを表し、私たちがよく使うiphdr、tcphdr、udphdr、icmpdrを表します.
iphdrの一般的なメンバーについて簡単に説明します.
    ipiph->ihl;IPヘッダの長さは、32ビット長が何個あるかを表し、ipヘッダの長さを計算する際には、通常ipiph->ihl*4が用いられる.
    ipiph->tot_len;IPメッセージ全体の長さから、ipヘッダの長さを減算すると、ipパケットのデータ部分になります.使用中はネットワークバイト順の問題に注意する必要があります.
    ipiph->ttl;生存時間は、1つのパケットが最も多く通過できるルータの数を規定しており、この値が0に減少すると、このパケットは破棄され、無限ループを防止する.
    ipiph->protocol;プロトコルタイプは、どのプロトコルから送られてきたパケットかを表し、ここでのプロトコルは、伝送層のプロトコルTCPなどを指す.
    ipiph->check;ipヘッダの検証値は,ヘッダのみを検証し,その中のデータを検証しない.
    ipiph->saddr;IPパケットのソースIPアドレス.
    ipiph->daddr;IPパケットの宛先IPアドレス.
tcphdrの一般的なメンバーの簡単な説明:
     th->source;パケットのソース側のスローガンを表します.
     th->dest;パケットの宛先ポート番号を示します.
    th->doff;TCPヘッダの長さは、32ビットのデータが何個含まれているかを示すので、tcpヘッダの長さを計算するにはth->doff*4が必要です.
    th->check;TCPヘッダのチェックサムは、メッセージ全体のデータ部分を暗号化したり、他の処理をしたりした場合、チェックサムを再計算する必要があります.
udphdrおよびicmphdrにおけるヘッダ長はsizeof(sturct udphdr/icmphdr)を直接用いて計算することができる.
skb->lenとskb->data_len:
    skb->len;skb->dataポインタが指すデータ領域の長さを表し、線形領域のデータ長、非線形データ領域のデータ長を含む.
    skb->data_len;非線形データ領域のデータ長を表します.
私たちはデータ情報を印刷するとき、完全に印刷できないことがよくあります.一部が欠けています.これはdata_len部分は0ではなく、dataが指すデータ領域が線形データに印刷された後、停止します.この場合、skbの非線形部分を線形部分に変換して再印刷する関数を使用して、すべてのデータ部分を印刷することができます.コード例:
if(skb->data_len!=0)
{			
	if(skb_linearize(skb))
	{
		printk("error line skb\r
"); printk("skb->data_len %d\r
",skb->data_len); return NF_DROP; } }

我々の非線形データが0でない場合、関数skb->linearize変換を使用して、skbのデータ末尾ポインタをdata_を後ろに押すことができることを示します.len個の長さ、data_len部分はdataポインタが指す末尾にもつながっている.
3.チェックサムの再計算
データ領域のデータを操作した後、元のパケットを直接送信することはできません.また、チェックフィールドを修正する必要があります.修正したチェックフィールドは主にskbのチェックフィールド、iphのチェックフィールド、伝送層(TCP、UDP、ICMP)のチェックフィールドがあります.
tcpを例に、チェックサムを計算します.
iph->check=0;
iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl);
if(skb->ip_summed == CHECKSUM_HW)
{
    			
        tcph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_TCP,csum_partial(tcph,(ntohs(iph ->tot_len)-iph->ihl*4),0));	
	skb->csum = offsetof(struct tcphdr,check);		
}
チェックサムを再計算したパケットは、ネットワーク上で伝送を継続することができる.
0 x 03開発プロセス
基本的な考え方は:まず協議を判断する;
各プロトコルの特徴に基づいて、データ部分の位置とデータ部分の長さを計算する.    
データ部分に対して行いたい処理、暗号解読などを行う.
チェックサムを再計算し、処理結果を返します.
関数が値を返すには、次の5つがあります.
NF_DROP;このパケットを破棄し、一般的にフィルタリング手段として使用することができる.
NF_ACCEPT;パケットを受け入れます.
NF_STOLEN;*-*使ったこともないし、具体的な意味も理解していません.
NF_QUEUE;ユーザキューに並び、netlinkを学習してカーネルのパケットをユーザ状態に送信するときに使用します.
NF_REPEAT;Hook関数を再呼び出します.
以下、TCPメッセージを例にとります.
if(likely(iph->protocol==IPPROTO_TCP))
{
	tcph=tcp_hdr(skb);
	data=skb->data+iph->ihl*4+tcph->doff*4;
	header=iph->ihl*4+tcph->doff*4;
	length=skb->len-iph->ihl*4-tcph->doff*4;
	if(skb->len-header>0)
	{
		printk("**************now_start_in_data*****************
"); printk("header length is %d",header); printk("\r
"); printk("len-header is %d",skb->len-header); printk("\r
"); printk("data length is %d",length); printk("\r
"); if(skb->data_len!=0) { if(skb_linearize(skb)) { printk("error line skb\r
"); printk("skb->data_len %d\r
",skb->data_len); return NF_DROP; } } for(i=0;icheck=0; iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl); if(skb->ip_summed == CHECKSUM_HW) { tcph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_TCP,csum_partial(tcph,(ntohs(iph ->tot_len)-iph->ihl*4),0)); skb->csum = offsetof(struct tcphdr,check); } } } return NF_ACCEPT;

完全なtcpパケットのキャプチャは送信で完了しました!(*-*転送はまだできません)
0 x 04フルコード
#include  
#include   
#include 
#include 
#include 
#include   
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CHECKSUM_HW 1
unsigned int my_func(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *))
{	struct iphdr *iph=ip_hdr(skb);
	struct tcphdr *tcph;
	struct udphdr *udph;
	struct icmphdr *icmph;
	struct net_device	*master;
	int i=0,ret=-1;
	int header=0;
	int index=0;
	unsigned char *data=NULL;
	int length=0;
	if(likely(iph->protocol==IPPROTO_TCP))
	{
		tcph=tcp_hdr(skb);
		data=skb->data+iph->ihl*4+tcph->doff*4;
		header=iph->ihl*4+tcph->doff*4;
		length=skb->len-iph->ihl*4-tcph->doff*4;
		if(skb->len-header>0)
		{
			printk("**************now_start_in_data*****************
"); printk("header length is %d",header); printk("\r
"); printk("len-header is %d",skb->len-header); printk("\r
"); printk("data length is %d",length); printk("\r
"); if(skb->data_len!=0) { if(skb_linearize(skb)) { printk("error line skb\r
"); printk("skb->data_len %d\r
",skb->data_len); return NF_DROP; } } for(i=0;icheck=0; iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl); if(skb->ip_summed == CHECKSUM_HW) { tcph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_TCP,csum_partial(tcph,(ntohs(iph ->tot_len)-iph->ihl*4),0)); skb->csum = offsetof(struct tcphdr,check); } } } else if(likely(iph->protocol==IPPROTO_UDP)) { udph=udp_hdr(skb); data=skb->data+iph->ihl*4+sizeof(struct udphdr); header=iph->ihl*4+sizeof(struct udphdr); length=ntohs(iph->tot_len)-iph->ihl*4-sizeof(struct udphdr); if(skb->len-header>0) { printk("header length is %d",header); printk("\r
"); printk("len -header is %d",skb->len-header); printk("\r
"); printk("data length is %d",length); printk("\r
"); for(i=0;idata_len!=0) { if(skb_linearize(skb)) { printk("error line skb\r
"); printk("skb->data_len %d\r
",skb->data_len); return NF_DROP; } } for(i=0;icheck=0; iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl); if(skb->ip_summed == CHECKSUM_HW) { udph->check=csum_tcpudp_magic(iph->saddr,iph->daddr,(ntohs(iph ->tot_len)-iph->ihl*4), IPPROTO_UDP,csum_partial(udph, (ntohs(iph ->tot_len)-iph->ihl*4), 0)); } printk("*********************UDPend********************
"); } } else if(likely(iph->protocol==IPPROTO_ICMP)) { icmph=icmp_hdr(skb); data=skb->data+iph->ihl*4+sizeof(struct icmphdr); header=iph->ihl*4+sizeof(struct icmphdr); length=ntohs(iph->tot_len)-iph->ihl*4-sizeof(struct icmphdr); if(skb->len-header>0) { printk("header length is %d",header); printk("\r
"); printk("len - header is %d",skb->len-header); printk("\r
"); printk("data length is %d",length); printk("\r
"); if(skb->data_len!=0) { if(skb_linearize(skb)) { printk("error line skb\r
"); printk("skb->data_len %d\r
",skb->data_len); return NF_DROP; } } for(i=0;icheck=0; iph->check=ip_fast_csum((unsigned char*)iph, iph->ihl); if(skb->ip_summed == CHECKSUM_HW) { icmph->checksum=ip_compute_csum(icmph, (ntohs(iph ->tot_len)-iph->ihl*4)); } printk("*********************ICMPend********************
"); } } return NF_ACCEPT; } static struct nf_hook_ops nfho = { .hook = my_func, .pf = PF_INET, .hooknum =NF_INET_LOCAL_IN , .priority = NF_IP_PRI_FIRST, .owner = THIS_MODULE, }; static int __init http_init(void) { if (nf_register_hook(&nfho)) { printk(KERN_ERR"nf_register_hook() failed
"); return -1; } return 0; } static void __exit http_exit(void) { nf_unregister_hook(&nfho); } module_init(http_init); module_exit(http_exit); MODULE_AUTHOR("AFCC_"); MODULE_LICENSE("GPL");

makefile:
ifneq ($(KERNELRELEASE),)
	obj-m += in.o
else
    PWD := $(shell pwd)
    KVER := $(shell uname -r)
    KDIR := /lib/modules/$(KVER)/build
default:    
	$(MAKE) -C $(KDIR)  M=$(PWD) modules
all:
	make -C $(KDIR) M=$(PWD) modules 
clean:
	rm -rf *.o *.mod.c *.ko *.symvers *.order *.makers
 endif

次に転送部分を学習し、ハードウェアデュアルNICの転送orzに使用します.
勉强の过程の中で多くの大物のブログを见てやっと今日の総括があって、しかし多くの回り道を歩いて、自分にこの时间の学んだことをよく整理させることができることを望みます.