深入解析docker内核网桥

news2024/11/15 8:02:27

今天做虚拟桌面,朋友问我,为什么vnc 连接另一个docker 容器一直超时,原因是在docker 启动的时候没有组网,那么接下来我就要解析下docker的内核网络。

我们思考几个问题,带你了解linux 中docker 网络实现的基本原理。

文章分析的基石来源于 Docker 的moby源码:

https://github.com/moby/moby

一、docker 网络组成

docker 默认有主机模式和网桥模式,这里只说网桥模式,我们说一下docker内核的网桥代码

先简单说一下容器,容器底层调用的是linux 的 clone,我们使用clone的时候有一些独特的标志位

CLONE_NEWNES,

CLONE_NEWUTS,

CLONE_NEWIPC,

CLONE_NEWPID,

CLONE_NEWNET 

使用linux内核的这些标志位,我们都可以创建出不同NS的进程,其中我们需要注意的是CLONE_NEWNET 

二、Docker 桥接模式分析

Docker Bridge 实现方式如下:

1)我们实现要考虑不同的两个进程 netns 是不一样的,那么这两个不同的netns之间如何通信?

我们可以使用linux 的 veth 接口 ,申请两个veth 设备假设是veth0 和 veth1。而veth pair 设备确保无论哪一个收到报文都会发送给另一端

2)我们把veth0 附加到 docker daemon 创建的docker0 网桥上。保证宿主机有能力把报文发送到veth0

3) 然后我们把 veth1 添加到容器所属的命名空间下,veth1 在Docker 容器里看来就是eth0。一方面保证网络报文发往veth0,可以被veth1收到,实现宿主机到docker 容器之间连通性。

1)问题反思?

docker 网桥是如何实现的?

docker 又是怎么通过建立linux 设备veth的?

docker 创建这些基础网络组件的过程中,到底是用了linux 内核的哪些特性?

2)docker 网桥是如何实现的?

go 部分

我们翻阅Docker 源码,发现这么一段

位于源码的

 daemon/daemon_unix.go

	// --ip processing
	if cfg.DefaultIP != nil {
		netOption[bridge.DefaultBindingIP] = cfg.DefaultIP.String()
	}

_, err = controller.NewNetwork("bridge", "bridge", "",
		libnetwork.NetworkOptionEnableIPv6(cfg.EnableIPv6),
		libnetwork.NetworkOptionDriverOpts(netOption),
		libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),
		libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc))

libnetwork/controller.go 

func (c *Controller) addNetwork(n *Network) error {
	d, err := n.driver(true)
	if err != nil {
		return err
	}

	// Create the network
	if err := d.CreateNetwork(n.id, n.generic, n, n.getIPData(4), n.getIPData(6)); err != nil {
		return err
	}

	n.startResolver()

	return nil
}

最后我们发现网桥的实现最后到了CreateNetwork中,我们在追踪进入CreateNetwork中

libnetwork/drivers/bridge/bridge_linux.go 

CreateNetwork 的实现我们发现具体关键代码位于

	// If the bridge interface doesn't exist, we need to start the setup steps
	// by creating a new device and assigning it an IPv4 address.
	bridgeAlreadyExists := bridgeIface.exists()
	if !bridgeAlreadyExists {
		bridgeSetup.queueStep(setupDevice)
		bridgeSetup.queueStep(setupDefaultSysctl)
	}

再继续看 setupDevice,一切水落石处,Docker 网桥创建的关键在于 setupDevice

func setupDevice(config *networkConfiguration, i *bridgeInterface) error {
	// We only attempt to create the bridge when the requested device name is
	// the default one. The default bridge name can be overridden with the
	// DOCKER_TEST_CREATE_DEFAULT_BRIDGE env var. It should be used only for
	// test purpose.
	var defaultBridgeName string
	if defaultBridgeName = os.Getenv("DOCKER_TEST_CREATE_DEFAULT_BRIDGE"); defaultBridgeName == "" {
		defaultBridgeName = DefaultBridgeName
	}
	if config.BridgeName != defaultBridgeName && config.DefaultBridge {
		return NonDefaultBridgeExistError(config.BridgeName)
	}

	// Set the bridgeInterface netlink.Bridge.
	i.Link = &netlink.Bridge{
		LinkAttrs: netlink.LinkAttrs{
			Name: config.BridgeName,
		},
	}

	// Set the bridge's MAC address. Requires kernel version 3.3 or up.
	hwAddr := netutils.GenerateRandomMAC()
	i.Link.Attrs().HardwareAddr = hwAddr
	log.G(context.TODO()).Debugf("Setting bridge mac address to %s", hwAddr)

	if err := i.nlh.LinkAdd(i.Link); err != nil {
		log.G(context.TODO()).WithError(err).Errorf("Failed to create bridge %s via netlink", config.BridgeName)
		return err
	}

	return nil
}
linux 接口部分:

既然我们找到了库的实现,那么我们继续思考netlink库为什么能调在linux 系统里创建网桥,它到底是使用了kernel 开发的哪些能力?

分析go netlink 库我发现了一个函数叫linkModify,他的一段代码实现如下:

	_, err := req.Execute(unix.NETLINK_ROUTE, 0)
	if err != nil {
		return err
	}

底层使用的是 

func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {
	fd, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC, protocol)
	if err != nil {
		return nil, err
	}
	s := &NetlinkSocket{
		fd: int32(fd),
	}
	s.lsa.Family = unix.AF_NETLINK
	if err := unix.Bind(fd, &s.lsa); err != nil {
		unix.Close(fd)
		return nil, err
	}

	return s, nil
}

也就是说Docker 的网络库最终使用的是

netlink socket API:
 socket()函数
        socket域(地址族)是AF_NETLINK
        socket类型是SOCK_RAW或SOCK_DGRAM,因为netlink是一种面向数据的服务
        netlink协议类型定义在netlink.h(以下以NETLINK_ROUTE为例),也可以自定义
AF_NETLINK 无疑是linux 中内核态和用户态通信的重要手段,他和ioctl的最大区别是,AF_NETLINK是一种协议族,提供了一种标准化的机制,用于用户态程序与内核之间进行网络相关的通信。它定义了不同的协议类型,如NETLINK_ROUTENETLINK_SELINUX等,以支持特定的网络操作和功能。使用AF_NETLINK,用户态程序可以发送特定类型的消息给内核,以请求或传递网络相关的信息。

docker 是使用AF_NETLINK 做的,而我们使用brctl 却是使用的ioctl

docker veth 设备是如何实现的

case *Veth:
		data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil)
		peer := data.AddRtAttr(nl.VETH_INFO_PEER, nil)
		nl.NewIfInfomsgChild(peer, unix.AF_UNSPEC)
		peer.AddRtAttr(unix.IFLA_IFNAME, nl.ZeroTerminated(link.PeerName))
		if base.TxQLen >= 0 {
			peer.AddRtAttr(unix.IFLA_TXQLEN, nl.Uint32Attr(uint32(base.TxQLen)))
		}
		if base.NumTxQueues > 0 {
			peer.AddRtAttr(unix.IFLA_NUM_TX_QUEUES, nl.Uint32Attr(uint32(base.NumTxQueues)))
		}
		if base.NumRxQueues > 0 {
			peer.AddRtAttr(unix.IFLA_NUM_RX_QUEUES, nl.Uint32Attr(uint32(base.NumRxQueues)))
		}
		if base.MTU > 0 {
			peer.AddRtAttr(unix.IFLA_MTU, nl.Uint32Attr(uint32(base.MTU)))
		}
		if link.PeerHardwareAddr != nil {
			peer.AddRtAttr(unix.IFLA_ADDRESS, []byte(link.PeerHardwareAddr))
		}
		if link.PeerNamespace != nil {
			switch ns := link.PeerNamespace.(type) {
			case NsPid:
				val := nl.Uint32Attr(uint32(ns))
				peer.AddRtAttr(unix.IFLA_NET_NS_PID, val)
			case NsFd:
				val := nl.Uint32Attr(uint32(ns))
				peer.AddRtAttr(unix.IFLA_NET_NS_FD, val)
			}
		}

我们发现还是AF_NETLINK 实现的

三、快速组网组成一个测试网络

创建网桥

ip link add br0 type bridge

启动网桥

ip link set br0 up

创建网络命名空间ns1 和ns2 

~# ip netns add ns1
~# ip netns add ns2

 创建 veth 对

~# ip link add veth0 type veth peer br-veth0
~# ip link add veth1 type veth peer br-veth1

查看创建的 veth 对,通过查看此时 veth对 在 root名称空间下

ip address show

将创建的 veth对 的一端插入到指定的名称空间 

~# ip link set veth0 netns ns1
~# ip link set veth1 netns ns2

通过进入不同的名称空间查看网卡的一端

~# ip netns exec ns1 ip a
~# ip netns exec ns2 ip a

将创建的 veth对 的另一端插入到 br0 的桥接网卡

~# ip link set br-veth0 master br0
~# ip link set br-veth1 master br0

 启动网卡 veth0 veth1 br-veth0 br-veth1,并配置 ip 地址

// 开启网桥部分
~# ip link set br-veth0 up
~# ip link set br-veth1 up

// 开启ns里的veth
~# ip netns exec ns1 ip link set veth0 up
~# ip netns exec ns2 ip link set veth1 up

// 设置容器内的veth 设备ip
~# ip netns exec ns1 ifconfig veth0 192.168.100.10/24
~# ip netns exec ns2 ifconfig veth1 192.168.100.20/24

四、总结

Docker 创建网桥和Veth设备都是通过AF_NETLINK 套接字实现的,我需要去读 <精通linux内核网络>这本书去调研一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1122071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

家庭燃气表微信抄表识别系统

1.背景需求 目前家里燃气度数的读数上报&#xff0c;每个月在社区微信群里面将手机拍摄的燃气表读数截图&#xff08;加住址信息水印&#xff09;&#xff0c;发到群里给抄表员。 2.总体设计 设计目标 功能一&#xff1a;手机上随时可以远程采集读数图片&#xff08;自动加住…

Jenkins环境部署与任务构建

一、CI/CD 1、CI/CD 概念&#xff1a; CI/CD 是一种软件开发和交付方法&#xff0c;旨在加速应用程序的开发、测试和部署过程&#xff0c;以提高软件交付的质量和效率。 (1) 持续集成 (CI Continuous Integration): 持续集成是开发团队频繁集成其代码更改的过程。开发者将其…

2023Jenkins连接k8s

首先配置k8s config文件 1.方式获取k8s密钥 cat .kube/config 2.导出方式或者密钥 kubectl config view --raw > k8s-config-admin pipeline {agent {kubernetes {yaml apiVersion: v1kind: Podmetadata:labels:some-label: devopsspec:containers:- name: dockerimage: d…

SpringBoot 第一个接口编写

RestController //表示该类为请求处理类public class HttpDeal {RequestMapping("/login")//这个方法处理哪一个地址过来的请求public String hello(){return "返回给浏览器";}}

【网络协议】聊聊网关 NAT机制

再宿舍的时候&#xff0c;其实只能通过局域网进行处理&#xff0c;但是如果接入互联网&#xff0c;一般是配置路由器当然还有网关。 MAC头和IP头的细节 一旦配置了IP地址和网关&#xff0c;就可以制定目标地址进行访问。 MAC头主要信息目标和源MAC地址&#xff0c;以及协议类…

二、BurpSuite Intruder暴力破解

一、介绍 解释&#xff1a; Burp Suite Intruder是一款功能强大的网络安全测试工具&#xff0c;它用于执行暴力破解攻击。它是Burp Suite套件的一部分&#xff0c;具有高度可定制的功能&#xff0c;能够自动化和批量化执行各种攻击&#xff0c;如密码破解、参数枚举和身份验证…

031-第三代软件开发-屏幕保护

第三代软件开发-屏幕保护 文章目录 第三代软件开发-屏幕保护项目介绍屏幕保护 关键字&#xff1a; Qt、 Qml、 MediaPlayer、 VideoOutput、 function 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Object Language&#…

SIEMENS S7-1200 汽车转弯灯程序 编程与分析

公告 项目地址:https://github.com/MartinxMax/SIEMENS-1200-car_turn_signal 分析 题目: 画IO分配表 输入输出m3.0左转弯开关q0.0左闪灯m3.1右转弯开关q0.1右闪灯m3.2停止开关 博图V16配置 设置PLC的IP地址 允许远程通信访问 将HMI设备拖入 注意,我们这边选择的是HMI连接…

Leetcode——二维数组及滚动数组练习

118. 杨辉三角 class Solution { public:vector<vector<int>> generate(int numRows) {// 定义二维数组vector<vector<int>> num(numRows);for(int i0;i<numRows;i){//这里是给内层vector定义大小。默认是0,这里n是个数&#xff0c;不是值num[i].re…

【Java基础面试四十二】、 static修饰的类能不能被继承?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a; static修饰的类能不能被…

物联网AI MicroPython传感器学习 之 RTC时钟模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 DS1302 是DALLAS 公司推出的涓流充电时钟芯片&#xff0c;内含有一个实时时钟/日历和31字节静态RAM&#xff0c;实时时钟/日历电路提供秒、分、时、日、周、月、年的信息&#xff0c;每月的天数…

驱动开发2 CoetexA7核 字符设备驱动(LED亮灯)(单独映射寄存器实现+封装结构体映射实现)

一、单独映射寄存器实现 可参考arm点灯C语言 cortex-A7核 点LED灯 &#xff08;附 汇编实现、使用C语言 循环实现、使用C语言 封装函数实现【重要、常用】&#xff09;-CSDN博客 1 应用程序 test.c #include <stdio.h> #include <sys/types.h> #include <sys/s…

云服务器搭建Hadoop分布式

文章目录 1.服务器配置2.Java环境3. 安装Hadoop4. 集群配置5. 编写集群的启动脚本 1.服务器配置 服务器主机名配置115.157.197.82s110核115.157.197.84s210核115.157.197.109s310核115.157.197.31s410核115.157.197.60gracal10核 所有的软件安装在/opt/module下&#xff0c;软…

python生成的报告中绘制了多张图,但最后都混合到一起了

问题来源&#xff1a; 用python生成的报告中&#xff0c;存在三张图&#xff0c;第一个张图是正常的&#xff0c; 后面的图都是不正常的&#xff0c;全都是多张图混合而成的&#xff0c;这是为什么呢&#xff1f; 三段代码均是下述调用方式 import matplotlib.pyplot as plt pl…

biquad滤波器的设计

1.介绍 Biquad滤波器是一种常用的数字滤波器结构&#xff0c;它使用二阶差分方程&#xff08;difference equation&#xff09;来实现滤波功能。它得名于其包含两个极点&#xff08;poles&#xff09;和一个零点&#xff08;zero&#xff09;。 双二阶滤波器(biquad)是最常用…

DALL·E 3:OpenAI的革命性图像生成模型与ChatGPT的融合

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

贪吃蛇项目实践

游戏背景&#xff1a; 贪吃蛇是久负盛名的游戏&#xff0c;它也和俄罗斯⽅块&#xff0c;扫雷等游戏位列经典游戏的⾏列。 实现基本的功能&#xff1a; 贪吃蛇地图绘制 蛇吃⻝物的功能 &#xff08;上、下、左、右⽅向键控制蛇的动作&#xff09; 蛇撞墙死亡 蛇撞⾃⾝死亡 计…

Leetcode 454 四数相加II(哈希表 + getOrDefault方法用于获取Map中指定键的值,如果键不存在,则返回一个默认值)

Leetcode 454 四数相加II&#xff08;哈希表&#xff09; 解法1 HashMap getOrDefault方法 解法1 HashMap getOrDefault方法 【HashMap】 【⭐️HashMap常用操作】 创建HashMap&#xff1a;HashMap<Integer, Integer> hash new HashMap<>(); 向HashMap添加元素…

vant组件是使用?

首先 在vue项目中使用的时候 要先下载组件 使用npm安装 # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant# Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v2 使用yarn安装或pnpm # 通过 yarn 安装 yarn add vant# 通过 pnpm 安装 pnpm add vant 在框架中引入即…

No170.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…