Linux内核网络编程

netfilter

内核网络编程

网络协议数据结构inet_protosw

Linux-2.6.26.3/net/ipv4/af_inet.c文件中有一个名为inet_init()的函数对协议进行了初始化。inet_init()函数使用proto_register()函数来注册每个内嵌协议。

软中断CPU报文队列及其处理

  • Linux内核网络协议层的层间传递手段——软中断

软中断机制的核心元素:

  • 软中断状态:是否有触发的软中断未处理
  • 软中断向量表:包含两个成员变量,一个是处理此软中断的回调函数,另一个是处理时所需的参数。
  • 软中断守护内核线程:内核建立一个内核线程ksoftirqd来轮询软中断状态,调用软中断向量表中的软中断回调函数处理中断

中断事件处理过程:

  1. 中断事件发生
  2. 调用raise_softirq()函数设置对应的中断标记位,触发中断事务
  3. 检测中断状态寄存器的状态
  4. ksoftirqd通过查询发现某一软中断事务发生之后,通过软中断向量表调用软中断程序action
软中断使用方法

Linux系统最用同时注册32个软中断,目前系统使用了6个软中断。其中一个为taskle机制,该机制用来实现下半部,描述软中断的核心数据结构为中断向量表,其定义如下:

struct softirq_action 
{ 
 void (*action)(struct softirq_action *); 
 void *data; 
};
  • action:软中断服务程序
  • data:服务程序输入参数

sk_buff结构

因为内核层和用户层在网络方面的差别很大,在内核的网络层的sk_buff结构占有重要的地位,几乎所有的处理与此结构有关系。

sk_buff主要成员:

image-20220405143913438

image-20220405143934422

socket数据在内核中接收和发送

socket数据在内核中的流程主要包括初始化、销毁、接收和发送网络数据源。其过程设计网卡驱动、网络协议栈和应用层的接口函数。

  • socket()初始化:创建socket()需要传递family、type、protocol这三个参数。
  • 创建socket()其实就是创建一个socket实例,然后创建一个文件描述符结构。创建套接字文件描述符会互相建立关联,即建立相互连接的的指针,并且初始化这些文件的读写操作映射到read()、write()函数上来。

  • 在初始化套接字的时候,同时初始化socket的操作函数(proto_ops结构)

  • 创建socket的同时还创建sock结构的数据空间。还会初始化三个队列:receive_queue,send_queue,backlog_queue。

  • 接收网络数据recv():

网络数据接收依次经过网卡驱动和协议栈程序。

image-20220325144139835

image-20220325144502467

  • 发送网络数据send():

linux对网络数据的发送过程的处理与接收过程相反。在一端对socket进行write()的过程中。首先会把write的字符串缓冲区整理成msghdr的数据结构形式,然后调用sock_sendmsg()把msghdr的数据传送至inet层。

image-20220325144513325

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命令能得到

image-20220325152108271

以上这些文件,使用sudo insmod Hello.ko来加载内核:

但是能发现并没有任何消息提示,这是因为printk函数并不是在命令行将信息打印而是在系统日志里面,需要使用指令dmesg进入系统日志:

image-20220325152255344

也可以使用lsmod查看内核依赖关系:

image-20220325152321560

最后,输入rmmod指令卸载内核

image-20220325152411520

内核模块的基本架构

image-20220325153548250

  1. 模块初始化

自动调用模块的的初始化函数,进行模块的初始化,主要是资源申请。

  1. 模块清楚函数

使用命令rmmod卸载内核模块,模块清除函数自动调用。主要状态重置和资源释放。

  1. 模块许可声明、作者、模块描述等信息

并不是强制要求必须声明。内核可以识别以下四种许可方式:GPLa Dual BSD/GPLa Dual MPL/GPLa Proprietary。如果没有采用以上许可证方式的声明则假定为私有的,内核加载这种模块会被“污染”。

  • MODULE_AUTHOR:作者描述模块
  • MODULE_DESCRIPTION:模块用途的简短描述
  • MODULE_VERSION:模块版本号
  • MODULE_AVIAS:模块别名

内核加载模块过程

image-20220325155012425


内核卸载模块过程

image-20220325155120104


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; 	   		 //钩子的优先级,默认情况为继承优先级
};

image-20220325164013026

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");
}
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值