Linux内核网络编程
netfilter
內核網絡編程
網絡協議數據結構inet_protosw
在Linux-2.6.26.3/net/ipv4/af_inet.c文件中有一個名為inet_init()的函數對協議進行了初始化。inet_init()函數使用proto_register()函數來注冊每個內嵌協議。
軟中斷CPU報文隊列及其處理
- Linux內核網絡協議層的層間傳遞手段——軟中斷。
軟中斷機制的核心元素:
- 軟中斷狀態:是否有觸發的軟中斷未處理
- 軟中斷向量表:包含兩個成員變量,一個是處理此軟中斷的回調函數,另一個是處理時所需的參數。
- 軟中斷守護內核線程:內核建立一個內核線程ksoftirqd來輪詢軟中斷狀態,調用軟中斷向量表中的軟中斷回調函數處理中斷。
中斷事件處理過程:
軟中斷使用方法
Linux系統最用同時注冊32個軟中斷,目前系統使用了6個軟中斷。其中一個為taskle機制,該機制用來實現下半部,描述軟中斷的核心數據結構為中斷向量表,其定義如下:
struct softirq_action { void (*action)(struct softirq_action *); void *data; };- action:軟中斷服務程序
- data:服務程序輸入參數
sk_buff結構
因為內核層和用戶層在網絡方面的差別很大,在內核的網絡層的sk_buff結構占有重要的地位,幾乎所有的處理與此結構有關系。
sk_buff主要成員:
socket數據在內核中接收和發送
socket數據在內核中的流程主要包括初始化、銷毀、接收和發送網絡數據源。其過程設計網卡驅動、網絡協議棧和應用層的接口函數。
- socket()初始化:創建socket()需要傳遞family、type、protocol這三個參數。
-
創建socket()其實就是創建一個socket實例,然后創建一個文件描述符結構。創建套接字文件描述符會互相建立關聯,即建立相互連接的的指針,并且初始化這些文件的讀寫操作映射到read()、write()函數上來。
-
在初始化套接字的時候,同時初始化socket的操作函數(proto_ops結構)
-
創建socket的同時還創建sock結構的數據空間。還會初始化三個隊列:receive_queue,send_queue,backlog_queue。
- 接收網絡數據recv():
網絡數據接收依次經過網卡驅動和協議棧程序。
- 發送網絡數據send():
linux對網絡數據的發送過程的處理與接收過程相反。在一端對socket進行write()的過程中。首先會把write的字符串緩沖區整理成msghdr的數據結構形式,然后調用sock_sendmsg()把msghdr的數據傳送至inet層。
msghdr結構中數據區的每個數據包,創建sk_buff結構,填充數據,掛至發送隊列。一層層往下層協議傳遞。以下的每層協議將不再對數據進行復制,而是對sk_buff結構直接進行操作。
內核模塊編程
內核模塊編程和典型的應用程序的區別:
典型應用有一個main程序,而內核模塊需要一個初始化函數和清理函數,在向內核中插入模塊時調用初始化函數,卸載內核模塊時調用清理函數。
加載內核相對于直接編寫內核模塊有很大方便性:
- 不用重新編譯內核
- 可以動態加載和卸載,調試使用方便。
Hello,World
編寫C語言程序:
//必要的頭文件 #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> //模塊許可證聲明(必須) MODULE_LICENSE("Dual BSD/GPL"); //模塊加載函數(必須) static int hello_init(void) {printk(KERN_ALERT "Hello World enter/n");return 0; } //模塊卸載函數(必須) static void hello_exit(void) {printk(KERN_ALERT "Hello World exit/n"); } //模塊的注冊 module_init(hello_init); module_exit(hello_exit); //聲明模塊的作者(可選) MODULE_AUTHOR("XXX"); //聲明模塊的描述(可選) MODULE_DESCRIPTION("This is a simple example!/n"); //聲明模塊的別名(可選) MODULE_ALIAS("A simplest example");編寫makefile文件:
obj-m += hello.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #complie object all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #clean clean:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean執行make命令能得到
以上這些文件,使用sudo insmod Hello.ko來加載內核:
但是能發現并沒有任何消息提示,這是因為printk函數并不是在命令行將信息打印而是在系統日志里面,需要使用指令dmesg進入系統日志:
也可以使用lsmod查看內核依賴關系:
最后,輸入rmmod指令卸載內核
內核模塊的基本架構
自動調用模塊的的初始化函數,進行模塊的初始化,主要是資源申請。
使用命令rmmod卸載內核模塊,模塊清除函數自動調用。主要狀態重置和資源釋放。
并不是強制要求必須聲明。內核可以識別以下四種許可方式:GPLa Dual BSD/GPLa Dual MPL/GPLa Proprietary。如果沒有采用以上許可證方式的聲明則假定為私有的,內核加載這種模塊會被“污染”。
- MODULE_AUTHOR:作者描述模塊
- MODULE_DESCRIPTION:模塊用途的簡短描述
- MODULE_VERSION:模塊版本號
- MODULE_AVIAS:模塊別名
內核加載模塊過程
內核卸載模塊過程
netfilter的五個鉤子函數
在IP包的IPv4協議棧上的傳遞過程中,有五個檢查點,并且各引入了一對NF_HOOK()宏函數的一個響應的調用:
- PREROUTING:在報文做路由以前執行
- LOCAK-IN:在報文轉向另一個NIC(網卡)以前執行
- FORWARD:在報文流出以前執行
- LOCAL-OUT:在流入本地的報文做路由以后執行
- POSTROUTING:在本地報文做流出路由之前執行
利用這5個參考點,查閱用戶注冊的回調函數,根據用戶定義的回調函數來監視進出的網絡數據包,是netfilter的基本實現框架。
netfilter定義了一個全局變量來存儲用戶的回調函數:
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
其中,NPROTO是協議類型,可以是TCP、UDP或者IP協議。NF_MAX_HOOKS是掛接的鉤子最大數量。
以上介紹的五個檢查點對應五個鉤子函數。
- NF_IP_PRE_ROUTING:
- NF_IP_FORWARD:
- NF_IP_POST_ROUTING:
- NF_IP_LOCAL_IN:
- NF_IP_LOACL_OUT:
如果是要做包過濾,需要用到NF_IP_LOCAL_IN鉤子函數。
NF_HOOK宏
netfilter的框架是在協議棧處理過程中調用NF_HOOK(),插入處理過程來實現的。
#ifdef CONFIG_NETFILTER #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \ nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn))) #else #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb) #endif /*CONFIG_NETFILTER*/以上為NF_HOOK()函數的定義。從該函數宏可知,當編譯了netfilter時(#ifdef CONFIG_NETFILTER )就會調用nf_hook,slow()函數。如果沒有的話則調用NF_HOOK宏中的參數okfn。
鉤子的處理規則
netfilter的鉤子函數的返回值可以為NF_ACCEPT、 NF_DROP、 NF_STOLEN、NF_QUERE、 NF_REPEAT
- nf_accept:繼續傳遞,保持和原來傳輸的一致
- nf_drop:丟棄包,不再繼續傳遞
- nf_stolen:接管包,不再繼續傳遞
- nf_quere:隊列化包(通常是為用戶空間處理做準備)
- nf_repeat:再次調用這個鉤子
注冊/注銷鉤子
注冊和注銷鉤子函數的接口主要有:nf_register_hook、nf_unregister_hook、nf_register_sockopt 、nf_unregister_sockopt
nf_hook_ops結構
struct nf_hook_ops { struct list_head list; //鉤子鏈表nf_hookfn *hook; //鉤子處理函數struct module //模塊所有者 int pf; //鉤子的協議族int hooknum; //鉤子的位置值int priority; //鉤子的優先級,默認情況為繼承優先級 }; typedef unsigned int nf_hookfn(unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *));okfn()函數是當回調函數為空時,netfilter調用的處理函數。
- 注冊鉤子
void nf_register_net_hook(struct net *net, const struct nf_hook_ops *ops);
- 注銷鉤子
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *ops);
鉤子函數的Hello,World
#include <linux/init.h> #include <linux/module.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/skbuff.h> #include <net/tcp.h> #include <linux/netdevice.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h>static struct nf_hook_ops nfhoLocalIn; //定義實現鉤子函數的結構體 MODULE_LICENSE("Dual BSD/GPL");unsigned int hello_hookfn(void* priv, struct sk_buff* skb, const struct nf_hook_state* state) //回調函數 {printk(KERN_ALERT "Hello World Hook\n");return NF_ACCEPT; } int init_module() //初始化模塊 {nfhoLocalIn.hook = hello_hookfn; //構建結構體nfhoLocalIn.pf = PF_INET;nfhoLocalIn.priority = NF_IP_PRI_FIRST;nf_register_net_hook(&init_net, &nfhoLocalIn); //注冊鉤子printk("My nf register\n");return 0; } void cleanup_module() {nf_unregister_net_hook(&init_net, &nfhoLocalIn); //注銷鉤子printk("My nf unregister\n"); }總結
以上是生活随笔為你收集整理的Linux内核网络编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux环境下创建运行.java文件
- 下一篇: linux内核编程4部曲之一:linux