最新要闻
- 4月皮卡销量排名出炉 长城江淮大增 新能源有待突破|世界时快讯
- 世界之最大全100条_世界之最大全
- 大模型三大能力超越ChatGPT 千亿AI巨头科大讯飞否认做手机
- 大爷买二等座票带孙女坐一等座被赶 12306回应:不允许乱坐 全球热点
- 【焦点热闻】好莱坞编剧大罢工:GPT技术引发激烈争议
- 经典28定律?苹果在中国手机市场份额2成 赚走8成利润|每日简讯
- 电池只能充电500次?别太荒谬!收下这份真正的充电秘籍
- 【全球新要闻】摊主买彩票中2千多万激动砸摊位:称21人合买 明天就分钱
- 每日精选:惠普打印机禁用非原装墨盒:官方称是为用户安全考虑
- 当前焦点!地狱笑话?大学母亲节配图是《进击的巨人》
- 泥鳅汤做法_泥鳅汤的烹饪方法 环球实时
- 已在轨生活160多天 航天员费俊龙从太空发回对母亲的节日祝福
- 热播电视剧将本科写成大专 高校不干了 官方道歉|天天聚看点
- 【热闻】首发紫光展锐T750!海信悄然推出F70 Lite手机
- 迭部县气象台发布大风蓝色预警信号【2023-05-14】
- DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
世界速讯:监听容器中的文件系统事件
基本概念
Linux 文件系统事件监听:应用层的进程操作目录或文件时,会触发 system call,此时,内核中的 notification 子系统把该进程对文件的操作事件上报给应用层的监听进程(称为 listerner)。
(相关资料图)
dnotify:2001 年的 kernel 2.4 版本引入,只能监控 directory,采用的是 signal 机制来向 listener 发送通知,可以传递的信息很有限。
inotify:2005 年在 kernel 2.6.13 中亮相,除了可以监控目录,还可以监听普通文件,inotify 摈弃了 signal 机制,通过 event queue 向 listener 上传事件信息。
fanotify:kernel 2.6.36 引入,fanotify 的出现解决了已有实现只能 notify 的问题,允许 listener 介入并改变文件事件的行为,实现从“监听”到“监控”的跨越。
本文主要介绍如何通过 inotify 和 fanotify 监听容器中的文件系统事件。
Inotify
基本介绍
inotify(inode[1] notify)是 Linux 内核中的一个子系统,由 John McCutchan[2] 创建,用于监视文件系统事件。它可以在文件或目录发生变化时通知应用程序,例如,监听文件的创建、修改或删除事件。inotify 可以用于自动更新文件系统视图、重新加载配置文件,记录文件改变历史等场景。
Inotify 的工作流程如下:
用户通过系统调用(如:write、read)操作文件;
内核将文件系统事件保存到 fsnotify_group 的事件队列中;
唤醒等待 inotify 的进程(listener);
进程通过 fd 从内核队列读取 inotify 事件。
其中,inotify_event_info 的定义如下:
structinotify_event_info{structfsnotify_eventfse;u32mask;/*Watchmask.*/intwd;/*Watchdescriptor.*/u32sync_cookie;/*Cookietosynchronizetwoevents.*/intname_len;/*Name.*/charname[];/*Length(includingNULs)ofname.*/};
mask 标记具体的文件操作事件。
API 介绍
Inotify 可以用来监听单个文件,也可以用来监听目录。当监听的是目录时,inotify 除了生成目录的事件,还会生成目录中文件的事件。
“
注意:当使用 inotify 监听目录时,并不会递归监听子目录中的文件,如果需要得到这些事件,需要手动指定监听这些文件。对于很大的目录树,这个过程将花费大量时间。
参考:inotify.7[3]
- inotify_init(void)
初始化 inotify 实例,返回文件描述符,用于内核向用户态程序传输监听到的 inotify 事件。函数声明为:
intinotify_init(void);
内核同时提供了int inotify_init1(int flags)
,flags 的可选值如下:
IN_NONBLOCK读取文件描述符时不会被阻塞,即使没有数据可用也是如此。如果没有数据可用,则读操作将立即返回0,而不是等待数据可用。IN_CLOEXEC如果在程序运行时打开了一个文件描述符,并且在调用execve()时没有将其关闭,那么在新程序中仍然可以使用该文件描述符。设置IN_CLOEXEC标志后,可以确保在调用execve()时关闭文件描述符,避免在新程序中使用。
可以通过 OR 指定多个flag
,当flags=0
等价于int inotify_init(void)
。
- inotify_add_watch
添加需要监听的目录或文件(watch list),可以添加新的路径,也可以是已经添加过的路径。fd
是inotify_init
返回的文件描述符,mask
指定需要监听的事件类型,通过 OR
指定多个事件。返回值是当前路径的wd
(watch descriptor),可用于移除对该路径的监听。
函数声明为:
#includeintinotify_add_watch(intfd,constchar*pathname,uint32_tmask);
Inotify 支持监听的事件包括:
/*SupportedeventssuitableforMASKparameterofINOTIFY_ADD_WATCH.*/#defineIN_ACCESS0x00000001/*Filewasaccessed.*/#defineIN_MODIFY0x00000002/*Filewasmodified.*/#defineIN_ATTRIB0x00000004/*Metadatachanged.*/#defineIN_CLOSE_WRITE0x00000008/*Writtablefilewasclosed.*/#defineIN_CLOSE_NOWRITE0x00000010/*Unwrittablefileclosed.*/#defineIN_CLOSE(IN_CLOSE_WRITE|IN_CLOSE_NOWRITE)/*Close.*/#defineIN_OPEN0x00000020/*Filewasopened.*/#defineIN_MOVED_FROM0x00000040/*FilewasmovedfromX.*/#defineIN_MOVED_TO0x00000080/*FilewasmovedtoY.*/#defineIN_MOVE(IN_MOVED_FROM|IN_MOVED_TO)/*Moves.*/#defineIN_CREATE0x00000100/*Subfilewascreated.*/#defineIN_DELETE0x00000200/*Subfilewasdeleted.*/#defineIN_DELETE_SELF0x00000400/*Selfwasdeleted.*/#defineIN_MOVE_SELF0x00000800/*Selfwasmoved.*/
- inotify_rm_watch
移除被监听的路径。fd 是inotify_init
返回的文件描述符,wd 是inotify_add_watch
返回的监听文件描述符。
函数声明为:
#includeintinotify_rm_watch(intfd,intwd);
实例
以下是基于 Rust 语言实现的实例:
usenix::{poll::{poll,PollFd,PollFlags},sys::inotify::{AddWatchFlags,InitFlags,Inotify,InotifyEvent},};usesignal_hook::{consts::SIGTERM,low_level::pipe};usestd::os::unix::net::UnixStream;usestd::{env,io,os::fd::AsRawFd,path::PathBuf};fnmain()->io::Result<()>{letargs:Vec=env::args().collect();ifargs.len()<2{eprintln!("Usage:{}",args[0]);std::process::exit(1);}letpath=PathBuf::from(&args[1]);//初始化inotify,得到fdletinotify_fd=Inotify::init(InitFlags::empty())?;//添加被监听的目录或文件,指定需要监听的事件letwd=inotify_fd.add_watch(&path,AddWatchFlags::IN_ACCESS|AddWatchFlags::IN_OPEN|AddWatchFlags::IN_CREATE,)?;let(read,write)=UnixStream::pair()?;//注册用于处理信号的pipeifletErr(e)=pipe::register(SIGTERM,write){println!("failedtosetSIGTERMsignalhandler{e:?}");}letmutfds=[PollFd::new(inotify_fd.as_raw_fd(),PollFlags::POLLIN),PollFd::new(read.as_raw_fd(),PollFlags::POLLIN),];loop{matchpoll(&mutfds,-1){Ok(polled_num)=>{ifpolled_num<=0{eprintln!("polled_num<=0!");break;}ifletSome(flag)=fds[0].revents(){ifflag.contains(PollFlags::POLLIN){//得到inotify事件,进行处理letevents=inotify_fd.read_events()?;foreventinevents{handle_event(event)?;}}}ifletSome(flag)=fds[1].revents(){ifflag.contains(PollFlags::POLLIN){println!("receivedSIGTERMsignal");break;}}}Err(e)=>{ife==nix::Error::EINTR{continue;}eprintln!("Pollerror{:?}",e);break;}}}inotify_fd.rm_watch(wd)?;Ok(())}fnhandle_event(event:InotifyEvent)->io::Result<()>{letfile_name=matchevent.name{Some(name)=>name,None=>returnOk(()),};letevent_mask=event.mask;letkind=ifevent_mask.contains(AddWatchFlags::IN_ISDIR){"directory"}else{"file"};println!("{}{}was{:?}.",kind,file_name.to_string_lossy(),event_mask);Ok(())}
编译&测试:
cargobuild./target/debug/inotifytest
可以看到,inotify 不会递归监听二级目录下的文件dir1/file2.txt
。
经测试,Inotify 可以直接监听容器 rootfs 下的目录:
nerdctlrun--rm-itgolang./target/debug/inotify/run/containerd/io.containerd.runtime.v2.task/default/CONTAINERD_ID/rootfs
Fanotify
基本介绍
Inotify 能够监听目录和文件的事件,但这种 notifiation 机制也存在局限:inotify 只能通知用户态进程触发了哪些文件系统事件,而无法进行干预,典型的应用场景是杀毒软件。
Fanotify[4] 的出现就是为了解决这个问题,同时允许递归监听目录下的子目录和文件。
Fanotify 的工作流程如下:
用户通过系统调用(如:write、read)操作文件;
内核将文件系统事件发送到 fsnotify_group 的事件队列中;
唤醒等待 fanotify 事件的进程(listener);
进程通过 fd 从内核队列读取 fanotify 事件;
如果是 FAN_OPEN_PERM 和 FAN_ACCESS_PERM 监听类型,进程需要通过 write 把许可信息(允许 or 拒绝)写回内核;
内核根据许可信息决定是否继续完成该文件系统事件。
fanotify_event 的定义如下:
structfanotify_event{structfsnotify_eventfse;structhlist_nodemerge_list;/*Listforhashedmerge*/u32mask;struct{unsignedinttype:FANOTIFY_EVENT_TYPE_BITS;unsignedinthash:FANOTIFY_EVENT_HASH_BITS;};structpid*pid;};
fsnotify_group 的定义参考:linux/fsnotify_backend.h#L185[5]
API 介绍
- fanotify_init()
初始化 fanotify
事件组,返回该事件组的文件描述符,用于内核向用户态程序传输 fanotify
事件,同时接收来自用户态进程的许可信息。函数声明为:
#include/*DefinitionofO_*constants*/#includeintfanotify_init(unsignedintflags,unsignedintevent_f_flags);
flags 定义了事件通知的类型和文件描述符的行为,可选值有:
/*TheseareNOTbitwiseflags.Bothbitsareusedtogether.*/#defineFAN_CLASS_NOTIF0x00000000#defineFAN_CLASS_CONTENT0x00000004#defineFAN_CLASS_PRE_CONTENT0x00000008/*flagsusedforfanotify_init()*/#defineFAN_CLOEXEC0x00000001#defineFAN_NONBLOCK0x00000002#defineFAN_UNLIMITED_QUEUE0x00000010#defineFAN_UNLIMITED_MARKS0x00000020#defineFAN_ENABLE_AUDIT0x00000040/*Flagstodeterminefanotifyeventformat*/#defineFAN_REPORT_TID0x00000100/*event->pidisthreadid*/#defineFAN_REPORT_FID0x00000200/*Reportuniquefileid*/#defineFAN_REPORT_DIR_FID0x00000400/*Reportuniquedirectoryid*/#defineFAN_REPORT_NAME0x00000800/*Reporteventswithname*//*Conveniencemacro-FAN_REPORT_NAMErequiresFAN_REPORT_DIR_FID*/#defineFAN_REPORT_DFID_NAME(FAN_REPORT_DIR_FID|FAN_REPORT_NAME)
Fanotify 允许多个 listener 监听同一个文件系统对象,并且分为不同的级别,通过 flags
指定。
FAN_CLASS_NOTIF:只用于监听,不访问文件内容。
FAN_CLASS_CONTENT:可以访问文件内容。
FAN_CLASS_PRE_CONTENT:在访问文件内容之前可获取访问权限。
event_f_flags
用于设置 fanotify 事件创建并打开的文件描述符状态,可选值有:
#defineO_RDONLY00/*Allowonlyreadaccess.*/#defineO_WRONLY01/*Allowonlywriteaccess.*/#defineO_RDWR02/*Allowreadandwriteaccess.*///其它常用的event_f_flags#defineO_LARGEFILE__O_LARGEFILE/*Enablesupportforfilesexceeding2GB.*/#defineO_CLOEXEC__O_CLOEXEC/*Setclose_on_exec.*/
以下 event_f_flags
也可以使用:O_APPEND
、O_DSYNC
、O_NOATIME
、O_NONBLOCK
和 O_SYNC
,使用除这些以外的其它值将返回 EINVAL
错误码。
更多请信息参考 fanotify_init.2[6]
- fanotify_mark()
添加、删除和修改文件系统中被监听的路径,必须对该路径有访问权限。函数声明为:
#includeintfanotify_mark(intfanotify_fd,unsignedintflags,uint64_tmask,intdirfd,constchar*pathname);
fanotify_fd
是fanotify_init
返回的文件描述符,flags
描述操作类型,可选值有:
/*flagsusedforfanotify_modify_mark()*/#defineFAN_MARK_ADD0x00000001#defineFAN_MARK_REMOVE0x00000002/*如果pathname是符号链接,只监听符号链接而不需要监听文件本身(默认会监听文件本身)*/#defineFAN_MARK_DONT_FOLLOW0x00000004#defineFAN_MARK_ONLYDIR0x00000008/*只监听目录,如果传入的不是目录返回错误*//*FAN_MARK_MOUNTis0x00000010*/#defineFAN_MARK_IGNORED_MASK0x00000020#defineFAN_MARK_IGNORED_SURV_MODIFY0x00000040#defineFAN_MARK_FLUSH0x00000080/*移除所有marks*//*FAN_MARK_FILESYSTEMis0x00000100*/
mask 定义了需要监听的事件:
/*thefollowingeventsthatuser-spacecanregisterfor*/#defineFAN_ACCESS0x00000001/*Filewasaccessed*/#defineFAN_MODIFY0x00000002/*Filewasmodified*/#defineFAN_ATTRIB0x00000004/*Metadatachanged*/#defineFAN_CLOSE_WRITE0x00000008/*Writtablefileclosed*/#defineFAN_CLOSE_NOWRITE0x00000010/*Unwrittablefileclosed*/#defineFAN_OPEN0x00000020/*Filewasopened*/#defineFAN_MOVED_FROM0x00000040/*FilewasmovedfromX*/#defineFAN_MOVED_TO0x00000080/*FilewasmovedtoY*/#defineFAN_CREATE0x00000100/*Subfilewascreated*/#defineFAN_DELETE0x00000200/*Subfilewasdeleted*/#defineFAN_DELETE_SELF0x00000400/*Selfwasdeleted*/#defineFAN_MOVE_SELF0x00000800/*Selfwasmoved*/#defineFAN_OPEN_EXEC0x00001000/*Filewasopenedforexec*/#defineFAN_Q_OVERFLOW0x00004000/*Eventqueuedoverflowed*/#defineFAN_FS_ERROR0x00008000/*Filesystemerror*/#defineFAN_OPEN_PERM0x00010000/*Fileopeninpermcheck*/#defineFAN_ACCESS_PERM0x00020000/*Fileaccessedinpermcheck*/#defineFAN_OPEN_EXEC_PERM0x00040000/*Fileopen/execinpermcheck*/#defineFAN_EVENT_ON_CHILD0x08000000/*Interestedinchildevents*/#defineFAN_RENAME0x10000000/*Filewasrenamed*/#defineFAN_ONDIR0x40000000/*Eventoccurredagainstdir*//*helperevents*/#defineFAN_CLOSE(FAN_CLOSE_WRITE|FAN_CLOSE_NOWRITE)/*close*/#defineFAN_MOVE(FAN_MOVED_FROM|FAN_MOVED_TO)/*moves*/
参数 dirfd
和 pathname
确定需要监听的文件系统对象:
(1)如果 pathname
为 NULL
,由 dirfd
确定。
(2)如果 pathname
为 NULL
且 dirfd
的值为 AT_FDCWD
,监听当前工作目录。
(3)如果 pathname
为绝对路径,dirfd
被忽略。
(4)如果 pathname
为相对路径且 dirfd
不是 AT_FDCWD
,监听 pathname
相对于 dirfd
目录的路径。
(5)如果 pathname
为相对路径且 dirfd
的值为 AT_FDCWD
,监听 pathname
相对于当前目录的路径。
Fanotify 有 3 种监听模式:directed
,per-mount
和 global
,由 fanotify_mark
函数的 flags
参数指定,默认为 FAN_MARK_INODE
,也就是 directed
模式。
directed
:flag
为FAN_MARK_MOUNT
,和inotify
类似,监听指定inode
对象,如果是目录,可以添加FAN_EVENT_ON_CHILD
指定监听该目录下的所有文件(不会递归监听子目录中的文件)。per-mount
和global
模式下,FAN_EVENT_ON_CHILD
无效。per-mount
:flag
为FAN_MARK_MOUNT
,监听指定挂载点下所有的内容(目录,子目录,文件),如果传入的path
不是挂载点,则会监听path
所在的挂载点。global
:flag
为FAN_MARK_FILESYSTEM
,监听path
所在的文件系统,包括所有挂载点中的目录和文件。
实例
uselazy_static::lazy_static;uselibc::{__s32,__u16,__u32,__u64,__u8};usenix::poll::{poll,PollFd,PollFlags};usesignal_hook::{consts::SIGTERM,low_level::pipe};usestd::os::unix::net::UnixStream;usestd::{env,ffi,fs,io,mem,os::fd::AsRawFd,path::PathBuf,slice};#[derive(Debug,Clone,Copy)]#[repr(C)]structFanotifyEvent{event_len:__u32,vers:__u8,reserved:__u8,metadata_len:__u16,mask:__u64,fd:__s32,pid:__s32,}lazy_static!{staticrefFAN_EVENT_METADATA_LEN:usize=mem::size_of::();}constFAN_CLOEXEC:u32=0x0000_0001;constFAN_NONBLOCK:u32=0x0000_0002;constFAN_CLASS_CONTENT:u32=0x0000_0004;constO_RDONLY:u32=0;constO_LARGEFILE:u32=0;constFAN_MARK_ADD:u32=0x0000_0001;constFAN_MARK_MOUNT:u32=0x0000_0010;//constFAN_MARK_FILESYSTEM:u32=0x00000100;constFAN_ACCESS:u64=0x0000_0001;constFAN_OPEN:u64=0x0000_0020;constFAN_OPEN_EXEC:u64=0x00001000;constAT_FDCWD:i32=-100;constFAN_EVENT_ON_CHILD:u64=0x08000000;constFAN_ONDIR:u64=0x4000_0000;//初始化fanotify,调用libc的函数fninit_fanotify()->Result{unsafe{matchlibc::fanotify_init(FAN_CLOEXEC|FAN_CLASS_CONTENT|FAN_NONBLOCK,O_RDONLY|O_LARGEFILE,){-1=>Err(io::Error::last_os_error()),fd=>Ok(fd),}}}fnmark_fanotify(fd:i32,path:&str)->Result<(),io::Error>{letpath=ffi::CString::new(path)?;unsafe{matchlibc::fanotify_mark(fd,//FAN_MARK_ADD,FAN_MARK_ADD|FAN_MARK_MOUNT,//FAN_MARK_ADD|FAN_MARK_FILESYSTEM,FAN_OPEN|FAN_ACCESS|FAN_OPEN_EXEC|FAN_EVENT_ON_CHILD,AT_FDCWD,path.as_ptr(),){0=>Ok(()),_=>Err(io::Error::last_os_error()),}}}fnread_fanotify(fanotify_fd:i32)->Vec{letmutvec=Vec::new();unsafe{letbuffer=libc::malloc(*FAN_EVENT_METADATA_LEN*1024);letsizeof=libc::read(fanotify_fd,buffer,*FAN_EVENT_METADATA_LEN*1024);letsrc=slice::from_raw_parts(bufferas*mutFanotifyEvent,sizeofasusize/*FAN_EVENT_METADATA_LEN,);vec.extend_from_slice(src);libc::free(buffer);}vec}//fanotifyevent只有fd,需要手动获取对应的pathfnget_fd_path(fd:i32)->io::Result{letfd_path=format!("/proc/self/fd/{fd}");fs::read_link(fd_path)}fnmain()->io::Result<()>{letargs:Vec=env::args().collect();ifargs.len()<2{eprintln!("Usage:{}",args[0]);std::process::exit(1);}letpath_buf=PathBuf::from(&args[1]);letpath=path_buf.to_str().unwrap_or(".");letfanotify_fd=init_fanotify()?;mark_fanotify(fanotify_fd,path)?;let(read,write)=UnixStream::pair()?;ifletErr(e)=pipe::register(SIGTERM,write){println!("failedtosetSIGTERMsignalhandler{e:?}");}letmutfds=[PollFd::new(fanotify_fd.as_raw_fd(),PollFlags::POLLIN),PollFd::new(read.as_raw_fd(),PollFlags::POLLIN),];loop{matchpoll(&mutfds,-1){Ok(polled_num)=>{ifpolled_num<=0{eprintln!("polled_num<=0!");break;}ifletSome(flag)=fds[0].revents(){ifflag.contains(PollFlags::POLLIN){letevents=read_fanotify(fanotify_fd);foreventinevents{handle_event(event)?;}}}ifletSome(flag)=fds[1].revents(){ifflag.contains(PollFlags::POLLIN){println!("receivedSIGTERMsignal");break;}}}Err(e)=>{ife==nix::Error::EINTR{continue;}eprintln!("Pollerror{:?}",e);break;}}}Ok(())}fnclose_fd(fd:i32){unsafe{libc::close(fd);}}fnhandle_event(event:FanotifyEvent)->io::Result<()>{letfd=event.fd;letevent_mask=event.mask;letkind=if(event_mask&FAN_ONDIR)!=0{"directory"}else{"file"};letpath=get_fd_path(fd)?;println!("{}{}mask{:b}.",kind,path.to_string_lossy(),event_mask);close_fd(fd);Ok(())}
- 参数:
flags
默认值 +FAN_EVENT_ON_CHILD
,path
为目录:
监控 path
目录下所有文件的文件系统事件。
参数:
flags
默认值 +FAN_EVENT_ON_CHILD
,path
为容器rootfs
目录,无法监听到事件。(不能跨 mount namespace[7])参数:
flags
默认值,传入目录但是没有FAN_ONDIR
:不能监听到事件。参数:
flags
默认值 +FAN_ONDIR
:
监听到 path
目录本身的文件系统事件。
参数:
flags
FAN_MARK_MOUNT
,path
为目录:监听到path
所在挂载点的事件。参数:
flags
FAN_MARK_MOUNT
,path
为挂载点下的文件:
mount--bindtestfatest
监听到挂载点下所有文件(包括子目录)的文件系统事件(dir1/file2.txt 是 path 子目录中的文件)。
参数:
flags
FAN_MARK_MOUNT
,path
为容器rootfs
目录,无法监听到事件。(不能跨 mount namespace)参数:
flags
FAN_MARK_FILESYSTEM
,path
为目录,监听到目录所在文件系统的事件。包括其它挂载点。参数:
flags
FAN_MARK_FILESYSTEM
,path
为容器rootfs
:
可以监听到 rootfs 下的文件系统事件,但看起来不完整,例如,下面的例子中,正确的结果应包括访问 /etc/hosts 的事件。
因此,使用 fanotify
监听容器 rootfs
中文件系统的最终解决方案:进入容器所在的 mount namespace,使用 FAN_MARK_MOUNT
flag
监听容器的根目录,即可递归监听容器中所有文件的事件。
Setns
基本介绍
setns[8] 是 Linux 的系统调用,允许进程切换到另一个进程所在的命名空间。Linux 中的命名空间提供了内核级别的资源隔离,不同命名空间中的程序享有独立的资源。目前,提供了 8 种资源隔离:
Mount
: 文件系统挂载点,flag
:CLONE_NEWNS
(mount namespace 是最早提出的命名空间,所以flag
定为CLONE_NEWNS
,而不是CLONE_NEWMNT
)UTS
: 主机名和域名信息,flag
:CLONE_NEWUTS
IPC
: 进程间通信,flag
:CLONE_NEWIPC
PID
: 进程 ID,flag
:CLONE_NEWPID
Network
: 网络资源,flag
:CLONE_NEWNET
User
: 用户和用户组的 ID,flag
:CLONE_NEWUSER
CGROUP
:Cgroup 资源,flag
:CLONE_NEWCGROUP
(从 Linux 4.6 开始支持)Time
:时间资源,flag
:CLONE_NEWTIME
(从 Linux 5.8 开始支持)
Linux 中操作命名空间除了 setns
,还有 clone
和 unshare
系统调用。
setns:给已存在进程设置已存在的命名空间。
clone:创建新进程时,使用新的命名空间。(默认使用父进程的命名空间)
unshare:让已存在进程使用新的命名空间。
API 介绍
函数声明如下:
#define_GNU_SOURCE/*Seefeature_test_macros(7)*/#includeintsetns(intfd,intnstype);
fd 和已存在进程有关:
- 已存在进程 /proc/[pid]/ns/ 目录下的不同命名空间对应文件的 fd:
nstype
指定命名空间的类型,包括以下值:
0
:任意类型(最好在知道 fd 指向命名空间类型时使用)CLONE_NEWCGROUP
:fd 必须指向 cgroup 命名空间(从 Linux 4.6 开始支持),调用者需拥有CAP_SYS_ADMIN
能力,setns 不会修改原 cgroup 中子 cgroup 的命名空间。CLONE_NEWIPC
:fd 必须指向 IPC 命名空间(从 Linux 3.0 开始支持),在原 user 命名空间和目标命名空间都需要有CAP_SYS_ADMIN
能力。CLONE_NEWNET
:fd 必须指向 network 命名空间(从 Linux 3.0 开始支持),在原 user 命名空间和目标命名空间都需要有CAP_SYS_ADMIN
能力。CLONE_NEWNS
:fd 必须指向 mount 命名空间(从 Linux 3.8 开始支持),在原命名空间需要有CAP_SYS_CHROOT
和CAP_SYS_ADMIN
能力,在目标命名空间都需要有CAP_SYS_ADMIN
能力。如果和其它进程(通过clone
的CLONE_FS
实现)共享文件系统属性,则不能加入新的 mount 命名空间。CLONE_NEWPID
:fd 必须指向 PID 命名空间(从 Linux 3.8 开始支持),在原 user 命名空间和目标命名空间都需要有CAP_SYS_ADMIN
能力。PID 和其它命名空间不同,sentns 加入 PID 命名空间之后,并不会修改 caller 的 PID 命名空间,加入之后,由 caller 创建的子进程使用新的 PID 命名空间。CLONE_NEWTIME
:fd 必须指向 time 命名空间(从 Linux 5.8 开始支持),在原 user 命名空间和目标命名空间都需要有CAP_SYS_ADMIN
能力。CLONE_NEWUSER
:fd 必须指向 user 命名空间(从 Linux 3.8 开始支持),必须在目标命名空间有CAP_SYS_ADMIN
能力。多线程程序加入 user 命名空间可能失败。不允许使用 setns 再次进入 caller 所在的 user 命名空间。出于安全原因考虑,如果和其它进程(通过clone
的CLONE_FS
实现)共享文件系统属性,则不能加入新的 user 命名空间。CLONE_NEWUTS
:fd 必须指向 UTS 命名空间(从 Linux 3.0 开始支持),在原 user 命名空间和目标命名空间都需要有CAP_SYS_ADMIN
能力。
- 进程 PID 的文件描述符(详见 pidfd_open[9],Linux 5.8 开始支持)
nstype
指定要加入的命名空间类型。例如:要加入 PID 为 1234 所在的 USER、NET、UTS 命名空间,保持其它命名空间不变:
intfd=pidfd_open(1234,0);setns(fd,CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWUTS);
实例
usenix::{sched::{setns,CloneFlags},};usestd::{env,fs,io,os::fd::AsRawFd,path::Path,process::Command};#[derive(Debug)]enumSetnsError{IO(io::Error),Nix(nix::Error),}fnset_ns(ns_path:String,flags:CloneFlags)->Result<(),SetnsError>{letfile=fs::File::open(Path::new(ns_path.as_str())).map_err(SetnsError::IO)?;setns(file.as_raw_fd(),flags).map_err(SetnsError::Nix)}fnjoin_namespace(pid:String)->Result<(),SetnsError>{set_ns(format!("/proc/{pid}/ns/pid"),CloneFlags::CLONE_NEWPID)?;set_ns(format!("/proc/{pid}/ns/ipc"),CloneFlags::CLONE_NEWIPC)?;set_ns(format!("/proc/{pid}/ns/cgroup"),CloneFlags::CLONE_NEWCGROUP,)?;set_ns(format!("/proc/{pid}/ns/net"),CloneFlags::CLONE_NEWNET)?;set_ns(format!("/proc/{pid}/ns/mnt"),CloneFlags::CLONE_NEWNS)?;Ok(())}fnprint_ns(path:&str){letoutput=Command::new("/bin/ls").arg("-l").arg(path).output().expect("failedtoexecuteprocess");ifoutput.status.success(){println!("{}",String::from_utf8_lossy(&output.stdout));}else{println!("err:{}",String::from_utf8_lossy(&output.stderr));}}fnmain(){letargs:Vec=env::args().collect();ifargs.len()<2{eprintln!("Usage:{}",args[0]);std::process::exit(1);}print_ns("/proc/self/ns");ifletErr(e)=join_namespace(args[1].clone()){eprintln!("joinnamespacefailed{e:?}");return;}print_ns("/proc/self/ns");}
可以看到,进程的 PID、IPC、Cgroup、NET、MNT 命名空间已经设置为容器的命名空间。
部分细节:
如需加入多个命名空间,MNT 命名空间应该最后加入。(先加入 MNT 命名空间会导致接下来的 join 操作无法正确读取原
/proc/pid/ns
下的文件)在本文使用的测试环境,加入 USER 命名空间会出错,返回 EINVAL 信息。
Nsenter
nsenter
是 linux 中的命令行工具,位于 util-linux[10] 包中,用于进入目标进程的命名空间运行程序。
❯nsenter--helpUsage:nsenter[options][[...]]Runaprogramwithnamespacesofotherprocesses.Options:-a,--allenterallnamespaces-t,--targettargetprocesstogetnamespacesfrom-m,--mount[=]entermountnamespace-u,--uts[=]enterUTSnamespace(hostnameetc)-i,--ipc[=]enterSystemVIPCnamespace-n,--net[=]enternetworknamespace-p,--pid[=]enterpidnamespace-C,--cgroup[=]entercgroupnamespace-U,--user[=]enterusernamespace-T,--time[=]entertimenamespace-S,--setuidsetuidinenterednamespace-G,--setgidsetgidinenterednamespace--preserve-credentialsdonottouchuidsorgids-r,--root[=]settherootdirectory-w,--wd[=]settheworkingdirectory-W.--wdnssettheworkingdirectoryinnamespace-F,--no-forkdonotforkbeforeexec"ing
进入容器的 PID、Mount 命名空间:
nsenter-m-p-t113503bash
Fanotify 监控容器
通过 setns 加入容器所在的 PID、Mount 命名空间;
启动 fanotify server;
监听到 fanotify 事件,通过 readlink 得到 fd 对应的 path;(由于已经处于容器所在的 PID 命名空间,因此,可以直接通过
/proc/self/fd/{fd}
得到 fanotify 事件 fd 对应的 path)Server 通过 stdout 将包含 path 的 fanotify 事件发送给 client;(由于已经处于容器所在 mount 命名空间,不能直接通过 socket 等进程间通信方式传输给 client)
client 对 fanotify 事件进行处理。
完整代码参考:optimizer-server[11]。
总结
Inotify 支持在节点上监听容器 rootfs 下的目录和文件。
Inotify 不支持递归监控子目录和文件。
Fanotify 监听 inode 对象和挂载点不支持跨 mount namespace,因此不支持在节点上直接监听容器 rootfs 下的目录和文件。
Fanotify(除
directed
模式)支持递归监控目录下的子目录和文件。Fanotify 可以借助
setns
进入容器所在的 mount 命名空间,通过per-mount
模式实现递归监听容器rootfs
下的事件。
参考资料
[1]inode: https://en.wikipedia.org/wiki/Inode
[2]John McCutchan: http://johnmccutchan.com/
[3]inotify.7: https://man7.org/linux/man-pages/man7/inotify.7.html
[4]Fanotify: https://man7.org/linux/man-pages/man7/fanotify.7.html
[5]linux/fsnotify_backend.h#L185: https://github.com/torvalds/linux/blob/31a371e419c885e0f137ce70395356ba8639dc52/include/linux/fsnotify_backend.h#L185
[6]fanotify_init.2: https://man7.org/linux/man-pages/man2/fanotify_init.2.html
[7]mount namespace: https://man7.org/linux/man-pages/man7/mount_namespaces.7.html
[8]setns: https://man7.org/linux/man-pages/man2/setns.2.html
[9]pidfd_open: https://man7.org/linux/man-pages/man2/pidfd_open.2.html
[10]util-linux: https://github.com/util-linux/util-linux/blob/master/sys-utils/nsenter.c
[11]optimizer-server: https://github.com/containerd/nydus-snapshotter/tree/main/tools/optimizer-server
关键词:
-
环球速看:EF命令行工具 migrate.exe 进行Code First更新数据库,6.3+使用ef6.exe
EF命令行工具migrate exe进行CodeFirst更新数据库,6 3+使用ef6 exe使用EF的CodeFirst迁移可以用于从Visual
来源: 世界速讯:监听容器中的文件系统事件
第139篇:微信小程序的登录流程|天天讯息
环球速看:EF命令行工具 migrate.exe 进行Code First更新数据库,6.3+使用ef6.exe
4月皮卡销量排名出炉 长城江淮大增 新能源有待突破|世界时快讯
20230514学习笔记——将代码提交到码云中 天天要闻
当前速递!【LeetCode字符串#extra】KMP巩固练习:旋转字符串、字符串轮转
世界之最大全100条_世界之最大全
大模型三大能力超越ChatGPT 千亿AI巨头科大讯飞否认做手机
大爷买二等座票带孙女坐一等座被赶 12306回应:不允许乱坐 全球热点
观点:一起来学rust|简单的mingrep
关于Kubernetes-v1.23.6-网络组件-calico的安装部署...|焦点快看
【焦点热闻】好莱坞编剧大罢工:GPT技术引发激烈争议
经典28定律?苹果在中国手机市场份额2成 赚走8成利润|每日简讯
电池只能充电500次?别太荒谬!收下这份真正的充电秘籍
【全球新要闻】摊主买彩票中2千多万激动砸摊位:称21人合买 明天就分钱
CentOS7搭建keepalived+DRBD+NFS高可用共享存储
每日精选:惠普打印机禁用非原装墨盒:官方称是为用户安全考虑
当前焦点!地狱笑话?大学母亲节配图是《进击的巨人》
泥鳅汤做法_泥鳅汤的烹饪方法 环球实时
已在轨生活160多天 航天员费俊龙从太空发回对母亲的节日祝福
热播电视剧将本科写成大专 高校不干了 官方道歉|天天聚看点
【热闻】首发紫光展锐T750!海信悄然推出F70 Lite手机
迭部县气象台发布大风蓝色预警信号【2023-05-14】
three.js 入门学习(二) 环球热资讯
DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃
世界消息!专家称电动车要发展农村型号:支持反向充电 可增加收入
环球热门:1-2!“全校班”广州队又输了!3连败+开局5轮不胜,直冲降级区
基于SLAM系统建图仿真,完成定位仿真
天天观速讯丨安卓一年一迭代谷歌也累了:开始挤牙膏更新
女子把变心男友送的黄金卖了14万:没真心但有真金!自愿赠与或不用返还_天天快看
升级彩超5项:瑞慈体检套餐279元母亲节大促 今日热搜
久穿不易变形 放克220g宽松短袖29元大促
徐工四款新“国货之光”问世:百变狮王、自动灭火机器人 国产化率100% 世界观焦点
万胜智能: 关于使用部分闲置募集资金进行现金管理的进展公告
美团一面:Spring Cloud 如何构建动态线程池?
动态焦点:每天走路超这一步数 能大幅降低死亡率 上班族学起来
B站“离谱”专利获批:开车也能发弹幕了?|新要闻
热议:俄媒:泽连斯基拒绝教皇方济各调解俄乌提议
世界热议:我对IdentityServer4的初步了解
每日关注!JavaSE面试题【长期更新】
想玩《塞尔达传说:王国之泪》却不知道买哪款Switch?这篇选购攻略帮你避坑!
CPU散片学问大:碰见这两个型号千万别买|今日热搜
01-Linux命令和C语言基础|全球快讯
全球今亮点!西湖5平米商亭租金284万 每天约7780元引热议
母亲节 我来讲一个给妈妈换了“苹果全家桶”后的故事
首次发现!唾液含剧毒的五爪金龙现身云南:寿命长达150年
每日短讯:2023年5月14日融雪剂价格最新行情预测
国金证券:稳增长政策效果加速显现 居民消费修复延续性较强
D加密沦陷!黑客放出《生化危机4重制版》破解资源:好评如潮大作免费玩
AI起了反效果:4月微软Bing市场份额不升反降
特斯拉雨天高速失控!旋转、掉头、撞墙后 司机接着加速跑了
【报资讯】怀旧服磨刀石是什么专业制作的(怀旧服磨刀石)
前端语言串讲 | 青训营笔记
女子夜里打出租 全程直播监控!司机:不自信了_当前焦点
环球快资讯丨最后一道封印解除!ChatGPT重大升级 上线联网功能
检察院不批捕取保候审后还会收监吗|世界快播
女子旅游后高烧不退确诊“不死癌症” 医生:晒太阳是重要诱因
性能完全不达标 EPA报告:特斯拉4680电池能量密度比2170还低 当前观察
landrover是什么车多少钱一辆 landrover是什么车
Python学习之六_同时访问Oracle和Mysql的方法
上海张江全链条发力营造更优企业创新发展环境 今日快讯
放弃ZEKU自研芯片!OPPO张璇:产品生命周期软件维护不受影响_世界速读
江苏扬州:体育嘉年华嗨出狂欢味 百余场赛事活动贯穿全年-全球播资讯
曾为中国最大的汽车经销商 庞大集团濒临退市
今天母亲节 妈妈收到孩子送礼物时的反应让千万网友动容-全球关注
每日焦点!自称长相比较可爱28岁女副教授回应带梗招生:院方支持新表达方式
Windows 10操作系统绝唱了!终极正式版开始强制升级
天天快讯:保定市区养犬收费标准来了!登记500/300,年检200!
学系统集成项目管理工程师(中项)系列21b_整体管理(下)
母亲节今天到来!微信上线限时状态:感谢妈妈 天天动态
卖给中国人的车 连玻璃都减配? 全球快看点
大哥13 Ultra同款!小米13/Pro相机界面升级:变焦转盘调焦更方便
成都市验房公司_成都验房公司
Java Socket编程|环球聚看点
Ubuntu下通过Wine安装LTSpice 17.1.8_当前讯息
环球聚焦:讯飞输入法推出苹果 macOS 版,支持 10.15 及以上版本
当前快看:江苏女子到山东旅游买到的特产竟是戒尺:自己之前根本没有见过
环球速读:80、90后的青春记忆!《街霸》过气了吗?
iPhone用户被骗子盯上!三招轻松破解
北京一车主遇无接触事故被认定负全责 骑车人自己滑倒:网友吵翻
百万召回能解决单踏板电门当刹车?特斯拉回应:选择权给大家 误踩会提醒
莱州市永安路街道:帮办代办暖心解忧 架起为民服务“连心桥”
23岁网红用GPT-4复制自己,每月狂赚3500万 当前播报
Prompt learning 教学[案例篇]:文生文案例设定汇总,你可以扮演任意角色进行专业分析-天天即时
七孔大豆纤维夏被到手59元:牛奶般丝滑 亲肤透气 今日报
何炅录制芒果TV《向往的生活》:手机真我11 Pro+抢镜
安卓机皇!三星Galaxy S23 Ultra限量版上市:9488元 全球速读
淄博八大局知名麻辣串疑被房东赶走:老板回应双方还在商讨此事 世界视讯
厦门英才学校小学部第三套课间操_厦门英才学校小学部-天天热消息
今日快讯:算命奇想
就没有《猫和老鼠》还原不了的图!AI被锤爆了
热门:中性笔后面的神秘液体是什么?竟然大有讲究!
今日讯!德州驴出肉率_德州驴
小米13 Pro被低估了!雷军力荐:数码发烧友就选它|观点
通讯!00后女生旅游不忘给新手机开光 网友:你是懂开光的
焦点资讯:《暗黑4》遇上DLSS 3:最低帧猛增50%
迁移到 Gradle 7.x 使用 Version Catalogs 管理依赖
焦点热议:线段树
【LeetCode剑指offer#04】包含min函数的栈、栈的压入、弹出序列(辅助栈的应用)
环球速读:51岁已被游客喊了十多年谭爷爷!熊猫饲养员谭金淘“出圈”