周末的时候写了一篇关于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. 命令注入工具Commix
  2. [云炬创业基础笔记]第二章创业者测试21
  3. SGML、HTML和XML之间的关系
  4. 百万数据报表读取:步骤分析以及自定义事件处理器
  5. 无法连接oralce问题(不断更新)
  6. java springmvc注解详解_springMVC注解开发详解(转)
  7. 1152Google Recruitment
  8. 0基础学python做什么工作好-转行零基础该如何学习python?很庆幸,三年前的我选对了...
  9. nancy中的本地化
  10. 上位机plc编程入门_图解PLC编程入门
  11. usb接口多少钱_3分钟搞懂USB
  12. 【雷达信号处理】---模糊函数与仿真
  13. 北京邦威思创科技有限公司 招聘要求
  14. LOJ10155 一本通1577 数字转换 题解
  15. PLSQL 14.0.6 下载使用教程
  16. CES Aisa总结篇|盛况依旧,却始终缺了点新意和真实落地的感觉
  17. 用python解决数据结构与算法_python中各种数据结构与算法的解决技巧
  18. DeepChem教程4:分子指纹
  19. dnf跨四服务器什么时候修复,DNF:跨四大区服务器炸了,数据回档导致多少玩家错过了毕业装备...
  20. 分享一部国外电影,挺好看的

热门文章

  1. 从零开始搭建一个语音对话机器人
  2. 我的世界 联机 软件
  3. 《Java SE实战指南》13-03:构造器和继承
  4. 牛年宝宝起名取名字:古诗有文采典故的女孩名字
  5. NTFS和FAT32分别是什么意思?他们两有什么区别?各自的作用分别是什么?
  6. 验证码显示不出来的的原因
  7. 【专利查询、下载及撰写】
  8. 企达云服务器,子弹短信大火之际,我们与它的技术服务商聊了聊
  9. 小游戏开发周期多长时间
  10. 【转载】如何把产品做简单