安装libXScrnSaver
1
|
|
azuredatastudio 1.23
1 2 3 4 |
|
azuredatastudio 1.13
https://blog.csdn.net/qq_38179971/article/details/102996923
1 2 3 4 5 |
|
1
|
|
1 2 3 4 |
|
https://blog.csdn.net/qq_38179971/article/details/102996923
1 2 3 4 5 |
|
https://docs.microsoft.com/zh-cn/sql/linux/quickstart-install-connect-red-hat?view=sql-server-ver15
https://packages.microsoft.com/rhel/7/mssql-server-2019/
https://packages.microsoft.com/rhel/7/mssql-server-2019/mssql-server-15.0.4073.23-4.x86_64.rpm
1
|
|
1
|
|
1
|
|
请确保为 SA 帐户指定强密码(最少 8 个字符,包括大写和小写字母、十进制数字和/或非字母数字符号)。
1
|
|
若要创建数据库,则需要使用可在 SQL Server 上运行 Transact-SQL 语句的工具进行连接。 以下步骤将安装 SQL Server 命令行工具:sqlcmd 和 bcp。
1
|
|
1
|
|
1
|
|
为方便起见,向 PATH 环境变量添加 /opt/mssql-tools/bin/。 这样可以在不指定完整路径的情况下运行这些工具。 运行以下命令以修改登录会话和交互式/非登录会话的路径:
1 2 3 |
|
以下步骤使用 sqlcmd 本地连接到新的 SQL Server 实例。
1
|
|
可以在命令行上省略密码,以收到密码输入提示。
如果以后决定进行远程连接,请指定 -S 参数的计算机名称或 IP 地址,并确保防火墙上的端口 1433 已打开。
如果成功,应会显示 sqlcmd 命令提示符:1>。
下面各部分将逐步介绍如何使用 sqlcmd 新建数据库、添加数据并运行简单查询。
以下步骤创建一个名为 TestDB 的新数据库。
在 sqlcmd 命令提示符中,粘贴以下 Transact-SQL 命令以创建测试数据库:
1
|
|
在下一行中,编写一个查询以返回服务器上所有数据库的名称:
1
|
|
前两个命令没有立即执行。 必须在新行中键入 GO 才能执行以前的命令:
1
|
|
提示
若要详细了解如何编写 Transact-SQL 语句和查询,请参阅教程:编写 Transact-SQL 语句。
接下来创建一个新表 Inventory,然后插入两个新行。
在 sqlcmd 命令提示符中,将上下文切换到新的 TestDB 数据库:
1
|
|
创建名为 Inventory 的新表:
1
|
|
将数据插入新表:
1
|
|
要执行上述命令的类型 GO:
1
|
|
现在,运行查询以从 Inventory 表返回数据。
通过 sqlcmd 命令提示符输入查询,以返回 Inventory 表中数量大于 152 的行:
1
|
|
执行此命令:
1
|
|
要结束 sqlcmd 会话,请键入 QUIT:
1
|
|
1
|
|
备份数据库名为test
1
|
|
例如还原数据库test
1
|
|
方法停止sqlserver数据库,已单用户模式启动数据库
1
|
|
然后在执行还原命令即可
https://blog.csdn.net/NW_NW_NW/article/details/75045966
https://blog.csdn.net/NW_NW_NW/article/details/75090101
https://blog.csdn.net/NW_NW_NW/article/details/75647220
https://blog.csdn.net/NW_NW_NW/article/details/76022117
https://blog.csdn.net/NW_NW_NW/article/details/76153027
https://blog.csdn.net/NW_NW_NW/article/details/76204707
https://blog.csdn.net/NW_NW_NW/article/details/76674232
https://blog.csdn.net/NW_NW_NW/article/details/76710941
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
了解了桥初始化大致要做的事情后,我们再来看看这些初始化或者注册的事情到底干了些什么?
1.注册协议生成树收包函数stp_proto_register
在桥初始化的时候,注册了一个br_stp_proto参数,此参数的具体模样是这样子的
1 2 3 |
|
br_stp_rcv函数在/net/bridge/br_stp_bpdu.c中主要针对网桥进行协议交换的帧(BPDU)进行配置操作。
2.桥转发数据库初始化br_fdb_init
此函数就是在内存中建立一块slab cache,以存放net_bridge_fdb_entry
其中:net_bridge_fdb_entry是一个结构体,用来转发数据库的记录项网桥所学到对的每个MAC地址都有这样一个记录
3.在proc目录下生成相关文件的注册函数register_pernet_subsys,初始化的时候给这个函数传递了一个参数br_net_ops,这个参数的模样是这样的
1 2 3 |
|
但是在桥初始化的时候,仅仅注册了br_net_exit,这个函数会将桥下面的所有文件全部清空。
4.通知链的相关函数注册register_netdevice_notifier这个注册函数主要针对设备信息的变化,注册参数br_device_notifier,具体如下:
1 2 3 |
|
br_device_event函数是用来当桥上的设备状态或者设备信息发生改变时做相应的处理,该函数在/net/bridge/br.c中
5.注册通知连,主要针对桥转发表事件的相关信息register_switchdev_notifier,传入的参数br_switchdev_notifier详细信息如下:
1 2 3 |
|
br_switchdev_event,主要针对桥转发表的事件做出相应的处理该函数在/net/bridge/br.c中
6.brioctl_set用来处理ioctl命令的函数,比如添加和删除网桥,br_ioctl_deviceless_stub给回调函数br_ioctl_hook,而br_ioctl_hook在sock_ioctl中
使用,这样通过在应用层调用socket的ioctl函数,就能够进行网桥的添加与删除了,函数用来处理添加和删除网桥的相关操作
以上就是网桥初始化的相关操作。
我们先来看一个命令:brctl addbr br1
上节我们提到一个用来处理ioctl命令的函数br_ioctl_deviceless_stub通过调用brioctl_set, 将br_ioctl_deviceless_stub赋值给回调函数br_ioctl_hook,而br_ioctl_hook在sock_ioctl中使用。 这样通过在应用层调用socket的ioctl函数,就能够进行网桥的添加与删除了。
如果我们想增加新的ioctl,用于我们新开放的功能,就可以在该函数里增加新的case即可。
当我们输入上面命令时,就会触发br_ioctl_deviceless_stub函数来响应br_add_bridge函数,当命令执行完成以后, 使用brctl show命令就可以看见我们添加的br1这个网桥设备已经生成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
该函数注册在,桥设备添加时候dev->netdev_ops = &br_netdev_ops; 在br_netdev_ops有一个函数指针.ndo_do_ioctl= br_dev_ioctl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
|
在桥上添加接口的基本步骤,如上,删除桥端口,主要是把建立接口时所做的事情撤销,如添加接口出错时的一些处理。
关于设备的添加删除的基本动作,我们已经知道。 这节,我们看看关于网桥设备以及桥设备上的端口的启动和关闭。
我们说过,在初始化一个桥设备的时候有这样一个操作: dev->netdev_ops = &br_netdev_ops;
br_netdev_ops这个参数,注册了很多函数,其中包括网桥设备的启动和关闭函数
br_dev_open和br_dev_stop,这两个函数的工作主要是初始化桥设备的一些队列和 桥设备上端口的一些启动和关闭动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
启动网桥设备,当启动网桥设备时,先前绑定在该设备上的端口也会跟着启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
关闭网桥设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
要启动网桥端口,必须满足下列几个条件
1.被管理的相关设备已用管理手段启动
2.被绑定的相关设备有载波状态
3.相关的网桥设备已用管理手段启动
注意:网桥设备上没有载波状态,因为网桥是虚拟设备。
当网桥是以用户空间命令建起来并且先前三个条件都满足时,该网桥端口就可以立即启用了
但是,假设当端口建立时,由于上述三项条件至少有一项不满足无法启动端口时,下面的条件是 每项条件最终满足时启用端口的场合:
1.当被关闭的网桥设备重新启动时,其所有关闭的端口就会启用
2.当被绑定的设备检测到载波状态时,桥程序会收到NETDE_CHANGE通知消息
3.当被关掉的版定设备重启时,桥程序会收到NETDEV_UP的通知消息
如若还不满足,网桥端口就会被关闭
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
注意,当网桥端口关闭时,非根网桥可能会变成根网桥
需要说明的是:
1.我们先暂时忽略数据包从一开始是怎么从驱动进入到netif_receive_skb的,因为这个暂时不影响我们理解这幅图的流程。
2.由于桥转发的篇幅较大,图中没有标示出,数据包中途被丢弃的情况。约定数据包会发送成功。
现在数据包(skb)已经准备好了装备要闯关了
1.首先skb从驱动经过繁杂的路线走到了netif_receive_skb这个函数中经过一些小波折到达__netif_receive_skb_core
中时遇到了第一个十字路口是看了看自己有没有skb->dev->rx_handler(注1)这个装备,如果有,则进入br_handle_frame(注2).如果没有则直接上协议栈。
注1:桥下的设备注册过rx_handler函数,所以数据包会进入桥,br_pass_frame_up函数将原先的设备换成了桥设备, 而桥设备没有注册过rx_handler函数,所以数据包不会二次进入桥。
注2:br_handle_frame我们在前几节提到过,是skb进入桥的入口函数,在br_add_if的时候会注册该函数。
2.skb注定要经历一番劫难,刚进入br_handle_frame又将陷入两难境地,此时有两个入口,这两个是netfilter设下的连个hook点,分别是,NF_BR_PRE_ROUTING,和NF_BR_LOCAL_IN,两种路径,如果数据包只想去本地,则会选择NF_BR_LOCAL_IN入口,然后发往本地,如果暂时还确定不了,则先进入NF_BR_PRE_ROUTING入口.
3.进入NF_BR_PRE_ROUTING入口后,会遇到br_handle_frame_finish函数,这个函数决定了skb的何去何从,(1)如果是转发,则在经过NF_BR_FORWARD钩子点进入转发阶段的障碍,最后在进入NF_BR_POST_ROUTING,以及最终的dev_queue_xmit,实现最终转发。(2)如果发往本地则重新进入NF_BR_LOCAL_IN最后在进入netif_receive_skb,进行转发。skb在经过目前口述的磨练最终得以释放。
4.如果是如果是本地发出的数据包,经过NF_BR_LOCAL_OUT钩子点然后进入最后阶段的NF_BR_POST_ROUTING,进行相应的转发。
一个很重要的函数br_handle_frame这个函数的初始注册地点是在桥添加接口的时候,注册在桥某一个接口上
1 2 3 4 5 6 7 |
|
其次,那么__netif_receive_skb_core
是怎样让数据包进入桥的呢?我们看看上面提到的netdev_rx_handler_register函数,具体做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
看完这个函数,我们就明白了为什么在__netif_receive_skb_core
中可以用skb->dev->rx_handle将数据包传入br_handle_frame函数,也就是将数据包传入了桥。
值得注意的是:上面的dev是桥下的设备,不是桥设备,桥设备(比如br0)是没有注册rx_handle这个函数的
好,了解到,桥的注册函数和如何接收数据包后,然后一起来看看br_handle_frame是如何操作的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
|
在br_handle_frame主要做一件事,就是将数据包放进那个钩子点。
说明:br_handle_frame函数中有两个hook函数,br_handle_local_finish和br_handle_frame_finish这两个函数只有在netfilter因其他原因没有丢弃或者消化该帧时才会被调用,ebtables也能查看帧。ebtables是一个架构,能提供一些netfilter所没有的提供的额外功能,尤其是,ebtables可以过滤和修改任何类型的帧,而非仅限于那些携带ip封包的帧。
br_handle_frame_finish. 作用:br_handle_frame_finish函数主要是决策将不同类别的数据包做不同的分发路径。
其函数处理的过程如下图所示:
首先判断该数据包是否符合桥转发的条件:
(1)桥端口状态是否是开启状态,如果没有开启则丢掉数据包
(2)是否允许从该桥上转发,如果不允许,则直接返回0
获得桥转发的条件以后,开始判断数据包的类型:
(1)判断此时桥的标志为允许做哪些事情,学习还是扩展
如果学习的标志位被至位,则更新数据转发表。否则继续向下走
(2)根据多播或者广播报文的类型决定数据包的去留
(3)判断此时端口的状态,如果是学习状态,则将数据包丢弃
(要注意的是:桥的端口状态(和上面的flag不冲突,上面的flag表示网桥可以做的事情)state表示网桥端口所处于的状态) 在处理完一些需要预备的事情之后,就要为数据包的转发开始做准备了
(1)网桥设备是否处于混杂模式,如果是则建立副本,为发往本地做个备份
(注意的是,所有网桥端口绑定的设备都会处于混杂模式,因为 网桥运行必须此模式。但除非明确的对其进行配置,否则网桥自己是不会处于混杂模式的)
(2)在次判断广播还是多播地址
广播地址:仅仅设置副本,进行广播转发和发往本地
多播地址:先查多播地址转发表,如果存在,设置副本,进行多播转发,原始数据包指向NULL,如果已经传送至本地,则会释放副本,不进行本地转发,否则重新转发到本地
(3)不是广播或者多播
判断是否本地地址,如果是本地地址,则将原始数据包指向NULL,发往本地。
否则进行数据包转发
无论是在发往本地还是转发,有一个函数的功能是不能忽略的,就是br_handle_vlan函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
这个函数的作用很简单就是,数据包是否要带tag,
过程:
在传递进来的vlan group中查找自己所处的vlan
如果该vlan不存在则判断当前模式是否是混杂模式和数据包的设备是否是桥下的设备,选择发包或者丢弃。
如果存在,且vlan是开启的,则统计vlan接口上的数据流量,最后根据vlan出口的标记位进行位运算判断是否要带tag.
然后我们来看一下上节提到的发往本地数据包的处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
这个函数所做的事情很简单,就是配置vlan的相关信息后,然后发往本地的netfilter钩子函数中 最后重新回到netif_recive_skb.如下函数:
1 2 3 4 5 |
|
再来看看数据包转发的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
整个数据包转发的过程与转发到本地的过程类似,只不过所进入的netfilter钩子点不同.
整个分析中不包含数据包从本地发出的数据包
https://www.ibm.com/developerworks/cn/linux/1310_xiawc_networkdevice/
和磁盘设备类似,Linux 用户想要使用网络功能,不能通过直接操作硬件完成,而需要直接或间接的操作一个 Linux 为我们抽象出来的设备,既通用的 Linux 网络设备来完成。一个常见的情况是,系统里装有一个硬件网卡,Linux 会在系统里为其生成一个网络设备实例,如 eth0,用户需要对 eth0 发出命令以配置或使用它了。更多的硬件会带来更多的设备实例,虚拟的硬件也会带来更多的设备实例。随着网络技术,虚拟化技术的发展,更多的高级网络设备被加入了到了 Linux 中,使得情况变得更加复杂。在以下章节中,将一一分析在虚拟化技术中经常使用的几种 Linux 网络设备抽象类型:Bridge、802.1.q VLAN device、VETH、TAP,详细解释如何用它们配合 Linux 中的 Route table、IP table 简单的创建出本地虚拟网络。 相关网络设备工作原理
Bridge(桥)是 Linux 上用来做 TCP/IP 二层协议交换的设备,与现实世界中的交换机功能相似。Bridge 设备实例可以和 Linux 上其他网络设备实例连接,既 attach 一个从设备,类似于在现实世界中的交换机和一个用户终端之间连接一根网线。当有数据到达时,Bridge 会根据报文中的 MAC 信息进行广播、转发、丢弃处理。
图 1.Bridge 设备工作过程
如图所示,Bridge 的功能主要在内核里实现。当一个从设备被 attach 到 Bridge 上时,相当于现实世界里交换机的端口被插入了一根连有终端的网线。这时在内核程序里,netdev_rx_handler_register()被调用,一个用于接受数据的回调函数被注册。以后每当这个从设备收到数据时都会调用这个函数可以把数据转发到 Bridge 上。当 Bridge 接收到此数据时,br_handle_frame()被调用,进行一个和现实世界中的交换机类似的处理过程:判断包的类别(广播/单点),查找内部 MAC 端口映射表,定位目标端口号,将数据转发到目标端口或丢弃,自动更新内部 MAC 端口映射表以自我学习。
Bridge 和现实世界中的二层交换机有一个区别,图中左侧画出了这种情况:数据被直接发到 Bridge 上,而不是从一个端口接受。这种情况可以看做 Bridge 自己有一个 MAC 可以主动发送报文,或者说 Bridge 自带了一个隐藏端口和寄主 Linux 系统自动连接,Linux 上的程序可以直接从这个端口向 Bridge 上的其他端口发数据。所以当一个 Bridge 拥有一个网络设备时,如 bridge0 加入了 eth0 时,实际上 bridge0 拥有两个有效 MAC 地址,一个是 bridge0 的,一个是 eth0 的,他们之间可以通讯。由此带来一个有意思的事情是,Bridge 可以设置 IP 地址。通常来说 IP 地址是三层协议的内容,不应该出现在二层设备 Bridge 上。但是 Linux 里 Bridge 是通用网络设备抽象的一种,只要是网络设备就能够设定 IP 地址。当一个 bridge0 拥有 IP 后,Linux 便可以通过路由表或者 IP 表规则在三层定位 bridge0,此时相当于 Linux 拥有了另外一个隐藏的虚拟网卡和 Bridge 的隐藏端口相连,这个网卡就是名为 bridge0 的通用网络设备,IP 可以看成是这个网卡的。当有符合此 IP 的数据到达 bridge0 时,内核协议栈认为收到了一包目标为本机的数据,此时应用程序可以通过 Socket 接收到它。一个更好的对比例子是现实世界中的带路由的交换机设备,它也拥有一个隐藏的 MAC 地址,供设备中的三层协议处理程序和管理程序使用。设备里的三层协议处理程序,对应名为 bridge0 的通用网络设备的三层协议处理程序,即寄主 Linux 系统内核协议栈程序。设备里的管理程序,对应 bridge0 寄主 Linux 系统里的应用程序。
Bridge 的实现当前有一个限制:当一个设备被 attach 到 Bridge 上时,那个设备的 IP 会变的无效,Linux 不再使用那个 IP 在三层接受数据。举例如下:如果 eth0 本来的 IP 是 192.168.1.2,此时如果收到一个目标地址是 192.168.1.2 的数据,Linux 的应用程序能通过 Socket 操作接受到它。而当 eth0 被 attach 到一个 bridge0 时,尽管 eth0 的 IP 还在,但应用程序是无法接受到上述数据的。此时应该把 IP 192.168.1.2 赋予 bridge0。
另外需要注意的是数据流的方向。对于一个被 attach 到 Bridge 上的设备来说,只有它收到数据时,此包数据才会被转发到 Bridge 上,进而完成查表广播等后续操作。当请求是发送类型时,数据是不会被转发到 Bridge 上的,它会寻找下一个发送出口。用户在配置网络时经常忽略这一点从而造成网络故障。
VLAN 又称虚拟网络,是一个被广泛使用的概念,有些应用程序把自己的内部网络也称为 VLAN。此处主要说的是在物理世界中存在的,需要协议支持的 VLAN。它的种类很多,按照协议原理一般分为:MACVLAN、802.1.q VLAN、802.1.qbg VLAN、802.1.qbh VLAN。其中出现较早,应用广泛并且比较成熟的是 802.1.q VLAN,其基本原理是在二层协议里插入额外的 VLAN 协议数据(称为 802.1.q VLAN Tag),同时保持和传统二层设备的兼容性。Linux 里的 VLAN 设备是对 802.1.q 协议的一种内部软件实现,模拟现实世界中的 802.1.q 交换机。
图 2 .VLAN 设备工作过程
如图所示,Linux 里 802.1.q VLAN 设备是以母子关系成对出现的,母设备相当于现实世界中的交换机 TRUNK 口,用于连接上级网络,子设备相当于普通接口用于连接下级网络。当数据在母子设备间传递时,内核将会根据 802.1.q VLAN Tag 进行对应操作。母子设备之间是一对多的关系,一个母设备可以有多个子设备,一个子设备只有一个母设备。当一个子设备有一包数据需要发送时,数据将被加入 VLAN Tag 然后从母设备发送出去。当母设备收到一包数据时,它将会分析其中的 VLAN Tag,如果有对应的子设备存在,则把数据转发到那个子设备上并根据设置移除 VLAN Tag,否则丢弃该数据。在某些设置下,VLAN Tag 可以不被移除以满足某些监听程序的需要,如 DHCP 服务程序。举例说明如下:eth0 作为母设备创建一个 ID 为 100 的子设备 eth0.100。此时如果有程序要求从 eth0.100 发送一包数据,数据将被打上 VLAN 100 的 Tag 从 eth0 发送出去。如果 eth0 收到一包数据,VLAN Tag 是 100,数据将被转发到 eth0.100 上,并根据设置决定是否移除 VLAN Tag。如果 eth0 收到一包包含 VLAN Tag 101 的数据,其将被丢弃。上述过程隐含以下事实:对于寄主 Linux 系统来说,母设备只能用来收数据,子设备只能用来发送数据。和 Bridge 一样,母子设备的数据也是有方向的,子设备收到的数据不会进入母设备,同样母设备上请求发送的数据不会被转到子设备上。可以把 VLAN 母子设备作为一个整体想象为现实世界中的 802.1.q 交换机,下级接口通过子设备连接到寄主 Linux 系统网络里,上级接口同过主设备连接到上级网络,当母设备是物理网卡时上级网络是外界真实网络,当母设备是另外一个 Linux 虚拟网络设备时上级网络仍然是寄主 Linux 系统网络。
需要注意的是母子 VLAN 设备拥有相同的 MAC 地址,可以把它当成现实世界中 802.1.q 交换机的 MAC,因此多个 VLAN 设备会共享一个 MAC。当一个母设备拥有多个 VLAN 子设备时,子设备之间是隔离的,不存在 Bridge 那样的交换转发关系,原因如下:802.1.q VLAN 协议的主要目的是从逻辑上隔离子网。现实世界中的 802.1.q 交换机存在多个 VLAN,每个 VLAN 拥有多个端口,同一 VLAN 端口之间可以交换转发,不同 VLAN 端口之间隔离,所以其包含两层功能:交换与隔离。Linux VLAN device 实现的是隔离功能,没有交换功能。一个 VLAN 母设备不可能拥有两个相同 ID 的 VLAN 子设备,因此也就不可能出现数据交换情况。如果想让一个 VLAN 里接多个设备,就需要交换功能。在 Linux 里 Bridge 专门实现交换功能,因此将 VLAN 子设备 attach 到一个 Bridge 上就能完成后续的交换功能。总结起来,Bridge 加 VLAN device 能在功能层面完整模拟现实世界里的 802.1.q 交换机。
Linux 支持 VLAN 硬件加速,在安装有特定硬件情况下,图中所述内核处理过程可以被放到物理设备上完成。
TUN/TAP 设备是一种让用户态程序向内核协议栈注入数据的设备,一个工作在三层,一个工作在二层,使用较多的是 TAP 设备。VETH 设备出现较早,它的作用是反转通讯数据的方向,需要发送的数据会被转换成需要收到的数据重新送入内核网络层进行处理,从而间接的完成数据的注入。
图 3 .TAP 设备和 VETH 设备工作过程
如图所示,当一个 TAP 设备被创建时,在 Linux 设备文件目录下将会生成一个对应 char 设备,用户程序可以像打开普通文件一样打开这个文件进行读写。当执行 write()操作时,数据进入 TAP 设备,此时对于 Linux 网络层来说,相当于 TAP 设备收到了一包数据,请求内核接受它,如同普通的物理网卡从外界收到一包数据一样,不同的是其实数据来自 Linux 上的一个用户程序。Linux 收到此数据后将根据网络配置进行后续处理,从而完成了用户程序向 Linux 内核网络层注入数据的功能。当用户程序执行 read()请求时,相当于向内核查询 TAP 设备上是否有需要被发送出去的数据,有的话取出到用户程序里,完成 TAP 设备的发送数据功能。针对 TAP 设备的一个形象的比喻是:使用 TAP 设备的应用程序相当于另外一台计算机,TAP 设备是本机的一个网卡,他们之间相互连接。应用程序通过 read()/write()操作,和本机网络核心进行通讯。
VETH 设备总是成对出现,送到一端请求发送的数据总是从另一端以请求接受的形式出现。该设备不能被用户程序直接操作,但使用起来比较简单。创建并配置正确后,向其一端输入数据,VETH 会改变数据的方向并将其送入内核网络核心,完成数据的注入。在另一端能读到此数据。
为了更好的说明 Linux 网络设备的用法,下面将用一系列的例子,说明在一个复杂的 Linux 网络元素组合出的虚拟网络里,数据的流向。网络设置简介如下:一个中心 Bridge:bridge0 下 attach 了 4 个网络设备,包括 2 个 VETH 设备,1 个 TAP 设备 tap0,1 个物理网卡 eth0。在 VETH 的另外一端又创建了 VLAN 子设备。Linux 上共存在 2 个 VLAN 网络,既 vlan100 与 vlan200。物理网卡和外部网络相连,并且在它之下创建了一个 VLAN ID 为 200 的 VLAN 子设备。
图 4 .ARP from vlan100 child device
如图所示,当用户尝试 ping 192.168.100.3 时,Linux 将会根据路由表,从 vlan100 子设备发出 ARP 报文,具体过程如下:
1) 用户 ping 192.168.100.3
2) Linux 向 vlan100 子设备发送 ARP 信息。
3) ARP 报文被打上 VLAN ID 100 的 Tag 成为 ARP@vlan100,转发到母设备上。
4) VETH 设备将这一发送请求转变方向,成为一个需要接受处理的报文送入内核网络模块。
5) 由于对端的 VETH 设备被加入到了 bridge0 上,并且内核发现它收到一个报文,于是报文被转发到 bridge0 上。
6) bridge0 处理此 ARP@vlan100 信息,根据 TCP/IP 二层协议发现是一个广播请求,于是向它所知道的所有端口广播此报文,其中一路进入另一对 VETH 设备的一端,一路进入 TAP 设备 tap0,一路进入物理网卡设备 eth0。此时在 tap0 上,用户程序可以通过 read()操作读到 ARP@vlan100,eth0 将会向外界发送 ARP@vlan100,但 eth0 的 VLAN 子设备不会收到它,因为此数据方向为请求发送而不是请求接收。
7) VETH 将请求方向转换,此时在另一端得到请求接受的 ARP@vlan100 报文。
8) 对端 VETH 设备发现有数据需要接受,并且自己有两个 VLAN 子设备,于是执行 VLAN 处理逻辑。其中一个子设备是 vlan100,与 ARP@vlan100 吻合,于是去除 VLAN ID 100 的 Tag 转发到这个子设备上,重新成为标准的以太网 ARP 报文。另一个子设备由于 ID 不吻合,不会得到此报文。
9) 此 VLAN 子设备又被 attach 到另一个桥 bridge1 上,于是转发自己收到的 ARP 报文。
10) bridge1 广播 ARP 报文。
11) 最终另外一个 TAP 设备 tap1 收到此请求发送报文,用户程序通过 read()可以得到它。
图 5 .ARP from vlan200 child device
和前面情况类似,区别是 VLAN ID 是 200,对端的 vlan200 子设备设置为 reorder_hdr = 0,表示此设备被要求保留收到的报文中的 VLAN Tag。此时子设备会收到 ARP 报文,但是带了 VLAN ID 200 的 Tag,既 ARP@vlan200。
图 5 .ARP from central bridge
当 bridge0 拥有 IP 时,通过 Linux 路由表用户程序可以直接将 ARP 报文发向 bridge0。这时 tap0 和外部网络都能收到 ARP,但 VLAN 子设备由于 VLAN ID 过滤的原因,将收不到 ARP 信息。
图 6 .ARP from external network
当外部网络连接在一个支持 VLAN 并且对应端口为 vlan200 时,此情况会发生。此时所有的 VLAN ID 为 200 的 VLAN 子设备都将接受到报文,如果设置 reorder_hdr=0 则会收到带 Tag 的 ARP@vlan200。
图 7 .ping from TAP device
给 tap0 赋予 IP 并加入路由,此时再 Ping 其对应网段的未知 IP 会产生 ARP 发送请求。需要注意的是此时由于 tap0 上存在的是发送而不是接收请求,因此 ARP 报文不会被转发到桥上,从而什么也不会发生。图中右边画了一个类似情况:从 vlan200 子设备发送 ARP 请求。由于缺少 VETH 设备反转请求方向,因此报文也不会被转发到桥上,而是直接通过物理网卡发往外部网络。
图 8 .file operation on TAP device
用户程序指定 tap0 设备发送报文有两种方式:socket 和 file operation。当用 socket_raw 标志新建 socket 并指定设备编号时,可以要求内核将报文从 tap0 发送。但和前面的 ping from tap0 情况类似,由于报文方向问题,消息并不会被转发到 bridge0 上。当用 open()方式打开 tap 设备文件时,情况有所不同。当执行 write()操作时,内核认为 tap0 收到了报文,从而会触发转发动作,bridge0 将收到它。如果发送的报文如图所示,是一个以 A 为目的地的携带 VLAN ID 100 Tag 的单点报文,bridge0 将会找到对应的设备进行转发,对应的 VLAN 子设备将收到没有 VLAN ID 100 Tag 的报文。 Linux 上配置网络设备命令举例
以 Redhat6.2 红帽 Linux 发行版为例,如果已安装 VLAN 内核模块和管理工具 vconfig,TAP/TUN 设备管理工具 tunctl,那么可以用以下命令设置前述网络设备:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
使用默认的网络是不支持指派固定IP的
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 9 10 |
|
1 2 3 4 5 6 |
|
vim /etc/sysconfig/network-scripts/ifcfg-enp0s3
1
|
|
vim /etc/sysconfig/network-scripts/ifcfg-br0
1 2 3 4 |
|
service network restart
1 2 3 4 5 6 7 8 |
|