我在上一篇日志用unshare创建轻量级虚拟环境中介绍了如何用系统调用unshare创建一个虚拟机. 这一个方法正是参照了mininet的实现方式, 我将其用C语言实现, 达到了mininet中创建虚拟网络节点(包括主机和交换机)的效果. 这一篇文章中, 我想直接引用mininet的代码来阐述实现的方法. 除此之外, 我还会介绍mininet识别网络节点输出的结束, 以及中断进程,接收命令, 创建虚拟网卡等功能的实现.
在介绍之前, 我想介绍一下mnexec.c. 看过mininet代码的童鞋都知道它是用python写的, 但是它却有且只有一个C语言写的文件-mnexec.c, 十分碍眼. 在我看来, mnexec.c正是mininet的灵魂, 所有的操作就必须在它存活的时候进行. 在安装mininet的过程中, 这个C文件会被编译并输出成一个叫”mnexec”的文件, 放在 /usr/local/bin/目录下. 也就是说, 在mininet安装完成后, 除了我们熟悉的”mn”命令, 还生成了另外一个shell命令-“mnexec”(欢迎没事干的童鞋验证). 下面是这个命令的帮助说明:
usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...
-c: close all file descriptors except stdin/out/error
-d: detach from tty by calling setsid()
-n: run in new network namespace
-p: print ^A + pid
-a pid: attach to pid's network namespace
-g group: add to cgroup
-r rtprio: run with SCHED_RR (usually requires -g)
“-c”/”-d”大概是为了避免意外错误, “-p”我会在后面介绍, “-a”是省事儿的时候用的(不常用), “-g”/”-r”我没研究过=,=. 我是不是漏了”-n”了? 我故意的! 如果加了这个参数的, 就基本能创建一个网络节点了. 来看看”-n”执行的是哪段代码:
/* run in network namespace */
if (unshare(CLONE_NEWNET) == -1) {
perror("unshare");
return 1;
}
break;
unshare! 看到没有! 这正是用来从操作系统中隔离并克隆出一个网络名字空间的系统调用啊. 有了它, 执行mnexec命令的那个进程就有了一块独立的网络环境, 离独立的网络节点就不远了.
unshare! 读了我上一篇博文的童鞋可能知道, 还需要运行起一个bash, 这主要是靠mnexec.c下面这一段代码实现的
这段代码会处理mnexec命令其他的参数.
mnexec.c要介绍的大概就这么多, 接下来要讲它与mininet创建网络节点之间的关系. mininet中网络节点的操作主要是由Node()类来实现. 创建一个网络节点就是在Node()类初始化的时候进行的. 在Node().__init__()中, 可以看到下面的代码:
# (p)rint pid, and run in (n)amespace
opts = '-cdp'
if self.inNamespace:
opts += 'n'
# bash -m: enable job control
cmd = [ 'mnexec', opts, 'bash', '-m' ]
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=True )
这里Popen函数是Subprocess库里的Popen函数, 它的功能是执行shell命令”cmd”并且用一个管道将该命令的进程与使用该函数的进程连通. 根据之前我对mnexec的介绍, 这段代码的作用就是用mnexec命令创建一个有独立网络名字空间并且执行着bash的进程, 进程的标准输入/输出/错误都指向管道. 如此以来, 当创建一个Node()时, 就能得到一个有独立网络环境的节点.
用户输入的shell命令会通过管道传给网络节点, 之后mininet会再从管道中获得网络节点的输出. 由于输出是不以EOF结束的, 所以判断输出的结束需要特殊的处理. 在mininet中, 为了识别出输出的结束, 它除了让网络节点打印出用户输入的shelle命令的输出外, 还让网络节点在输出结束后打印一个不常用的字符.
# print ^A{pid}n{sentinel}
cmd += ' printf "