最近对lxc比较感兴趣,作为一个new comer to lxc,我希望找一些工作可以作。在mail list中,我找到了一封
关于attach_interface的邮件。
The python3 module does support it (which is why lxc-device supports it too) by using the namespace capabilities of the ip command. It'd probably be nice to re-implement that in C with netlink as add_interface and remove_interface so that it's available to all API users (patches are welcome!).
于是,重写lxc-device就加到了我的TODO.lxc当中。经过几天对lxc的使用了解以及实现的简单熟悉之后。我发出了第一版patch Rewrite lxc-device.
patch通过使用netlink来与kernel交互并完成对netdev的netns的设置,以达到attach和detach的效果。下面分三个章节介绍一下在这个过程中了解到的知识。也是
为自己记录一下吧。
(1)lxc简介。
其实container这个概念在很多地方都会使用到,从kernel到userspace。我这里所说的lxc指的是 https://linuxcontainers.org/。
使用方法: 可以参考 Stgraber的blog: https://www.stgraber.org/2013/12/20/lxc-1-0-blog-post-series/。
代码位置: 现在lxc的代码可以在这个地方找到:https://github.com/lxc/lxc。
编译安装: 和其他项目类似,./autogen && ./configure && make && make install. 我比较喜欢使用(./configure --enable-dependency-tracking --enable-doc --enable-
api-docs --enable-examples --enable-python --enable-bash --enable-tests --enable-configpath-log --prefix=/usr)
代码框架: 进入代码,和大多数项目类似,都有doc,src等目录,主要实现代码都在src下面。进入src,主要是tests。lxc。python-lxc, lua-lxc
tests: 是测试代码, lxc是主要实现代码,python|lua-lxc是语言绑定的代码。
如上所述,lxc-device 现在是通过python调用ip 命令实现的。所以能找到一个叫做lxc-device的python脚本在 src/lxc/lxc-device
如图所试:
/-------------doc
|------config
|-------src/ ----------------tests
|--------include
|--------python-lxc
|---------lua-lxc
|---------lxc/-----------------lxc_start.c
|----------lxc_destroy.c
....
|-----------lxc-device (a python file)
现在的lxc-device主要工作流程如下:
if args.action == "add":
if os.path.exists("/sys/class/net/%s/" % args.device):
ret = container.add_device_net(args.device, args.name)
else:
ret = container.add_device_node(args.device, args.name)
其中, add_device_net() 是一个只有在python-lxc中实现的interface。所以,在struct lxc_container (src/lxc/lxccontainer.h)中没有这个函数。
也就是说,这种实现知识权宜之计,通过在python里面实现了一个add_device_net的函数,然后通过一个脚本调用这个函数以实现attach interface。
(2)netlink简介
如Stgraber 所说,要实现attach interface 并且不依赖与iprout2,就应该使用netlink在实现。庆幸的是lxc里面已经有了netlink模块。(src/lxc/nl.h|c).
netlink其实是一种userspace与kernelspace交流的一种方式。在kernel中我在这里主要关心几个功能。RTM_NEWLINK, RTM_GETLINK, RTM_DELLINK, RTM_SETLINK.
几个功能的实现在net/core/rtnetlink.c。 使用起来很简单,只需要新建一个socket(AF_NETLINK,...)然后想这个socket sendmsg在recvmsg就可以了。具体可以参考
http://www.linuxjournal.com/article/7356。
(3)lxc-device 的实现。
既然已经对lxc和netlink都有了了解。那么就来看一下要实现lxc-device需要做些什么吧。
1. 为struct lxc_container 添加两个interface,其实就是函数指针的属性。类似于面向对象。(该方法在很多项目中使用,比如kernel.vfs, kernel.scheduler,
kernel.driver). 并且需要使用netlink实现这两个函数。这是最主要的工作。
1.1:为了实现attach_interface(),
首先肯定是需要使用netlink设置device的netns到c->init_pid(c)的netns。简单的查看代码之后发现在lxc已经有了一个函数可以
完成这项工作,(lxc_netdev_move_by_name(const char *ifname, pid_t pid))该函数将指定的netdev move到指定的pid 的netns里面,但是为了方便,我为这个
函数添加了一个参数,const char* newname, 用来指定移动之后的netdev的name。在很多时候,我们移动一个netdev之后并不是原来的name。所以这个参数是
很有实用价值的。其实实现也是很简单的,只需要在发送给kernel的netlink信息里面追加一个条目标记(IFLA_IFNAME, ifname),到了内核里面,会查看因为已经指定
了index所以kernel会直到这是需要rename。
1569 /*
1570 * Interface selected by interface index but interface
1571 * name provided implies that a name change has been
1572 * requested.
1573 */
1574 if (ifm->ifi_index > 0 && ifname[0]) {
1575 err = dev_change_name(dev, ifname);
1576 if (err < 0)
1577 goto errout;
1578 modified = 1;
1579 }
其次,因为move的时候,如果netdev是up状态,会得到一个busy error。所以需要一个函数用来判断netdev是不是up。然而,现在的lxc里面没有实现
这样的一个函数,所以我添加了两个函数用来的到netdev的状态。
netdev_get_flag(): 用来得到netdev的flag
lxc_netdev_isup(): 调用netdev_get_flag()得到flag之后判断(flag & IFF_UP)至此,attach_interface() 就基本上实现结束了。
+static bool lxcapi_attach_interface(struct lxc_container *c, const char *ifname, + const char *dst_ifname) +{ + int ret = 0; + + ret = lxc_netdev_isup(ifname); + if (ret < 0) + goto err; + + /* netdev of ifname is up. */ + if (ret) { + ret = lxc_netdev_down(ifname); + if (ret) + goto err; + } + + ret = lxc_netdev_move_by_name(ifname, c->init_pid(c), dst_ifname); + if (ret) + goto err; + + return true; +err: + /* -EINVAL means there is no netdev named as ifanme. */ + if (ret == -EINVAL) { + ERROR("No network device named as %s.", ifname); + } + return false; +}1.2 为了实现detach_interface()
detach_interface()原理很简单,首先得到当前进程pid,outside_pid, 然后fork()一个子进程,设置子进程的netns为container的netns。以便可以访问到
需要操作的netdev。但是由于pidns还是host的pidns。所以我们可以在子进程中设置netdev的netns到outside_pid的netns。 这样就完成了detach的操作。
+static bool lxcapi_detach_interface(struct lxc_container *c, const char *ifname, + const char *dst_ifname) +{ + pid_t pid, pid_outside; + + pid_outside = getpid(); + + pid = fork(); + if (pid < 0) { + ERROR("failed to fork task to get interfaces information"); + return false; + } + + if (pid == 0) { // child + int ret = 0; + if (!enter_to_ns(c)) { + ERROR("failed to enter namespace"); + exit(-1); + } + + ret = lxc_netdev_isup(ifname); + if (ret < 0) + exit(ret); + + /* netdev of ifname is up. */ + if (ret) { + ret = lxc_netdev_down(ifname); + if (ret) + exit(ret); + } + + ret = lxc_netdev_move_by_name(ifname, pid_outside, dst_ifname); + + /* -EINVAL means there is no netdev named as ifanme. */ + if (ret == -EINVAL) { + ERROR("No network device named as %s.", ifname); + } + exit(ret); + } + + if (wait_for_pid(pid) != 0) + return false; + + return true; +}
2. 使用c语言并且使用新加入的两个接口实现lxc_device.c
这个工作基本上就是用 c语言将以前的python脚本重写一遍,不过添加了detach的功能。实现过程简单。需要注意的是修改Makefile文件。需要修改
src/lxc/Makefile.am 删除以前的lxc-device 添加lxc_device.c的编译方法。当然,作为lxc-device,不仅要处理interface,其他的device也是需要处理的,这个时候就设计到怎样分别是不是一个interface设备的问题了。而这其中最重要的
是在detach的时候,怎样进入到container里面判断device是不是一个netdevice。在以前的解决方法中,lxc-device是这样作的,查找/sys/class/net/%s/ 如果存在,则是一个
interface.但是在detach的时候,我们就需要进入到container的mountns 然后查找这个目录。而在现在的方法中,我们只有一个方便的方法可以进入到netns。所以在这个地方
我没有使用这个方法。而是使用了getifaddrs()。 如下。
+static bool is_interface(const char* dev_name, pid_t pid) +{ + pid_t p = fork(); + + if (p == 0) { + struct ifaddrs *interfaceArray = NULL, *tempIfAddr = NULL; + + switch_to_newnet(pid); + + /* Grab the list of interfaces */ + if (getifaddrs(&interfaceArray)) { + ERROR("failed to get interfaces list"); + exit(-1); + } + + /* Iterate through the interfaces */ + for (tempIfAddr = interfaceArray; tempIfAddr != NULL; tempIfAddr = tempIfAddr->ifa_next) { + if (strcmp(tempIfAddr->ifa_name, dev_name) == 0) { + exit(0); + } + } + exit(1); + } + + if (wait_for_pid(p) == 0) { + return true; + } + return false; +}
3. 添加这两个接口到python绑定当中,让python里面的Container类也有这两个接口
该工作主要使用 struct PyObject 来实现一个python绑定。没有深入研究,只是简单的按照既定格式完成了绑定工作
至此,我完成了lxc-device的重写工作,当然lua的绑定我没有做,因为我对lua实在是不了解。其实patch已经发出。也得到了一些ACK,但还没有全部apply。这几天的感觉是
lxc不是一个比较活跃的社区。不过只是为了了解一下这个东西。感觉这个项目还是比较有意思。尤其是一些实现比如lxc_attach, 和正在开发中的checkpooint/restore。 下面会继续这些方面的记录。
这是我的第一篇记录文章,总体来起来比较乱。感觉自己学习的过程中探索了很多,有时候会忘了。于是在别人的建议下想用这种方式记录下来。也是为了以后想起来自己做过
些什么。无数次无功而返之后,今天第一次写了些东西,留给自己以后回想。