一 简介
我们常常在Linux系统中编写socket接收TCP/UDP协议数据,大家有没有想过它怎么实现的,如果我们要实现socket接收自定义的协议数据又该怎么做呢?带着这个疑问,我们一起往下看吧~~
二 Linux内核函数简介
在Linux系统中要想实现通过socket接收网络协议栈送过来的数据,首先要对这两个内核函数实现注册, 先来看看这两个函数原型:
//ops: 指向一个描述套接字协议族的 net_proto_family 结构体。这个结构体定义了:
//协议族编号(family),比如 AF_INET(IPv4)或 AF_INET6(IPv6)。
//与此协议族关联的 create 函数,用于创建套接字。
int sock_register(const struct net_proto_family *ops);
//prot: 指向一个描述传输层协议的 proto 结构体,该结构体定义了协议的具体实现,包括各种操作和资源管理逻辑,例如发送、接收、内存分配等。
//alloc_slab: 一个布尔值,指定是否为该协议分配内存缓存(slab 缓存),通常用于控制协议的缓冲区管理。
int proto_register(struct proto *prot, int alloc_slab);
1.sock_register 将用户自定义的协议族挂载到内核的协议族表(net_families),注册成功后,用户态程序可以通过指定协议族(如 socket(AF_INET, ..))来创建对应的套接字。
2.proto_register 函数将传输层协议的实现注册到内核的协议栈中,使其可以在对应的套接字类型下运行(如 SOCK_STREAM 对应 TCP),注册的协议通常与 sock_register 中的协议族关联,用于实现更高层次的功能。
两者的关系:
-
sock_register: 是更高层的接口,用于注册协议族(如 AF_INET)。
-
proto_register: 是传输层的具体实现,用于注册实际的协议逻辑(如 TCP、UDP、或自定义协议)。
通常,一个协议族可能对应多种协议,比如AF_INET(IPv4)对的具体协议又TCP/UDP等,proto_register 是为了支持某个协议族的实际功能,而 sock_register 提供的是更外层的入口。二者可以配合使用,以实现从协议族到协议具体实现的完整功能。
自定义协议族:内核模块实现
下面这个代码是实现自定义一个协议族AF_MYPROTO(28)的内核模块。
测试平台:CentOS7, Linux内核版本: 3.10.0,编译工具:gcc 4.8.5
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/init.h>
/* 自定义协议族号 (通常选择未使用的值,sock.h有定义) */
#define AF_MYPROTO 28
/* 定义 proto_ops,用于实现 socket 操作 */
static int myproto_sock_create(struct net *net, struct socket *sock, int protocol, int kern);
/* 应用层调用close会调用sock_my_release */
static int sock_my_release (struct socket *sock)
{
pr_info("sock_my_release............\n");
return 0;
}
/* 应用层调用recv函数会触发调用下面这个sock_my_recvmsg函数 */
static int sock_my_recvmsg (struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t total_len,
int flags)
{
char buf[32];
int i=0;
for(i=0; i<32; ++i) buf[i] = (total_len+i)&0xFF;
memcpy_toiovec(msg->msg_iov, buf, 32); /* 拷贝数据到应用层buffer */
pr_info("total_len=%ld flags=%d\n", total_len, flags);
return 0;
}
/* 应用层调用send函数会触发调用该sock_my_sendmsg函数 */
static int sock_my_sendmsg (struct kiocb *iocb, struct socket *sock,
struct msghdr *m, size_t total_len)
{
pr_info("sock_my_sendmsg............\n");
return 0;
}
static const struct proto_ops myproto_ops = {
.family = AF_MYPROTO,
.owner = THIS_MODULE,
.release = sock_my_release,
.bind = sock_no_bind, /* 默认空函数 xxx_no_xxx(),下同 */
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = sock_no_getname,
.poll = sock_no_poll,
.ioctl = sock_no_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_no_setsockopt,
.getsockopt = sock_no_getsockopt,
.sendmsg = sock_my_sendmsg, /* 指定上面实现的函数 */
.recvmsg = sock_my_recvmsg, /* 指定上面实现的函数 */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
/* 定义传输协议 proto 结构体 */
static struct proto myproto_proto = {
.name = "MYPROTO",
.owner = THIS_MODULE,
.obj_size = sizeof(struct sock),
};
// 定义协议族 net_proto_family
static const struct net_proto_family myproto_family = {
.family = AF_MYPROTO,
.create = myproto_sock_create,
.owner = THIS_MODULE,
};
/* 创建 socket 函数 */
static int myproto_sock_create(struct net *net, struct socket *sock, int protocol, int kern) {
struct sock *sk;
pr_info("MYPROTO: Creating socket\n");
if (!protocol)
protocol = IPPROTO_IP;
/* 分配 socket 的底层数据结构:PF_INET: Internet IP Protocol */
sk = sk_alloc(net, PF_INET, GFP_KERNEL, &myproto_proto);
if (!sk) return -ENOMEM;
sock->ops = &myproto_ops;
sock_init_data(sock, sk);
return 0;
}
/* 模块加载和卸载,模块入口 */
static int __init myproto_init(void) {
int ret;
pr_info("MYPROTO: Initializing module\n");
/* 注册传输层协议,具体协议 */
ret = proto_register(&myproto_proto, 1);
if (ret) {
pr_err("MYPROTO: Failed to register proto\n");
return ret;
}
ret = sock_register(&myproto_family); /* 注册协议族 */
if (ret) {
pr_err("MYPROTO: Failed to register protocol family\n");
proto_unregister(&myproto_proto);
return ret;
}
pr_info("MYPROTO: Module loaded\n");
return 0;
}
/* 模块卸载时候调用 */
static void __exit myproto_exit(void) {
pr_info("MYPROTO: Cleaning up module\n");
// 注销协议族和传输层协议
sock_unregister(AF_MYPROTO);
proto_unregister(&myproto_proto);
pr_info("MYPROTO: Module unloaded\n");
}
module_init(myproto_init);
module_exit(myproto_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux编程用C@Young");
编译内核模块的Makefile如下:注意内核代码名称为net_prot.c才能编译,
obj-m:=net_prot.o #名称对应起来,也可自己修改
KERNELDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.koendif
编译完成后,使用insmod向内核插入模块:insmod net_prot.ko
使用dmesg查看模块加载信息:
三 自定义协议族:应用程序实现
这个应用程序创建AF_MYPROTO自定义协议的socket,读取数据,示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#define AF_MYPROTO 28 // 和内核协议族号保持一致
int main() {
int sockfd, i=100;
char buf[2048];
// 创建套接字
sockfd = socket(AF_MYPROTO, SOCK_RAW, 0);
if (sockfd < 0) {
perror("socket");
return EXIT_FAILURE;
}
printf("Socket created successfully with AF_MYPROTO\n");
while(--i){
read(sockfd, buf, 32+i);
for(int i=0; i<32; ++i){
printf("0x%x ", buf[i]);
}
printf("\n");
}
// 关闭套接字
close(sockfd);
return EXIT_SUCCESS;
}
使用dmesgch
四 测试结果
收到来自内核返回数据:该数据是固定填充,做示例演示。
五 总结
通过这个例子,我们了解到了内核协议族的注册与使用流程,加深对Linux协议栈的了解。
我是小C,欢迎大家一起交流学习~