周末的时候写了一篇关于Docker底层支撑技术的文章:

以firejail sandbox解析Docker核心原理依赖的四件套 : https://blog.csdn.net/dog250/article/details/81025071

获得了一些反响,总结下来就三点:

1. 确实这四件套支撑了很多的容器技术;

2. 唱衰Docker以及OverlayFS,Cgroup,NS这些,以为它们违背了某种原则;

3. 关于firejail网络方面的某些细节。

上述的1和2显得过于形而上,保留个人观点不讨论,或者等哪天闲了再说。关于第三点,我觉得有必要梳理一下,本文先说一个典型的。

有网友问到,firejail的Macvlan配置细节是怎样的, firejail容器内的Macvlan虚拟网卡如何访问容器外宿主机的IP地址呢?? ( 先说答案吧,默认情况下,不能访问 )

我并没有深入的了解过firejail,本身接触它也不久,所以只能从其文档代码中去一窥究竟了。firejail的github源在这里: https://github.com/netblue30/firejail

总而言之,firejail使用网络的方式有两种:

Bridge veth的方式:与Docker Bridge模式一致,如果–net参数指定一个Linux Bridge的话。

Macvlan bridge模式:如果–net参数指定一块物理网卡,即会产生一个bridge模式的macvlan虚拟网卡。

这些在其manual中也有提到:

–net=bridge_interface

Enable a new network namespace and connect it to this bridge interface. Unless specified with option –ip and –defaultgw, an IP address and a

default gateway will be assigned automatically to the sandbox. The IP address is verified using ARP before assignment. The address configured as

default gateway is the bridge device IP address. Up to four –net bridge devices can be defined. Mixing bridge and macvlan devices is allowed.

.

Example:

s u o b r c t l a b r b r 0 s

u

d

o

b

r

c

t

l

a

d

d

b

r

b

r

0

sudo ifconfig br0 10.10.20.1/24

s u o b r c t l a b r b r 1 s

u

d

o

b

r

c

t

l

a

d

d

b

r

b

r

1

sudo ifconfig br1 10.10.30.1/24

i r e j a i l – n e t = b r 0 – n e t = b r 1 . – n e t = e t h e r n e t i n t e r a c e n a b l e a n e w n e t w o r k n a m e s p a c e a n c o n n e c t i t t o t h i s e t h e r n e t i n t e r a c e u s i n t h e s t a n a r L i n u x m a c v l a n r i v e r . n l e s s s p e c i i e w i t h o p t i o n – i p a n – e a u l t w , a n a r e s s a n a e a u l t a t e w a w i l l b e a s s i n e a u t o m a t i c a l l t o t h e s a n b o x . h e a r e s s i s v e r i i e u s i n A R b e o r e a s s i n m e n t . h e a r e s s c o n i u r e a s e a u l t a t e w a i s t h e e a u l t a t e w a o t h e h o s t . p t o o u r – n e t e v i c e s c a n b e e i n e . i x i n b r i e a n m a c v l a n e v i c e s i s a l l o w e . o t e : w l a n e v i c e s a r e n o t s u p p o r t e o r t h i s o p t i o n . . x a m p l e : f

i

r

e

j

a

i

l

n

e

t

=

b

r

0

n

e

t

=

b

r

1

.

n

e

t

=

e

t

h

e

r

n

e

t

i

n

t

e

r

f

a

c

e

E

n

a

b

l

e

a

n

e

w

n

e

t

w

o

r

k

n

a

m

e

s

p

a

c

e

a

n

d

c

o

n

n

e

c

t

i

t

t

o

t

h

i

s

e

t

h

e

r

n

e

t

i

n

t

e

r

f

a

c

e

u

s

i

n

g

t

h

e

s

t

a

n

d

a

r

d

L

i

n

u

x

m

a

c

v

l

a

n

d

r

i

v

e

r

.

U

n

l

e

s

s

s

p

e

c

i

f

i

e

d

w

i

t

h

o

p

t

i

o

n

i

p

a

n

d

d

e

f

a

u

l

t

g

w

,

a

n

I

P

a

d

d

r

e

s

s

a

n

d

a

d

e

f

a

u

l

t

g

a

t

e

w

a

y

w

i

l

l

b

e

a

s

s

i

g

n

e

d

a

u

t

o

m

a

t

i

c

a

l

l

y

t

o

t

h

e

s

a

n

d

b

o

x

.

T

h

e

I

P

a

d

d

r

e

s

s

i

s

v

e

r

i

f

i

e

d

u

s

i

n

g

A

R

P

b

e

f

o

r

e

a

s

s

i

g

n

m

e

n

t

.

T

h

e

a

d

d

r

e

s

s

c

o

n

f

i

g

u

r

e

d

a

s

d

e

f

a

u

l

t

g

a

t

e

w

a

y

i

s

t

h

e

d

e

f

a

u

l

t

g

a

t

e

w

a

y

o

f

t

h

e

h

o

s

t

.

U

p

t

o

f

o

u

r

n

e

t

d

e

v

i

c

e

s

c

a

n

b

e

d

e

f

i

n

e

d

.

M

i

x

i

n

g

b

r

i

d

g

e

a

n

d

m

a

c

v

l

a

n

d

e

v

i

c

e

s

i

s

a

l

l

o

w

e

d

.

N

o

t

e

:

w

l

a

n

d

e

v

i

c

e

s

a

r

e

n

o

t

s

u

p

p

o

r

t

e

d

f

o

r

t

h

i

s

o

p

t

i

o

n

.

.

E

x

a

m

p

l

e

:

firejail –net=eth0 –ip=192.168.1.80 –dns=8.8.8.8 firefox

现在我们关注第二点,即Macvlan的方式。我使用下面的命令启动firejail:

# 我为容器指派192.168.44.55这个IP地址

# 我的宿主机enp0s17这块网卡的IP地址是192.168.44.138

root@debian:/home/zhaoya# firejail --net=enp0s17 --ip=192.168.44.55/24

Reading profile /etc/firejail/server.profile

Reading profile /etc/firejail/disable-common.inc

Reading profile /etc/firejail/disable-programs.inc

Reading profile /etc/firejail/disable-passwdmgr.inc

** Note: you can use --noprofile to disable server.profile **

Parent pid 870, child pid 871

The new log directory is /proc/871/root/var/log

Interface MAC IP Mask Status

lo 127.0.0.1 255.0.0.0 UP

eth0-870 1e:01:c7:ff:4b:39 192.168.44.55 255.255.255.0 UP

Default gateway 192.168.44.2

Child process initialized

root@debian:~#

root@debian:~# ping 192.168.44.138

PING 192.168.44.138 (192.168.44.138) 56(84) bytes of data.

From 192.168.44.55 icmp_seq=1 Destination Host Unreachable

From 192.168.44.55 icmp_seq=2 Destination Host Unreachable

From 192.168.44.55 icmp_seq=3 Destination Host Unreachable

在容器外的宿主机抓包:

root@debian:/home/zhaoya# tcpdump -i enp0s17 arp or icmp -n

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on enp0s17, link-type EN10MB (Ethernet), capture size 262144 bytes

19:31:50.029168 ARP, Request who-has 192.168.44.138 tell 192.168.44.55, length 28

19:31:51.053176 ARP, Request who-has 192.168.44.138 tell 192.168.44.55, length 28

19:31:52.077292 ARP, Request who-has 192.168.44.138 tell 192.168.44.55, length 28

19:31:53.101540 ARP, Request who-has 192.168.44.138 tell 192.168.44.55, length 28

...

显然,ARP请求没有得到回应。

如果此时另外启动一个新的firejail容器,指派另一个同网段IP地址,你会发现,两个容器之间是可以互通的,但是容器和宿主机之间无法互通,这是怎么回事?

这就涉及到Macvlan的几种工作模式的原理了,我简单将其总结成下面的图示:

我们专门看看左上角第一幅图,即Bridge模式的Macvlan。显然,没有从虚拟网卡到物理网卡的通路(确实有点像裤衩…)…有了这个图示几乎便不用去看代码了。

问题的解释就是这样。

下面是见招拆招了。如果你非要实现Macvlan的虚拟网卡和物理网卡之间的互通,怎么办?其实简单,只需要构造下面的图即可:

这下不看代码也得看了!

主要看Macvlan的xmit回调:

static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)

{

const struct macvlan_dev *vlan = netdev_priv(dev);

const struct macvlan_port *port = vlan->port;

const struct macvlan_dev *dest;

if (vlan->mode == MACVLAN_MODE_BRIDGE) {

const struct ethhdr *eth = (void *)skb->data;

/* send to other bridge ports directly */

if (is_multicast_ether_addr(eth->h_dest)) {

macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);

// 显然在ARP广播注入所有的虚拟网卡接收路径后,便从物理网卡直接发到外部去了。

goto xmit_world;

}

dest = macvlan_hash_lookup(port, eth->h_dest);

// 如果不是广播,也只有路由到虚拟网卡的数据包从能被注入。

if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {

/* send to lowerdev first for its network taps */

dev_forward_skb(vlan->lowerdev, skb);

return NET_XMIT_SUCCESS;

}

}

xmit_world:

skb->dev = vlan->lowerdev;

// 直接经由物理网卡连接的线缆发出。

return dev_queue_xmit(skb);

}

对付这个非常简单,只需要改成下面的这个就行:

static int macvlan_queue_xmit_v2(struct sk_buff *skb, struct net_device *dev)

{

const struct macvlan_dev *vlan = netdev_priv(dev);

const struct macvlan_port *port = vlan->port;

const struct macvlan_dev *dest;

if (vlan->mode == MACVLAN_MODE_BRIDGE) {

const struct ethhdr *eth = (void *)skb->data;

/* send to other bridge ports directly */

if (is_multicast_ether_addr(eth->h_dest)) {

struct sk_buff *nskb;

macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);

nskb = skb_clone(skb, GFP_ATOMIC);

if (likely(nskb)) {

nskb->dev = vlan->lowerdev;

// 往物理网卡也注入一份。

dev_forward_skb(vlan->lowerdev, nskb);

}

goto xmit_world;

}

dest = macvlan_hash_lookup(port, eth->h_dest);

if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {

/* send to lowerdev first for its network taps */

dev_forward_skb(vlan->lowerdev, skb);

return NET_XMIT_SUCCESS;

}

// 如果目标MAC是物理网卡的,则注入到宿主物理网卡

else /*if (!compareMacs(eth, vlan->lowerdev))*/{

struct sk_buff *nskb;

nskb = skb_clone(skb, GFP_ATOMIC);

if (likely(nskb)) {

nskb->dev = vlan->lowerdev;

dev_forward_skb(vlan->lowerdev, nskb);

}

}

}

xmit_world:

skb->dev = vlan->lowerdev;

return dev_queue_xmit(skb);

}

Macvlan虚拟网卡的发送路径就是这么轻松搞定的,那么接下来看一个比较棘手的,即物理网卡的发送路径,因为显然我们需要宿主机也能通过物理网卡和容器通信,比如SSH登录它。

这个比较棘手是因为我们不得不改物理网卡的驱动的hard_start_xmit回调函数,目前也没有在dev_queue_xmit中看到有什么比较好的HOOK点,而修改物理网卡驱动就为了支持Macvlan这么一个并非通用的机制,也有点怪异…

为了尽快跑起来看到效果,不去想那么多,直接使用ptype_all机制。

在数据包将要发送到物理网卡前,会有一个抓包的HOOK点,即 dev_queue_xmit_nit 函数。我们只需要注册一个ETH_P_ALL类型的packet_type即可将数据包拉到一个func回调中,之后便可以在这个回调中做任何想做的事了:

int xmit_handle(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)

{

const struct macvlan_port *port = macvlan_port_get_rcu(skb->dev);

const struct ethhdr *eth = eth_hdr(skb);

const struct macvlan_dev *vlan;

if (skb->pkt_type != PACKET_OUTGOING) {

// 仅仅在发送路径使能

goto drop_out;

}

if (port) {

vlan = macvlan_hash_lookup(port, eth->h_dest);

if (vlan) // 如果找到了虚拟网卡就单播注入。

dev_forward_skb(vlan->dev, skb);

else // 如果没有对应到,就广播注入。

macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);

return 0;

}

drop_out:

consume_skb(skb);

return 0;

}

static struct packet_type macvlan_handle_pack = {

.type = __constant_htons(ETH_P_ALL),

.func = xmit_handle,

};

最后别忘了注册:

macvlan_init_module:

dev_add_pack(&macvlan_handle_pack);

macvlan_cleanup_module:

dev_remove_pack(&macvlan_handle_pack);

编译成新的macvlan.ko,然后加载,再执行最开始的流程,宿主机和容器便可以相互通信了。

由于这是一个临时的方案,所以物理网卡发送数据包是旁路给虚拟网卡的,真正的数据包还会在物理链路上冗余一份,这是一个不足。如果将发送逻辑放在物理网卡驱动的发送回调中便可以解决这个问题。另外,其实在调用具体网卡驱动的hard_start_xmit前先check一下公共的标识位也是比较不错的,比如引入一个 NETIF_F_MASTER 标志,在dev_queue_xmit的发送之前,便可以:

if ((dev->features & NETIF_F_MASTER) == 0) {

int ret;

ret = check_flags(skb, struct net_device *dev, struct packet_type *pt);

if (ret == E_BYPASS)

goto out;

}

...

dev_hard_start_xmit...

...

out:

rcu_read_unlock_bh();

return rc;

这样看起来会更加优雅。

这就是整个Macvlan与宿主物理网卡之间通信的整个逻辑了,简单地解决了其不能互通的问题。

其实解决这个问题对于我个人也是很有用的,这样我就可以方便地SSH登录容器了。不过,值得注意的是,这实际上并不一定就是个问题,Macvlan设计上就是如此吧,即把物理网卡仅仅看作是一个容纳虚拟网卡的 容器 ,而不是其中一个节点。这种关系让人不禁想起task_struct这个结构体以及进程/线程之间的关系…

就这么点事,不多说。浙江温州皮鞋湿!

linux 虚拟网卡与物理网卡关系,Linux Macvlan的虚拟网卡与宿主物理网卡之间的Bridge通信问题...相关推荐

  1. Linux Macvlan的虚拟网卡与宿主物理网卡之间的Bridge通信问题

    周末的时候写了一篇关于Docker底层支撑技术的文章: 以firejail sandbox解析Docker核心原理依赖的四件套:https://blog.csdn.net/dog250/article ...

  2. linux终端和执行进程的关系,Linux系统编程第04期:打通进程与终端的任督二脉

    本课程是<嵌入式工程师自我修养>自学教程六步走中的第3步:Linux系统编程第04期. 进程,是Linux系统中任务调度运行的基本单元:一个程序的运行,都要被操作系统包装成"进程 ...

  3. Linux下apache和fcgi的关系,Linux下编译安装Apache httpd 2.4

    目录 一 编译安装的优势 二httpd的版本 三httpd 24的新特性 四 编译安装httpd 24 五 配置http24启动及停止 六 配置man手册 七 验证 Apache是世界使用排名第一的W ...

  4. linux lvm 大小与硬盘大小关系,linux lvm扩容磁盘大小

    关闭需要扩容的虚拟机,并通过管理虚拟机界面添加磁盘空间,本次调整50G 注意,本次写入为增加容量的大小,并非增加完硬盘的大小 硬盘添加后可以用fdisk -l 查看磁盘容量大小,可以看到硬盘增加了50 ...

  5. 修复Vmware虚拟网卡异常并远程连接Linux

    修复Vmware虚拟网卡异常并远程连接Linux 提示:文章适用于在设备管理器网络适配器中Vmware的网卡异常,重新修复网卡并配置IP地址后实现远程连接Linux 文章目录 修复Vmware虚拟网卡 ...

  6. 技嘉H370 HD3主板的物理机上安装 Linux CentOS7 解决无网卡驱动

    技嘉H370 HD3主板的物理机上安装 Linux CentOS7 解决无网卡驱动 一般的机器网卡驱动会随Linux系统一起安装,但有些奇葩的主板的网卡驱动Linux安装包没有,这就得找到它的网卡驱动 ...

  7. 网卡或网络配置文件(Linux网络操作系统与管理配置)小红帽

    网卡或网络配置文件(Linux网络操作系统与管理配置)小红帽 1.ifconfig是一个用来查看启用禁用的网络接口工具 /etc/sysconfig/network-scripts/ifcfg-eth ...

  8. Linux学习之CentOS(二十九)--Linux网卡高级命令、IP别名及多网卡绑定

    本篇随笔将详细讲解Linux系统的网卡高级命令.IP别名以及Linux下多网卡绑定的知识 一.网卡高级命令 在之前的一篇随笔里Linux学习之CentOS(九)--Linux系统的网络环境配置,详细讲 ...

  9. linux查看哪个网卡插着网线,(笔记)Linux下检测网卡与网线连接状态

    Linux下检测网卡与网线连接状态,使用ioctl向socket发送SIOCETHTOOL命令字. #include #include #include #include #include #incl ...

最新文章

  1. udacity classmates pytorch基础用法总结
  2. ios markdown 解析_Shortcuts 教程:正则表达式修改 Markdown 链接
  3. python中变量的类型是动态的随时可以变化_python动态类型简介
  4. python中func函数用法_python之4类回调函数的使用方法
  5. Web前端精髓年终总结
  6. solrcloud 7.5在k8s上的部署安装和使用教程
  7. PowerDesigner数据库设计PDM基于Excel的导入导出总结
  8. 省选专练之后缀自动机zoj1729 Hidden Password
  9. 数据oracle的等保三级测评,等级保护测评三级详解测评要求项测评方法及测评步骤...
  10. wps如何只让他显示3级标题_wps文字怎样设置多级标题
  11. 数字滤波器 matlab 仿真,基于MATLAB的经典数字滤波器的设计与仿真
  12. 图片生成链接最简单的方法
  13. java求一元二次方程:ax2+bx+c=0的根
  14. 全球及中国回转窑扫描仪行业发展动态与前景趋势预测报告2022-2028年
  15. 正多边形和多面体的对称群
  16. java 实现站内信_群发站内信实现
  17. 计算机主板测试配件,如何检测主板是否有问题_如何诊断主板是否损坏,没有图形步骤...
  18. Android Developer:合并清单文件
  19. spring jsm(一)
  20. objectArx ---基础操作

热门文章

  1. 聚光灯+法线贴图 shader
  2. 23个Linux实际开发常用命令
  3. 市场驱动传播趋势,通过AIDMA、AISAS认知ISMAS
  4. 罗永浩的《子弹短信》
  5. python练习3:输入分数,输出对应的ABCD级别
  6. Android常用布局之AbsoluteLayout(绝对布局)
  7. 2018/11/27渡课
  8. 红米笔记本linux,Redmi首款AMD笔记本将加入全新小米互传
  9. 主题7 文件与文件夹操作 单元作业
  10. 传魅族黄章主动示好小米 相关言论已删除