linux kernel-4.9.37
Network namespaces
网络命名空间可以理解为是完全独立丝毫没有相互影响的另一套协议栈。
Q1:网络命名空间之间为什么不能通讯?
A1:因为没有网卡设备。真实的网卡设备一般在 根命名空间使用。需要是由 veth(virtual Ethernet) 充当网络命名空间的网卡设备。
Q2:网络命名空间之间的网络通讯流程是什么样的?
A1:具体就流程就是 veth 的工作流程,会将数据包直接塞到接收侧的 rx 队列中。
参考1: veth跨命名空间通信
iprout2-5.9.0
先从 networking namspace 的命令进行分析,进而了解整个 networking namespace 的构成。
ip netns list #列出网络命名空间
ip netns add NAME #添加网络命名空间
下面就是 netns 参数可以使用的全部操作,暂时重点只关注 list 和 add
int do_netns(int argc, char **argv)
{
netns_nsid_socket_init();
if (argc < 1) {
netns_map_init();
return netns_list(0, NULL);
}
if (!do_all && argc > 1 && invalid_name(argv[1])) {
fprintf(stderr, "Invalid netns name \"%s\"\n", argv[1]);
exit(-1);
}
if ((matches(*argv, "list") == 0) || (matches(*argv, "show") == 0) ||
(matches(*argv, "lst") == 0)) {
netns_map_init();
return netns_list(argc-1, argv+1);
}
if ((matches(*argv, "list-id") == 0)) {
netns_map_init();
return netns_list_id(argc-1, argv+1);
}
if (matches(*argv, "help") == 0)
return usage();
if (matches(*argv, "add") == 0)
return netns_add(argc-1, argv+1, true);
if (matches(*argv, "set") == 0)
return netns_set(argc-1, argv+1);
if (matches(*argv, "delete") == 0)
return netns_delete(argc-1, argv+1);
if (matches(*argv, "identify") == 0)
return netns_identify(argc-1, argv+1);
if (matches(*argv, "pids") == 0)
return netns_pids(argc-1, argv+1);
if (matches(*argv, "exec") == 0)
return netns_exec(argc-1, argv+1);
if (matches(*argv, "monitor") == 0)
return netns_monitor(argc-1, argv+1);
if (matches(*argv, "attach") == 0)
return netns_add(argc-1, argv+1, false);
fprintf(stderr, "Command \"%s\" is unknown, try \"ip netns help\".\n", *argv);
exit(-1);
}
netns list 命令会在 NETNS_RUN_DIR(/var/run/netns) 目录中找到信息,并将这些信息通过 netns_map_add 函数组成 hash 结构,便于以后使用。
再之后会通过 netns_list 函数将信息打印。
void netns_map_init(void)
{
static int initialized;
struct dirent *entry;
DIR *dir;
int nsid;
if (initialized || !ipnetns_have_nsid())
return;
dir = opendir(NETNS_RUN_DIR);
if (!dir)
return;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0)
continue;
if (strcmp(entry->d_name, "..") == 0)
continue;
nsid = get_netnsid_from_name(entry->d_name);
if (nsid >= 0)
netns_map_add(nsid, entry->d_name);
}
closedir(dir);
initialized = 1;
}
netns add NAME 命令
这个函数创建了一个新的网络命名空间,然后挂载该命名空间到一个文件系统上众所周知的位置上。挂载名称使用该命名空间的名称。
下面函数的主要逻辑:
static int netns_add(int argc, char **argv, bool create)
{
/* This function creates a new network namespace and
* a new mount namespace and bind them into a well known
* location in the filesystem based on the name provided.
*
* If create is true, a new namespace will be created,
* otherwise an existing one will be attached to the file.
*
* The mount namespace is created so that any necessary
* userspace tweaks like remounting /sys, or bind mounting
* a new /etc/resolv.conf can be shared between users.
*/
char netns_path[PATH_MAX], proc_path[PATH_MAX];
const char *name;
pid_t pid;
int fd;
int made_netns_run_dir_mount = 0;
if (create) {
if (argc < 1) {
fprintf(stderr, "No netns name specified\n");
return -1;
}
} else {
if (argc < 2) {
fprintf(stderr, "No netns name and PID specified\n");
return -1;
}
if (get_s32(&pid, argv[1], 0) || !pid) {
fprintf(stderr, "Invalid PID: %s\n", argv[1]);
return -1;
}
}
name = argv[0];
snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
if (create_netns_dir())
return -1;
/* Make it possible for network namespace mounts to propagate between
* mount namespaces. This makes it likely that a unmounting a network
* namespace file in one namespace will unmount the network namespace
* file in all namespaces allowing the network namespace to be freed
* sooner.
*/
while (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) {
/* Fail unless we need to make the mount point */
if (errno != EINVAL || made_netns_run_dir_mount) {
fprintf(stderr, "mount --make-shared %s failed: %s\n",
NETNS_RUN_DIR, strerror(errno));
return -1;
}
/* Upgrade NETNS_RUN_DIR to a mount point */
if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none", MS_BIND | MS_REC, NULL)) {
fprintf(stderr, "mount --bind %s %s failed: %s\n",
NETNS_RUN_DIR, NETNS_RUN_DIR, strerror(errno));
return -1;
}
made_netns_run_dir_mount = 1;
}
/* Create the filesystem state */
fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0);
if (fd < 0) {
fprintf(stderr, "Cannot create namespace file \"%s\": %s\n",
netns_path, strerror(errno));
return -1;
}
close(fd);
if (create) {
netns_save();
if (unshare(CLONE_NEWNET) < 0) {
fprintf(stderr, "Failed to create a new network namespace \"%s\": %s\n",
name, strerror(errno));
goto out_delete;
}
strcpy(proc_path, "/proc/self/ns/net");
} else {
snprintf(proc_path, sizeof(proc_path), "/proc/%d/ns/net", pid);
}
/* Bind the netns last so I can watch for it */
if (mount(proc_path, netns_path, "none", MS_BIND, NULL) < 0) {
fprintf(stderr, "Bind %s -> %s failed: %s\n",
proc_path, netns_path, strerror(errno));
goto out_delete;
}
netns_restore();
return 0;
out_delete:
if (create) {
netns_restore();
netns_delete(argc, argv);
} else if (unlink(netns_path) < 0) {
fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n",
netns_path, strerror(errno));
}
return -1;
}
ip netns exec namespace ip a s
通过这个命令可以看到 namespace 命名空间中的网络地址情况。
-cmd_exec 函数会通过执行 do_switch 函数切换到命名空间,之后使用 execvp 命令。
在 do_switch 中几乎和添加命名空间类似,也是使用 unshare 函数和 setns 函数进行切换。
static int netns_exec(int argc, char **argv)
{
/* Setup the proper environment for apps that are not netns
* aware, and execute a program in that environment.
*/
if (argc < 1 && !do_all) {
fprintf(stderr, "No netns name specified\n");
return -1;
}
if ((argc < 2 && !do_all) || (argc < 1 && do_all)) {
fprintf(stderr, "No command specified\n");
return -1;
}
if (do_all)
return netns_foreach(on_netns_exec, argv);
/* ip must return the status of the child,
* but do_cmd() will add a minus to this,
* so let's add another one here to cancel it.
*/
return -cmd_exec(argv[1], argv + 1, !!batch_mode, do_switch, argv[0]); // 可以看出最关键的就是这个函数
}
int cmd_exec(const char *cmd, char **argv, bool do_fork,
int (*setup)(void *), void *arg)
{
fflush(stdout);
if (do_fork) {
int status;
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid != 0) {
/* Parent */
if (waitpid(pid, &status, 0) < 0) {
perror("waitpid");
exit(1);
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
}
exit(1);
}
}
if (setup && setup(arg))
return -1;
if (execvp(cmd, argv) < 0)
fprintf(stderr, "exec of \"%s\" failed: %s\n",
cmd, strerror(errno));
_exit(1);
}
static int do_switch(void *arg)
{
char *netns = arg;
/* we just changed namespaces. clear any vrf association
* with prior namespace before exec'ing command
*/
vrf_reset();
return netns_switch(netns);
}
int netns_switch(char *name)
{
char net_path[PATH_MAX];
int netns;
unsigned long mountflags = 0;
struct statvfs fsstat;
snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
netns = open(net_path, O_RDONLY | O_CLOEXEC);
if (netns < 0) {
fprintf(stderr, "Cannot open network namespace \"%s\": %s\n",
name, strerror(errno));
return -1;
}
if (setns(netns, CLONE_NEWNET) < 0) {
fprintf(stderr, "setting the network namespace \"%s\" failed: %s\n",
name, strerror(errno));
close(netns);
return -1;
}
close(netns);
if (unshare(CLONE_NEWNS) < 0) {
fprintf(stderr, "unshare failed: %s\n", strerror(errno));
return -1;
}
/* Don't let any mounts propagate back to the parent */
if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL)) {
fprintf(stderr, "\"mount --make-rslave /\" failed: %s\n",
strerror(errno));
return -1;
}
/* Mount a version of /sys that describes the network namespace */
if (umount2("/sys", MNT_DETACH) < 0) {
/* If this fails, perhaps there wasn't a sysfs instance mounted. Good. */
if (statvfs("/sys", &fsstat) == 0) {
/* We couldn't umount the sysfs, we'll attempt to overlay it.
* A read-only instance can't be shadowed with a read-write one. */
if (fsstat.f_flag & ST_RDONLY)
mountflags = MS_RDONLY;
}
}
if (mount(name, "/sys", "sysfs", mountflags, NULL) < 0) {
fprintf(stderr, "mount of /sys failed: %s\n",strerror(errno));
return -1;
}
/* Setup bind mounts for config files in /etc */
bind_etc(name);
return 0;
}
内核中 setns 函数的具体作用。
SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{
struct task_struct *tsk = current;
struct nsproxy *new_nsproxy;
struct file *file;
struct ns_common *ns;
int err;
file = proc_ns_fget(fd); // 获得文件结构
if (IS_ERR(file))
return PTR_ERR(file);
err = -EINVAL;
ns = get_proc_ns(file_inode(file)); // 获得文件中的 ns_common 结构
if (nstype && (ns->ops->type != nstype))
goto out;
new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs); // 创建新的命名空间
if (IS_ERR(new_nsproxy)) {
err = PTR_ERR(new_nsproxy);
goto out;
}
err = ns->ops->install(new_nsproxy, ns); // 将新的命名空间与文件进行关联
if (err) {
free_nsproxy(new_nsproxy);
goto out;
}
switch_task_namespaces(tsk, new_nsproxy); // 将当前任务移动至新的命名空间
out:
fput(file);
return err;
}
#include
int mount(const char *source, const char *target,
const char *filesystemtype, unsigned long mountflags,
const void *data);
#include
int unshare(int flags);
unshare() 允许进程(或线程)解除当前与其他进程(或线程)共享的执行上下文部分的关联。
其阐述基本和 setns 函数的第二个参数相同。重点看一下上面代码中使用的 CLONE_NEWNET 参数。
该参数会使得将当前进程移动到一个新的与之前进程不共享的命名空间中。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include
int setns(int fd, int nstype);
给定一个引用命名空间的文件描述符,将其与调用进程重新关联。将当前进程加入一个已存在的网络命名空间。
参数 fd 是一个 /proc/[pid]/ns 的一个文件描述符。将根据 nstype 的不同,调用线程将重新关联响应的命名空间。
nstype可以是一下几种选项:
man netns
或者 Linux系统调用-- mount/umount函数详解wireshark 过滤规则
netlink-route.ifla_ifname == "veth0"
https://www.cnblogs.com/linhaifeng/p/6657119.html
https://blog.csdn.net/zhanglidn013/article/details/70241732
https://www.cnblogs.com/-xuan/p/10838052.html
https://blog.csdn.net/supahero/article/details/100606953
https://blog.csdn.net/guotianqing/article/details/82356096