Reimplementation of lxc-device

     最近对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。 下面会继续这些方面的记录。


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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值