在内核模块中获取 NFS 客户端 IP 地址
Get NFS client IP address in a kernel module
我正在开发一个内核模块来跟踪 NFS 客户端在我的服务器上执行的操作。
我可以使用 hacky 方式(劫持 vfs 层)拦截文件操作,但我无法检索客户端的 IP 地址。
是否有任何信息可能存储在 current
任务中,我可以使用这些信息来获取执行操作的 NFS 客户端的 IP 地址?
我通过深入研究源代码得知 nfsd 在 struct super_block
的 s_fs_info
字段中存储了一个 struct nfsd_net
,但我只能将其检索为 struct net
指针。在 nfsd 的实现中,net_generic
方法用于获取 struct nfsd_net
指针(使用 nfsd_net_id
,即 pernet_operations
的 id
)。
我能以某种方式获得这个ID吗?如果是,我可以在我的内核模块中使用 struct nfsd_net
吗?它是在 fs/nfsd/netns.h
以外的地方定义的吗?
编辑
我正在使用 this approach 劫持打开函数。我正在为内核版本 4.15.0 编写此代码。这是内核模块的代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/preempt.h>
#include <linux/uaccess.h>
#include <linux/xattr.h>
MODULE_LICENSE("GPL");
#if defined(__i386__)
#define POFF 1
#define CSIZE 6
// push address, addr, ret
char *jmp_code="\x68\x00\x00\x00\x00\xc3";
typedef unsigned int PSIZE;
#else
#define POFF 2
#define CSIZE 12
// mov address to register rax, jmp rax. for normal x64 convention
char *jmp_code="\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0";
typedef unsigned long PSIZE;
#endif
DEFINE_SPINLOCK(root_open_lock);
int (*orig_root_open) (struct inode *, struct file *);
void *orig_root_open_code;
void hook(void *src_func,void *dst_addr){
barrier();
write_cr0(read_cr0() & (~0x10000));
memcpy(src_func,jmp_code,CSIZE);
*(PSIZE *)&(((unsigned char*)src_func)[POFF])=(PSIZE)dst_addr;
write_cr0(read_cr0() | 0x10000);
barrier();
}
void save_and_hook(void **p_reserve,void *src_func,void *dst_addr){
barrier();
write_cr0(read_cr0() & (~0x10000));
*p_reserve=kmalloc(CSIZE,GFP_KERNEL);
// save origin code
memcpy(*p_reserve,src_func,CSIZE);
hook(src_func,dst_addr);
write_cr0(read_cr0() | 0x10000);
barrier();
}
void fix(void **p_reserve,void *src_func){
barrier();
write_cr0(read_cr0() & (~0x10000));
memcpy(src_func,*p_reserve,CSIZE);
write_cr0(read_cr0() | 0x10000);
barrier();
}
int fake_root_open(struct inode *x, struct file *fp)
{
int ret;
printk("vfshijack: hijacked open\n"); // I need to find the client ip here.
barrier();
spin_lock(&root_open_lock);
fix(&orig_root_open_code, orig_root_open);
ret = orig_root_open(x, fp);
hook(orig_root_open, fake_root_open);
spin_unlock(&root_open_lock);
barrier();
return ret;
}
int vfs_init(void)
{
struct file *fp = filp_open("/", O_DIRECTORY|O_RDONLY, 0);
if (IS_ERR(fp))
return -1;
orig_root_open = fp->f_op->open;
if(orig_root_open)
{
save_and_hook(&orig_root_open_code, orig_root_open, fake_root_open);
}
filp_close(fp, NULL);
printk("vfshijack: vfshijack loaded\n");
return 0;
}
void vfs_exit(void)
{
if(orig_root_open)
{
fix(&orig_root_open_code, orig_root_open);
}
printk("vfshijack: vfshijack unloaded\n");
}
module_init(vfs_init);
module_exit(vfs_exit);
您可以尝试从 linux 内核跟踪工具获取所需信息,而无需使用某些自定义程序集挂钩内核二进制文件。大多数内核版本有perf
、ftrace
、trace-cmd
,更多自定义版本有stap
和lttng
。开始的一些文档:https://www.kernel.org/doc/html/v4.18/trace/index.html "Linux Tracing Technologies"
nfsd 中定义了几个跟踪点:
# modprobe nfsd
# modprobe nfs
# perf list tracepoint|grep nfs
# find /sys/kernel/debug/tracing/events -type d|grep nfsd
# trace-cmd list -e nfsd:read_start -F
nfsd/read_start 和 nfsd/write_start 跟踪点是很好的起点。两者都应该可以使用地址 rq_addr
和 fh 指针访问请求结构 rqstp
(但一些 eBPF 或 stap 脚本可能会有用)
https://elixir.bootlin.com/linux/v4.15/source/fs/nfsd/vfs.c#L1020
__be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,...)
trace_read_start(rqstp, fhp, offset, vlen);
trace_read_opened(rqstp, fhp, offset, vlen);
trace_read_io_done(rqstp, fhp, offset, vlen);
trace_read_done(rqstp, fhp, offset, vlen);
我没有 trace-cmd
或 nfs 守护程序跟踪的 stap 用法的完整示例。
Systemtap (stap
) 有一些 nfsd 统计的例子:
https://github.com/jav/systemtap/blob/master/testsuite/systemtap.examples/index.txt
# stap nfsd_unlink.stp -c "sleep 0.2"
The nfsdtop.stp script gathers and displays NFS lookups
https://github.com/larytet/SystemTap/blob/master/testsuite/systemtap.examples/network/nfsdtop.stp
我正在开发一个内核模块来跟踪 NFS 客户端在我的服务器上执行的操作。 我可以使用 hacky 方式(劫持 vfs 层)拦截文件操作,但我无法检索客户端的 IP 地址。
是否有任何信息可能存储在 current
任务中,我可以使用这些信息来获取执行操作的 NFS 客户端的 IP 地址?
我通过深入研究源代码得知 nfsd 在 struct super_block
的 s_fs_info
字段中存储了一个 struct nfsd_net
,但我只能将其检索为 struct net
指针。在 nfsd 的实现中,net_generic
方法用于获取 struct nfsd_net
指针(使用 nfsd_net_id
,即 pernet_operations
的 id
)。
我能以某种方式获得这个ID吗?如果是,我可以在我的内核模块中使用 struct nfsd_net
吗?它是在 fs/nfsd/netns.h
以外的地方定义的吗?
编辑
我正在使用 this approach 劫持打开函数。我正在为内核版本 4.15.0 编写此代码。这是内核模块的代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/cred.h>
#include <linux/sched.h>
#include <linux/preempt.h>
#include <linux/uaccess.h>
#include <linux/xattr.h>
MODULE_LICENSE("GPL");
#if defined(__i386__)
#define POFF 1
#define CSIZE 6
// push address, addr, ret
char *jmp_code="\x68\x00\x00\x00\x00\xc3";
typedef unsigned int PSIZE;
#else
#define POFF 2
#define CSIZE 12
// mov address to register rax, jmp rax. for normal x64 convention
char *jmp_code="\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0";
typedef unsigned long PSIZE;
#endif
DEFINE_SPINLOCK(root_open_lock);
int (*orig_root_open) (struct inode *, struct file *);
void *orig_root_open_code;
void hook(void *src_func,void *dst_addr){
barrier();
write_cr0(read_cr0() & (~0x10000));
memcpy(src_func,jmp_code,CSIZE);
*(PSIZE *)&(((unsigned char*)src_func)[POFF])=(PSIZE)dst_addr;
write_cr0(read_cr0() | 0x10000);
barrier();
}
void save_and_hook(void **p_reserve,void *src_func,void *dst_addr){
barrier();
write_cr0(read_cr0() & (~0x10000));
*p_reserve=kmalloc(CSIZE,GFP_KERNEL);
// save origin code
memcpy(*p_reserve,src_func,CSIZE);
hook(src_func,dst_addr);
write_cr0(read_cr0() | 0x10000);
barrier();
}
void fix(void **p_reserve,void *src_func){
barrier();
write_cr0(read_cr0() & (~0x10000));
memcpy(src_func,*p_reserve,CSIZE);
write_cr0(read_cr0() | 0x10000);
barrier();
}
int fake_root_open(struct inode *x, struct file *fp)
{
int ret;
printk("vfshijack: hijacked open\n"); // I need to find the client ip here.
barrier();
spin_lock(&root_open_lock);
fix(&orig_root_open_code, orig_root_open);
ret = orig_root_open(x, fp);
hook(orig_root_open, fake_root_open);
spin_unlock(&root_open_lock);
barrier();
return ret;
}
int vfs_init(void)
{
struct file *fp = filp_open("/", O_DIRECTORY|O_RDONLY, 0);
if (IS_ERR(fp))
return -1;
orig_root_open = fp->f_op->open;
if(orig_root_open)
{
save_and_hook(&orig_root_open_code, orig_root_open, fake_root_open);
}
filp_close(fp, NULL);
printk("vfshijack: vfshijack loaded\n");
return 0;
}
void vfs_exit(void)
{
if(orig_root_open)
{
fix(&orig_root_open_code, orig_root_open);
}
printk("vfshijack: vfshijack unloaded\n");
}
module_init(vfs_init);
module_exit(vfs_exit);
您可以尝试从 linux 内核跟踪工具获取所需信息,而无需使用某些自定义程序集挂钩内核二进制文件。大多数内核版本有perf
、ftrace
、trace-cmd
,更多自定义版本有stap
和lttng
。开始的一些文档:https://www.kernel.org/doc/html/v4.18/trace/index.html "Linux Tracing Technologies"
nfsd 中定义了几个跟踪点:
# modprobe nfsd
# modprobe nfs
# perf list tracepoint|grep nfs
# find /sys/kernel/debug/tracing/events -type d|grep nfsd
# trace-cmd list -e nfsd:read_start -F
nfsd/read_start 和 nfsd/write_start 跟踪点是很好的起点。两者都应该可以使用地址 rq_addr
和 fh 指针访问请求结构 rqstp
(但一些 eBPF 或 stap 脚本可能会有用)
https://elixir.bootlin.com/linux/v4.15/source/fs/nfsd/vfs.c#L1020
__be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,...)
trace_read_start(rqstp, fhp, offset, vlen);
trace_read_opened(rqstp, fhp, offset, vlen);
trace_read_io_done(rqstp, fhp, offset, vlen);
trace_read_done(rqstp, fhp, offset, vlen);
我没有 trace-cmd
或 nfs 守护程序跟踪的 stap 用法的完整示例。
Systemtap (stap
) 有一些 nfsd 统计的例子:
https://github.com/jav/systemtap/blob/master/testsuite/systemtap.examples/index.txt
# stap nfsd_unlink.stp -c "sleep 0.2"
The nfsdtop.stp script gathers and displays NFS lookups
https://github.com/larytet/SystemTap/blob/master/testsuite/systemtap.examples/network/nfsdtop.stp