Reimplementation of lxc-device

来源:互联网 发布:写作软件 知乎 编辑:程序博客网 时间:2024/05/20 07:19

     最近对lxc比较感兴趣,作为一个new comer to lxc,我希望找一些工作可以作。在mail list中,我找到了一封

关于attach_interface的邮件。

The python3 module does support it (which is why lxc-device supports ittoo) by using the namespace capabilities of the ip command. It'dprobably be nice to re-implement that in C with netlink as add_interfaceand remove_interface so that it's available to all API users (patchesare 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 interface1571          * name provided implies that a name change has been1572          * 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。 下面会继续这些方面的记录。


这是我的第一篇记录文章,总体来起来比较乱。感觉自己学习的过程中探索了很多,有时候会忘了。于是在别人的建议下想用这种方式记录下来。也是为了以后想起来自己做过

些什么。无数次无功而返之后,今天第一次写了些东西,留给自己以后回想。

0 0