├── .gitattributes ├── Images ├── 20150511093020861.png ├── 20150511134819738.png ├── 20150511135126939.png ├── 20150511135207564.png ├── 20150930104756340.png ├── 20170214105014180.png ├── 20170214105218009.png ├── 20170214105343056.png ├── 20170306140550331.png ├── QQ20170808-221548.png ├── linux_net_irqsched.png ├── linux_net_irqsched2.png ├── linux_net_multiqueue.png ├── linux_net_overview.png ├── linux_net_reuseport.png ├── linux_net_reuseport2.png ├── linux_net_rfs.png └── linux_net_rps.png ├── docker └── Docker pluginV2 管理机制.md ├── linux网络相关 ├── LACP简介.md ├── Linux-TC框架详解.md ├── Linux网络IO并行化技术概览.md ├── Tc 网卡多队列时每个队列配置公平队列sfq.md ├── 二层报文处理与tcpdump增加hook方式.md └── 巨页总结.md └── openstack ├── KVM上如何绑定虚拟机vcpu与物理CPU.md ├── Ovs bond.md ├── Sriov 下一些命令.md └── 流表的类型与查看.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /Images/20150511093020861.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20150511093020861.png -------------------------------------------------------------------------------- /Images/20150511134819738.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20150511134819738.png -------------------------------------------------------------------------------- /Images/20150511135126939.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20150511135126939.png -------------------------------------------------------------------------------- /Images/20150511135207564.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20150511135207564.png -------------------------------------------------------------------------------- /Images/20150930104756340.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20150930104756340.png -------------------------------------------------------------------------------- /Images/20170214105014180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20170214105014180.png -------------------------------------------------------------------------------- /Images/20170214105218009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20170214105218009.png -------------------------------------------------------------------------------- /Images/20170214105343056.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20170214105343056.png -------------------------------------------------------------------------------- /Images/20170306140550331.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/20170306140550331.png -------------------------------------------------------------------------------- /Images/QQ20170808-221548.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/QQ20170808-221548.png -------------------------------------------------------------------------------- /Images/linux_net_irqsched.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_irqsched.png -------------------------------------------------------------------------------- /Images/linux_net_irqsched2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_irqsched2.png -------------------------------------------------------------------------------- /Images/linux_net_multiqueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_multiqueue.png -------------------------------------------------------------------------------- /Images/linux_net_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_overview.png -------------------------------------------------------------------------------- /Images/linux_net_reuseport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_reuseport.png -------------------------------------------------------------------------------- /Images/linux_net_reuseport2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_reuseport2.png -------------------------------------------------------------------------------- /Images/linux_net_rfs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_rfs.png -------------------------------------------------------------------------------- /Images/linux_net_rps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liucimin/Learning/20c072357ef8f88c0d941e353788126dde1ebac3/Images/linux_net_rps.png -------------------------------------------------------------------------------- /docker/Docker pluginV2 管理机制.md: -------------------------------------------------------------------------------- 1 | 2 | [Xiaowei's Note](https://xiaoweiqian.github.io/note/) 3 | ## Introduction 4 | 5 | Docker pluginV2 管理机制可以使得第三方插件以镜像的方式管理起来,插件不再需要独立部署和维护,而是由docker统一维护,各个节点可以方便的从镜像仓库安装、升级和删除插件。 6 | 7 | ## Prepare 8 | 9 | - 网络插件开发参考 10 | 11 | [网络插件开发](https://xiaoweiqian.github.io/note/docker-network-remote-driver/) 12 | 13 | ## Configration 14 | 15 | 推荐使用alpine基础镜像(4M)作为plugin的运行环境,插件制作过程分为如下几个步骤。 16 | 17 | - 插件编译 18 | 19 | 插件编译和插件运行环境必须一致,因此使用alpine作为编译环境。 20 | 21 | 1. 编写Dockerfile.dev 22 | 23 | 若插件基于golang开发,使用golang:1.7.5-alpine3.5基础镜像作为插件编译环境 24 | 25 | ``` 26 | FROM golang:1.7.5-alpine3.5 27 | 28 | COPY . /go/src/github.com/XiaoweiQian/macvlan-driver 29 | WORKDIR /go/src/github.com/XiaoweiQian/macvlan-driver 30 | 31 | RUN set -ex \ 32 | && apk add --no-cache --virtual .build-deps \ 33 | gcc libc-dev linux-headers \ 34 | && go install --ldflags '-extldflags "-static"' \ 35 | && apk del .build-deps 36 | 37 | CMD ["/go/bin/macvlan-driver"] 38 | ``` 39 | 40 | 41 | ``` 42 | 43 | 2. 编写Makefile 44 | 45 | 启动插件编译容器,把编译成功的插件二进制文件拷贝到本地 46 | 47 | ``` 48 | compile: 49 | @echo "### compile docker-macvlan plugin" 50 | @docker build -q -t builder -f Dockerfile.dev . 51 | @echo "### extract docker-macvlan" 52 | @docker create --name tmp builder 53 | @docker cp tmp:/go/bin/macvlan-driver ./docker-macvlan 54 | @docker rm -vf tmp 55 | @docker rmi builder 56 | 57 | ``` 58 | 59 | - 插件打包 60 | 61 | 一个标准的docker第三方插件必须包含config.json文件和rootfs文件系统。 62 | 63 | 1. 编写Dockerfile 64 | 65 | 以alpine镜像为基础运行环境,把插件可执行文件打包到镜像中 66 | 67 | ``` 68 | FROM alpine 69 | 70 | RUN mkdir -p /run/docker/plugins 71 | 72 | COPY docker-macvlan /usr/bin/docker-macvlan 73 | 74 | CMD ["/usr/bin/docker-macvlan"] 75 | 76 | ``` 77 | 78 | 2. 编写config.json 79 | 80 | 配置插件基本参数 81 | 82 | ``` 83 | { 84 | "description": "macvlan Net plugin for Docker swarm", 85 | "documentation": "", 86 | "entrypoint": [ 87 | "docker-macvlan" 88 | ], 89 | "interface": { 90 | "socket": "macvlan_swarm.sock", 91 | "types": [ 92 | "docker.networkdriver/1.0" 93 | ] 94 | }, 95 | "linux": { 96 | "capabilities": [ 97 | "CAP_SYS_ADMIN", 98 | "CAP_NET_ADMIN" 99 | ] 100 | }, 101 | "mounts": null, 102 | "env": [ 103 | { 104 | "description": "Extra args to `macvlan_swarm` and `plugin`", 105 | "name": "EXTRA_ARGS", 106 | "settable": [ 107 | "value" 108 | ], 109 | "value": "" 110 | } 111 | ], 112 | "network": { 113 | "type": "host" 114 | }, 115 | "workdir": "" 116 | } 117 | 118 | ``` 119 | 120 | 3. 编写Makefile 121 | 122 | 生成插件文件系统rootfs,和config.json拷贝到插件目录plugin 123 | 124 | ``` 125 | rootfs: 126 | @echo "### docker build: rootfs image with docker-macvlan" 127 | @docker build -q -t ${PLUGIN_NAME}:rootfs . 128 | @echo "### create rootfs directory in ./plugin/rootfs" 129 | @mkdir -p ./plugin/rootfs 130 | @docker create --name tmp ${PLUGIN_NAME}:rootfs 131 | @docker export tmp | tar -x -C ./plugin/rootfs 132 | @echo "### copy config.json to ./plugin/" 133 | @cp config.json ./plugin/ 134 | @docker rm -vf tmp 135 | @docker rmi ${PLUGIN_NAME}:rootfs 136 | 137 | ``` 138 | 139 | - 插件上传 140 | 141 | 插件可以上传到hub公共仓库,也可以上传到本地私有仓库,如果要上传本地私有库,插件的名字必须按照此格式192.168.1.2:5000/plugin_name,才能上传成功。 142 | 143 | 1. 编写Makefile 144 | 145 | 通过指定插件目录plugin创建本地插件,启用插件并上传到镜像仓库 146 | 147 | ``` 148 | create: 149 | @echo "### remove existing plugin ${PLUGIN_NAME} if exists" 150 | @docker plugin rm -f ${PLUGIN_NAME} || true 151 | @echo "### create new plugin ${PLUGIN_NAME} from ./plugin" 152 | @docker plugin create ${PLUGIN_NAME} ./plugin 153 | 154 | enable: 155 | @echo "### enable plugin ${PLUGIN_NAME}" 156 | @docker plugin enable ${PLUGIN_NAME} 157 | 158 | push: clean compile rootfs create enable 159 | @echo "### push plugin ${PLUGIN_NAME}" 160 | @docker plugin push ${PLUGIN_NAME} 161 | 162 | ``` 163 | 164 | - 插件安装 165 | 166 | 所有docker节点通过docker plugin install命令安装插件 167 | 168 | ``` 169 | docker plugin install ${PLUGIN_NAME} --grant-all-permissions 170 | 171 | ``` -------------------------------------------------------------------------------- /linux网络相关/LACP简介.md: -------------------------------------------------------------------------------- 1 | 一、LACP简介 2 | 3 | ​ 1、LACP协议简介 4 | 5 | ​ 基于 IEEE802.3ad 标准的LACP(Link Aggregation Control Protocol,链路汇聚控 6 | 7 | ​ 制协议)是一种实现链路动态汇聚与解汇聚的协议。LACP 协议通过LACPDU(Link 8 | 9 | ​ Aggregation Control Protocol Data Unit,链路汇聚控制协议数据单元)与对端交互信息。 10 | 11 | ​ 使能某端口的 LACP 协议后,该端口将通过发送LACPDU 向对端通告自己的系统优 12 | 13 | ​ 先级、系统MAC、端口优先级、端口号和操作Key。对端接收到这些信息后,将这些信息与其它端口所保存的信息比较以选择能够汇聚的端口,从而双方可以对端口 14 | 15 | ​ 加入或退出某个动态汇聚组达成一致。 16 | 17 | ​ 2、LACP报文 18 | 19 | ​ ![img](http://www.dnzg.cn/uploads/allimg/130725/2130503246-0.gif) 20 | 21 | ​ 主要字段介绍: 22 | 23 | ​ Actor_Port/Partner_Port:本端/对端接口信息。 24 | 25 | ​ Actor_State/Partner_State:本端/对端状态。 26 | 27 | ​ Actor_System_Priority/Partner_System_Priority:本端/对端系统优先级。 28 | 29 | ​ Actor_System/Partner_System:本端/对端系统ID。 30 | 31 | ​ Actor_Key/Partner_Key:本端/对端操作Key,各接口的该值相同才能够聚合。 32 | 33 | ​ Actor_Port_Priority/Partner_Port_Priority:本端/对端接口优先级。 34 | 35 | ​ 二、链路聚合的分类 36 | 37 | ​ 1、 手工负载分担模式链路聚合 38 | 39 | ​ 1)手工汇聚概述 40 | 41 | ​ 手工负载分担模式是一种最基本的链路聚合方式,在该模式下,Eth-Trunk 接口的建 42 | 43 | ​ 立,成员接口的加入完全由手工来配置,没有链路聚合控制协议的参与。该模式下所有成员接口(selected)都参与数据的转发,分担负载流量,因此称为手工负载分担模式。手工汇聚端口的 LACP 协议为关闭状态,禁止用户使能手工汇聚端口的LACP 协议。 44 | 45 | ​ 2) 手工汇聚组中的端口状态 46 | 47 | ​ 在手工汇聚组中,端口可能处于两种状态:Selected 或Standby。处于Selected 状 48 | 49 | ​ 态且端口号最小的端口为汇聚组的主端口,其他处于Selected 状态的端口为汇聚组 50 | 51 | ​ 的成员端口。 52 | 53 | ​ 由于设备所能支持的汇聚组中的最大端口数有限制,如果处于Selected 状态的端口 54 | 55 | ​ 数超过设备所能支持的汇聚组中的最大端口数,系统将按照端口号从小到大的顺序 56 | 57 | ​ 选择一些端口为Selected 端口,其他则为Standby 端口。 58 | 59 | ​ 3)手工汇聚对端口配置的要求 60 | 61 | ​ 一般情况下,手工汇聚对汇聚前的端口速率和双工模式不作限制。但对于以下情况, 62 | 63 | ​ 系统会作特殊处理: 64 | 65 | ​ 对于初始就处于 DOWN 状态的端口,在汇聚时对端口的速率和双工模式没有限制; 66 | 67 | ​ 对于曾经处于 UP 状态,并协商或强制指定过端口速率和双工模式,而当前处于DOWN 状态的端口,在汇聚时要求速率和双工模式一致; 68 | 69 | ​ 对于一个汇聚组,当汇聚组中某个端口的速率和双工模式发生改变时,系统不进行解汇聚,汇聚组中的端口也都处于正常工作状态。但如果是主端口出现速率降低和双工模式变化,则该端口的转发可能出现丢包现象。 70 | 71 | ​ 2、 LACP 协议链路聚合 72 | 73 | ​ LACP(Link Aggregation Control Protocol)链路聚合包含两种类型: 74 | 75 | ​ 1) 静态 LACP 模式链路聚合 76 | 77 | ​ a)静态 LACP 模式链路聚合简介 78 | 79 | ​ 静态 LACP 模式下,Eth-Trunk 80 | 接口的建立,成员接口的加入,都是由手工配置完成的。但与手工负载分担模式链路聚合不同的是,该模式下LACP 81 | 协议报文参与活动接口的选择。也就是说,当把一组接口加入Eth-Trunk 82 | 接口后,这些成员接口中哪些接口作为活动接口,哪些接口作为非活动接口还需要经过LACP 协议报文的协商确定。 83 | 84 | ​ 静态汇聚端口的 LACP 协议为使能状态,当一个静态汇聚组被删除时,其成员端口 85 | 86 | ​ 将形成一个或多个动态LACP 汇聚,并保持LACP 使能。禁止用户关闭静态汇聚端口的LACP 协议。 87 | 88 | ​ b)静态汇聚组中的端口状态 89 | 90 | ​ 在静态汇聚组中,端口可能处于两种状态:Selected 或Standby。Selected 端口和 91 | 92 | ​ Standby 端口都能收发LACP 协议,但Standby 端口不能转发用户报文。 93 | 94 | ​ 说明: 95 | 96 | ​ 在一个汇聚组中,处于Selected 状态且端口号最小的端口为汇聚组的主端口,其他 97 | 98 | ​ 处于Selected 状态的端口为汇聚组的成员端口。 99 | 100 | ​ 在静态汇聚组中,系统按照以下原则设置端口处于 Selected 或者Standby 状态: 101 | 102 | ​ 系统按照端口全双工/高速率、全双工/低速率、半双工/高速率、半双工/低速率的优先次序,选择优先次序最高的端口处于Selected 状态,其他端口则处于Standby 状态。 103 | 104 | ​ 与处于 Selected 状态的最小端口所连接的对端设备不同,或者连接的是同一个对端设备但端口在不同的汇聚组内的端口将处于Standby 状态。 105 | 106 | ​ 端口因存在硬件限制(如不能跨板汇聚)无法汇聚在一起,而无法与处于Selected 状态的最小端口汇聚的端口将处于Standby 状态。 107 | 108 | ​ 与处于 Selected 状态的最小端口的基本配置不同的端口将处于Standby 状态。由于设备所能支持的汇聚组中的 Selected 109 | 端口数有限制,如果当前的成员端口数超过了设备所能支持的最大Selected 端口数,系统将按照端口号从小到大的顺序选择一些端口为Selected 110 | 端口,其他则为Standby 端口。 111 | 112 | ​ 2) 动态 LACP 模式链路聚合 113 | 114 | ​ a)动态 LACP 模式链路聚合简介 115 | 116 | ​ 动态 LACP 模式下,Eth-Trunk 接口的建立,成员接口的加入,活动接口的选择完 117 | 118 | ​ 全由LACP 协议通过协商完成。这就意味着启用了动态LACP 协议的两台直连设备上,不需要创建Eth-Trunk 119 | 接口,也不需要指定哪些接口作为聚合组成员接口,两台设备会通过LACP 协商自动完成链路的聚合操作。动态 LACP 120 | 汇聚是一种系统自动创建/删除的汇聚,不允许用户增加或删除动态LACP 121 | 汇聚中的成员端口。只有速率和双工属性相同、连接到同一个设备、有相同基本配置的端口才能被动态汇聚在一起。即使只有一个端口也可以创建动态汇聚,此时为单端口汇聚。动态汇聚中,端口的LACP 122 | 协议处于使能状态。 123 | 124 | ​ b)动态汇聚组中的端口状态 125 | 126 | ​ 在动态汇聚组中,端口可能处于两种状态:Selected 或Standby。Selected 端口和 127 | 128 | ​ Standby 端口都能收发LACP 协议,但Standby 端口不能转发用户报文。由于设备所能支持的汇聚组中的最大端口数有限制,如果当前的成员端口数量超过了最大端口数的限制,则本端系统和对端系统会进行协商,根据设备ID 优的一端的 129 | 130 | ​ 端口ID 的大小,来决定端口的状态。具体协商步骤如下: 131 | 132 | ​ 比较设备 ID(系统优先级+系统MAC 地址)。先比较系统优先级,如果相同再比较系统MAC 地址。设备ID 小的一端被认为优。 133 | 134 | ​ 比较端口 ID(端口优先级+端口号)。对于设备ID 优的一端的各个端口,首先比较端口优先级,如果优先级相同再比较端口号。端口ID 小的端口为 135 | 136 | ​ Selected 端口,剩余端口为Standby 端口。在一个汇聚组中,处于Selected 状态且端口号最小的端口为汇聚组的主端口,其他处于Selected 状态的端口为汇聚组的成员端口。 137 | 138 | ​ 说明: 139 | 140 | ​ 与手工汇聚组不同的是,在静态汇聚组和动态汇聚组中,处于 DOWN 的端口为 141 | 142 | ​ Standby 状态。 143 | 144 | ​ 三、LACP实现原理 145 | 146 | ​ 1、手工汇聚原理 147 | 148 | ​ 手工负载分担模式链路聚合是应用比较广泛的一种链路聚合,大多数运营级网络设备 149 | 150 | ​ 均支持该特性,当需要在两个直连设备间提供一个较大的链路带宽而对端设备又不支 151 | 152 | ​ 持LACP 协议时,可以使用手工负载分担模式 153 | 154 | ​ ![img](http://www.dnzg.cn/uploads/allimg/130725/2130502135-1.gif) 155 | 156 | ​ 说明: 157 | 158 | ​ 手工负载分担模式的Eth-Trunk 接口可以聚合不同单板、不同双工模式的成员接口。 159 | 160 | ​ 2、静态汇聚原理 161 | 162 | ​ a)基本概念 163 | 164 | ​ 静态LACP 模式链路聚合是一种利用LACP 协议进行参数协商选取活动链路的聚合模 165 | 166 | ​ 式。该模式由LACP 协议确定聚合组中的活动和非活动链路,又称为M∶N 模式,即 167 | 168 | ​ M 条活动链路与N 条备份链路的模式。这种模式提供了更高的链路可靠性,并且可以 169 | 170 | ​ 在M 条链路中实现不同方式的负载均衡。 171 | 172 | ​ ![img](http://www.dnzg.cn/uploads/allimg/130725/21305025V-2.gif) 173 | 174 | ​ M:N 模式的Eth-Trunk 接口中M 和N 的值可以通过配置活动接口数上限阈值来确定。 175 | 176 | ​ b)系统 LACP 优先级 177 | 178 | ​ 静态LACP 179 | 模式下,两端设备所选择的活动接口必须保持一致,否则链路聚合组就无法建立。而要想使两端活动接口保持一致,可以使其中一端具有更高的优先级,另一端根据高优先级的一端来选择活动接口即可。系统LACP 180 | 优先级就是为了区分两端优先级的高低而配置的参数。 181 | 182 | ​ 系统 LACP 优先级值越小优先级越高,缺省系统LACP 优先级值为32768。 183 | 184 | ​ c)接口 LACP 优先级 185 | 186 | ​ 接口LACP 优先级是为了区别不同接口被选为活动接口的优先程度。接口LACP 优先级值越小,优先级越高。缺省情况下,接口LACP 优先级为32768。 187 | 188 | ​ d)静态模式Eth-Trunk 接口建立过程 189 | 190 | ​ 静态模式Eth-Trunk 接口建立过程如下所示: 191 | 192 | ​ ① 两端互相发送 LACPDU 报文。 193 | 194 | ​ ② 两端设备根据系统 LACP 优先级确定主动端。 195 | 196 | ​ ③ 两端设备根据接口LACP 优先级确定活动接口,最终以主动端设备的活动接口确定两端的活动接口。 197 | 198 | ​ e) 互发 LACPDU 报文 199 | 200 | ​ 在两端设备CX-A 和CX-B 上创建Eth-Trunk 接口并配置为静态LACP 模式,然后向Eth-Trunk 接口中手工加入成员接口。此时成员接口上便启用了LACP 协议,两端互相发出LACPDU 报文,如下图所示。 201 | 202 | ​ ![img](http://www.dnzg.cn/uploads/allimg/130725/213050OQ-3.gif) 203 | 204 | ​ f)确定主动端 205 | 206 | ​ Eth-Trunk 两端设备均会收到对端发来的LACP 报文,根据报文中的优先级字段,确认 207 | 208 | ​ 活动接口。优先级字段的值越小,优先级越高。 209 | 210 | ​ 如下图所示,当CX-B 收到CX-A 发送LACP 报文时,CX-B 会查看并记录对端信息,并且比较系统优先级字段。CX-A 211 | 的系统优先级为10,高于CX-B 的系统优先级,所以选择CX-A 为主动端。此时CX-B 将按照CX-A 的接口优先级选择活动接口。如果 212 | Eth-Trunk 链路两端设备的系统优先级一致,系统将选择系统ID 字段较小的作为主动端。系统ID 由设备的MAC 地址产生。 213 | ​ 214 | 215 | ![img](http://www.dnzg.cn/uploads/allimg/130725/21305063K-4.gif) 216 | 217 | ​ g)选择活动接口 218 | ​ 选出主动端后,两端都会以主动端的接口优先级来选择活动接口。如上图所示,CX-A 为主动端,CX-A 的接口GE1/0/1、GE1/0/2 219 | 的优先级高于接口GE2/0/1,此时接口GE1/0/1、GE1/0/2 被选为活动接口,组成LACP 聚合组,以负载分担的方式转发数据。 220 | ​ 2、静态汇聚原理 221 | ​ 动态汇聚和静态汇聚原理类似,只是动态汇聚中所有端口都是通过协议确定,而不是像静态汇聚通过协议在指定端口中确定汇聚相关端口。 222 | ​ 四、实现细节 223 | ​ 1、链路聚合控制的相关参数 224 | ​ a)LACP协议如何唯一的标识聚合组: 225 | ​ 系统ID(System ID) ,由“系统优先级+系统MAC地址”组成,其中,之所以要有“系统优先级”,是因为LACP协议中,链路聚合两端设备扮演不同角色,有了“系统优先级”,管理员可以通过配置干预角色选举。 226 | ​ b)端口ID(Port ID): 227 | ​ 对于参与链路聚合的各个端口,也需要在设备内部唯一标识,端口ID由“端口优先级+端口号”组成,之所以需要“端口优先级”,也是因为涉及端口的不同角色选举 228 | ​ c)Aggregator ID: 229 | ​ 在一个设备上,能进行多组聚合,即有多个Aggregator,为了区分这些Aggregator,给每个Aggregator分配了一个聚合ID(Aggregator ID),为一个16位整数 230 | ​ 2、端口key 231 | ​ 聚合端口中有两种key:一种是操作key,一种是管理key。 232 | ​ 操作key是为形成聚合当前使用的key,管理key是允许管理者对key值进行操作的key。 233 | ​ 3、 操作key 234 | ​ 在动态LACP聚合中,只有操作KEY相同的端口才能属于同一个聚合组,你可以认为操作KEY相同的端口,其属性相同。 235 | ​ 在手工聚合和静态LACP聚合中,虽然同一个聚合组中的端口的操作KEY不一定相同(因端口由管理员手工加入),但是Selected端口的操作KEY一定相同。 236 | ​ 操作Key 是在端口汇聚时,系统根据端口的配置(即速率、双工、基本配置、管理 237 | ​ Key)生成的一个配置组合。 238 | ​ (1) 对于手工汇聚组和静态汇聚组,Selected 的端口有相同的操作Key。 239 | ​ (2) 静态汇聚端口在使能LACP 后,端口的管理Key 与汇聚组ID 相同。 240 | ​ (3) 动态汇聚端口在使能LACP 协议后,其管理Key 缺省为零。 241 | ​ (4) 对于动态汇聚组,同组成员一定有相同的操作Key。 242 | ​ 4、六要素 243 | ​ a)四个要素: 244 | ​ 一个聚合组来说,如果需要进行唯一标识的话,需要包含四个元素:本端系统ID、本端操作KEY、对端系统ID、对端操作KEY 245 | ​ b)两个要素: 246 | ​ 系统中并不是所有聚合组都包含多个链路,为了区分只包含单个链路的聚合组的情况,还需要额外加上两个元素:本端端口ID和对端端口ID。 247 | ​ c)结论: 248 | ​ 这六个元素唯一确定了一个聚合组,称为聚合组 ID(Link Aggregation Group ID,LAG ID)。如果一个聚合组中包含多个链路,那么LAG ID中,本端端口ID和对端端口ID为0,相当于只用四元组就可以刻画包含多个链路的聚合组。 249 | ​ 5、端口类型: 250 | ​ a)Selectet和Unselected: 251 | ​ 参与流量转发的端口称为Selected端口,否则称为Unselected端口 252 | ​ b)主端口(master端口) 253 | ​ 处于Selected状态且端口号最小的端口称为主端口(Master 254 | Port),可以形象的认为,聚合组中的所有端口被汇聚到了主端口,主端口在逻辑上代表了整个聚合组,对于GVRP/GMRP、STP/RSTP/MSTP等二层协议,都只从主端口发送,其他数据报文则在各个Selected端口间分担。 255 | ​ c)补充: 256 | ​ 由于Selected与Unselected端口在实际状态下的选取受到硬件的影响,所以不同厂家产品的具体表现形式可能有差异 257 | ​ 5、LACP绑定端口 258 | ​ 判断将一个端口绑定到Aggregator的关键依据是 LAG ID,判断方法是: 259 | ​ (1)Aggregator的操作KEY和端口的操作KEY相同。 260 | ​ (2)已经绑定到这个Aggregator的其他端口和这个端口有相同的链路LAG ID,即与Aggregator关联的LAG ID必须和端口的LAG ID相同。 261 | ​ (3) “LAG ID”则指的是聚合组ID( Link Aggregation Group ID),“聚合ID”则指的是Aggregator 262 | ID. (LAG ID就是指属于同一个聚合中的所有port 包括selected 263 | 和Standby,对于手工和静态比较好理解,就是指用户所指定的所有port,对于动态汇聚,指所有port) 264 | ​ 6、端口离开Aggregator 265 | ​ (1)如果Actor端口在一定时间内(使用long timeout时是90s,使用short 266 | timeout是3秒)收不到Partner端口发送的LACP报文,就宣告自己处于超时状态,如果在下一个short 267 | timeout时间(3秒)内还没有收到Partner的报文,就会离开这个Aggregator。 268 | ​ (2)如果从Partner端口收到的LACP报文,发现LAG ID发生了改变(系统ID或操作KEY发生了变化,系统ID改变说明连接到的对端设备发生了变化,操作KEY发生了变化可能是对端端口的属性发生了变化),这时端口也会离开这个Aggregator。 269 | ​ (3)还有一种导致端口离开Aggregator的情况:Actor端口本身的属性发生了变化,设备通过动态操作KEY功能给它分配的操作KEY发生变化,导致和Aggregator的LAG ID不匹配,从而离开聚合组。 270 | ​ 7、Active模式和Passive模式 271 | ​ (1)Active模式下,端口正常周期性的发送LACP报文; 272 | ​ (2)Passive模式下,端口平时不发送LACP报文,不过,一旦收到了对端的LACP报文,就会正常发送LACP报文了。 273 | ​ 274 | ​ -------------------------------------------------------------------------------- /linux网络相关/Linux-TC框架详解.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **最重要的参考**: 4 | 5 | [Linux的高级路由和流量控制](http://www.lartc.org/LARTC-zh_CN.GB2312.pdf) 6 | 7 | [Open vSwitch之QoS的实现](http://www.sdnlab.com/19208.html) 8 | 9 | # TC的安装 10 | 11 | TC是Linux自带的模块,一般情况下不需要另行安装,可以用 man tc 查看tc 相关命令细节,tc 要求内核 2.4.18 以上 12 | 13 | ``` 14 | TC的安装 15 | TC是Linux自带的模块,一般情况下不需要另行安装,可以用 man tc 查看tc 相关命令细节,tc 要求内核 2.4.18 以上 16 | ``` 17 | 18 | # TC原理介绍 19 | 20 | ​ 21 | 22 | Linux中的QoS分为入口(Ingress)部分和出口(Egress)部分,入口部分主要用于进行入口流量限速(policing),出口部分主要用于队列调度(queuingscheduling)。大多数排队规则(qdisc)都是用于输出方向的,输入方向只有一个排队规则,即ingressqdisc。ingressqdisc本身的功能很有限,输入方向只有一个排队规则,即ingressqdisc(因为没有缓存只能实现流量的drop)但可用于重定向incomingpackets。通过Ingressqdisc把输入方向的数据包重定向到虚拟设备ifb,而ifb的输出方向可以配置多种qdisc,就可以达到对输入方向的流量做队列调度的目的。 23 | 24 | Ingress 限速只能对整个网卡入流量限速,无队列之分: 25 | 26 | Ingress 流量的限速 27 | 28 | ```shell 29 | tc qdisc add dev eth0 ingress 30 | 31 | tc filter add dev eth0 parent ffff: protocol ip prio 10 u32 match ipsrc 0.0.0.0/0 police rate 2048kbps burst 1m drop flowid :1 32 | 33 | ``` 34 | 35 | 36 | 37 | Egress 限速: 38 | 39 | Linux 操作系统中的流量控制器 TC(Traffic Control) 用于Linux内核的流量控制,它利用队列规定建立处理数据包的队列,并定义队列中的数据包被发送的方式,从而实现对流量的控制。TC 模块实现流量控制功能使用的队列规定分为两类,一类是无类队列规定,另一类是分类队列规定。无类队列规定相对简单,而分类队列规定则引出了分类和过滤器等概念,使其流量控制功能增强。 40 | 41 | ** 无类队列**规定是对进入网络设备(网卡)的数据流不加区分统一对待的队列规定。使用无类队列规定形成的队列能够接收数据包以及重新编排、延迟或丢弃数据包。这类队列规定形成的队列可以对整个网络设备(网卡)的流量进行整形,但不能细分各种情况。常用的无类队列规定主要有 pfifo_fast(先进先出)、TBF(令牌桶过滤器)、SFQ(随机公平队列)、ID(前向随机丢包)等等。这类队列规定使用的流量整形手段主要是排序、限速和丢包。 42 | 43 | ** 分类队列**规定是对进入网络设备的数据包根据不同的需求以分类的方式区分对待的队列规定。数据包进入一个分类的队列后,它就需要被送到某一个类中,也就是说需要对数据包做分类处理。对数据包进行分类的工具是过滤器,过滤器会返回一个决定,队列规定就根据这个决定把数据包送入相应的类进行排队。每个子类都可以再次使用它们的过滤器进行进一步的分类。直到不需要进一步分类时,数据包才进入该类包含的队列排队。除了能够包含其他队列规定之外,绝大多数分类的队列规定还能够对流量进行整形。这对于需要同时进行调度(如使用SFQ)和流量控制的场合非常有用。 44 | 45 | Linux流量控制的基本原理如下图所示。 46 | 47 | 48 | 49 | ![](..\Images\20150511093020861.png) 50 | 51 | 接收包从输入接口(Input Interface)进来后,经过流量限制(Ingress Policing)丢弃不符合规定的数据包,由输入多路分配器(Input De-Multiplexing)进行判断选择。如果接收包的目的地是本主机,那么将该包送给上层处理,否则需要进行转发,将接收包交到转发块(ForwardingBlock)处理。转发块同时也接收本主机上层(TCP、UDP等)产生的包。转发块通过查看路由表,决定所处理包的下一跳。然后,对包进行排列以便将它们传送到输出接口(Output Interface)。 52 | 53 | 54 | 55 | **一般我们只能限制网卡发送的数据包,不能限制网卡接收的数据包**,所以我们可以通过改变发送次序来控制传输速率。Linux流量控制主要是在**输出接口排列**时进行处理和实现的。 56 | 57 | 58 | 59 | # TC规则 60 | 61 | ## 1.流量控制方式 62 | 63 | ​ 流量控制包括一下几种方式: 64 | 65 | ### SHAPING(限制) 66 | 67 | ​ 当流量被限制时,它的传输速率就被控制在某个值以下。限制值可以大大小于有效带宽,这样可以平滑突发数据流量,使网络更为稳定。SHAPING(限制)只适用于向外的流量。 68 | 69 | ### SCHEDULING(调度) 70 | 71 | ​ 通过调度数据包的传输,可以在带宽范围内,按照优先级分配带宽。SCHEDULING(调度)也只适用于向外的流量。 72 | 73 | ### POLICING(策略) 74 | 75 | ​ SHAPING(限制)用于处理向外的流量,而POLICING(策略)用于处理接收到的数据。 76 | 77 | ### DROPPING(丢弃) 78 | 79 | ​ 如果流量超过某个设定的带宽,就丢弃数据包,不管是向内还是向外。 80 | 81 | ## 2.流量控制处理对象 82 | 83 | ​ 流量的处理由三种对象控制,它们是**:qdisc(排队规则)、class(类别)和filter(过滤器**)。 84 | 85 | ​ qdisc(排队规则)是 queueing discipline的简写,它是理解流量控制(traffic control)的基础。**无论何时,内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的qdisc(排队规则)把数据包加入队列。**然后,内核会尽可能多的从qdisc里面取出数据包,把它们交给网络适配器驱动模块。最简单的qdisc是pfifo他不对进入的数据包做任何的处理,数据包采用先进先出的方式通过队列。不过,它会保存网络接口一时无法处 理的数据包。 86 | 87 | ​ qddis(排队规则)分为 CLASSLESS QDISC和 CLASSFUL QDISC 类别如下: 88 | 89 | ### CLASSLESS QDISC (无类别QDISC) 90 | 91 | #### 1.无类别QDISC包括: 92 | 93 | ** [ p | b ]fifo**,使用最简单的qdisc(排队规则),纯粹的先进先出。只有一个参数:limit ,用来设置队列的长度,pfifo是以数据包的个数为单位;bfifo是以字节数为单位。 94 | 95 | ** ** 96 | 97 | ** pfifo_fast**,在编译内核时,如果打开了高级路由器(Advanced Router)编译选项,pfifo_fast 就是系统的标准qdisc(排队规则)。它的队列包括三个波段(band)。在每个波段里面,使用先进先出规则。而三个波段(band)的优先级也不相同,band 0 的优先级最高,band 2的最低。如果band 0里面有数据包,系统就不会处理band 1 里面的数据包,band 1 和 band 2 之间也是一样的。数据包是按照服务类型(Type Of Service,TOS )被分配到三个波段(band)里面的。 98 | 99 | ** red**,red是Random Early Detection(随机早期探测)的简写。如果使用这种qdsic,当带宽的占用接近与规定的带宽时,系统会随机的丢弃一些数据包。他非常适合高带宽的应用。 100 | 101 | ** sfq**,sfq是Stochastic Fairness Queueing 的简写。它会按照会话(session --对应与每个TCP 连接或者UDP流)为流量进行排序,然后循环发送每个会话的数据包。 102 | 103 | ** tbf**,tbf是 Token Bucket Filter 的简写,适用于把流速降低到某个值。 104 | 105 | #### 2.无类别qdisc的配置 106 | 107 | ​ 如果没有可分类qdisc,不可分类qdisc 只能附属于设备的根。它们的用法如下: 108 | 109 | 110 | 111 | ``` 112 | tc qdisc add dev DEV root QDISC QDISC_PARAMETERS 113 | ``` 114 | 115 | 要删除一个不可分类qdisc,需要使用如下命令 116 | 117 | ``` 118 | tc qdisc del dev DEV root 119 | ``` 120 | 121 | 一个网络接口上如果没有设置qdisc,pfifo_fast就作为缺省的qdisc。 122 | 123 | ### CLASSFUL QDISC(分类 QDISC) 124 | 125 | #### 可分类QDISC包括: 126 | 127 | ​ ** CBQ**,CBQ是 Class Based Queueing(基于类别排队)的缩写。它实现了一个丰富的连接共享类别结构,既有限制(shaping)带宽的能力,也具有带宽优先级别管理的能力。带宽限制是通过计算连接的空闲时间完成的。空闲时间的计算标准是数据包离队事件的频率和下层连接(数据链路层)的带宽。 128 | 129 | ​ 130 | 131 | ​ ** HTB**,HTB是Hierarchy Token Bucket 的缩写。通过在实践基础上的改进,它实现一个丰富的连接共享类别体系。使用HTB可以很容易地保证每个类别的带宽,虽然它也允许特定的类可以突破带宽上限,占用别的类的带宽。HTB可以通过TBF(Token Bucket Filter)实现带宽限制,也能够划分类别的优先级。 132 | 133 | ​ **PRIO**,PRIO qdisc 不能限制带宽,因为属于不同类别的数据包是顺序离队的。使用PRIO qdisc 可以很容易对流量进行优先级管理,只有属于高优先级类别的数据包全部发送完毕,参会发送属于低优先级类别的数据包。为了方便管理,需要使用iptables 或者 ipchains 处理数据包的服务类型(Type Of Service,TOS)。 134 | 135 | HTB型的一些注意: 136 | 1. HTB型class具有优先级,prio。可以指定优先级,数字低的优先级高,优先级范围从 0~7,0最高。 137 | 它的效果是:存在空闲带宽时,优先满足高优先级class的需求,使得其可以占用全部空闲带宽,上限为ceil所指定的值。若此时还有剩余空闲带宽,则优先级稍低的class可以借用之。依优先级高低,逐级分配。 138 | 139 | 2. 相同优先级的class分配空闲带宽时,按照自身class所指定的rate(即保证带宽)之间的比例瓜分空闲带宽。 140 | 例如:clsss A和B优先级相同,他们的rate比为3:5,则class A占用空闲的3/8,B占5/8。 141 | 142 | 3. tc filter的prio使用提示,prio表示该filter的测试顺序,小的会优先进行测试,如果匹配到,则不会继续测试。故此filter 的prio跟class的prio并不一样,class的prio表明了此class的优先级:当有空闲带宽时prio 数值低的(优先级高)class会优先占用空闲。 143 | 若不同优先级的filter都可以匹配到包,则优先级高的filter起作用。相同优先级的filter(必须为同种类型classifier)严格按照命令的输入顺序进行匹配,先执行的filter起作用。 144 | 145 | 146 | #### ** 操作原理** 147 | 148 | ** **类(class)组成一个树,每个类都只有一个父类,而一个类可以有多个子类。某些qdisc (例如:CBQ和 HTB)允许在运行时动态添加类,而其它的qdisc(例如:PRIO)不允许动态建立类。允许动态添加类的qdisc可以有零个或者多个子类,由它们为数据包排队。此外,每个类都有一个叶子qdisc,默认情况下,这个也在qdisc有可分类,不过每个子类只能有一个叶子qdisc。 当一个数据包进入一个分类qdisc,它会被归入某个子类。我们可以使用一下三种方式为数据包归类,不过不是所有的qdisc都能够使用这三种方式。 149 | 150 | ​ 如果过滤器附属于一个类,相关的指令就会对它们进行查询。过滤器能够匹配数据包头所有的域,也可以匹配由ipchains或者iptables做的标记。 151 | 152 | ​ 153 | 154 | ​ 树的每个节点都可以有自己的过滤器,但是高层的过滤器也可以一直接用于其子类。如果数据包没有被成功归类,就会被排到这个类的叶子qdisc的队中。相关细节在各个qdisc的手册页中。 155 | 156 | #### **命名规则** 157 | 158 | ​ 所有的qdisc、类、和过滤器都有ID。ID可以手工设置,也可以由内核自动分配。ID由一个主序列号和一个从序列号组成,两个数字用一个冒号分开。 159 | 160 | ​ qdisc,一个qdisc会被分配一个主序列号,叫做句柄(handle),然后把从序列号作为类的命名空间。句柄才有像1:0 一样的表达方式。习惯上,需要为有子类的qdisc显式的分配一个句柄。 161 | 162 | ​ 类(Class),在同一个qdisc里面的类共享这个qdisc的主序列号,但是每个类都有自己的从序列号,叫做类识别符(classid)。类识别符只与父qdisc有关,与父类无关。类的命名习惯和qdisc相同。 163 | 164 | ​ 过滤器(Filter),过滤器的ID有三部分,只有在对过滤器进行散列组织才会用到。详情请参考tc-filtes手册页。 165 | 166 | #### ** 单位** 167 | 168 | ** **tc命令所有的参数都可以使用浮点数,可能会涉及到以下计数单位。 169 | 170 | ​ 171 | 172 | ##### 带宽或者流速单位: 173 | 174 | ![](.\Images\20150511134819738.png) 175 | 176 | ##### 数据的数量单位 177 | 178 | ![](.\Images\20150511135207564.png) 179 | 180 | ##### 时间的计量单位: 181 | 182 | ![](.\Images\20150511135126939.png) 183 | 184 | 185 | 186 | # TC命令 187 | 188 | ​ tc可以使用以下命令对qdisc、类和过滤器进行操作: 189 | 190 | ​ add, 在一个节点里加入一个qdisc、类、或者过滤器。添加时,需要传递一个祖先作为参数,传递参数时既可以使用ID也跨越式直接传递设备的根。如果要建立一个qdisc或者过滤器,可以使用句柄(handle)来命名。如果要建立一个类,可以使用类识别符(classid)来命名。 191 | 192 | ​ remove, 删除由某个句柄(handle)指定的qdisc,根qdisc(root)也可以删除。被删除qdisc上所有的子类以及附属于各个类的过滤器都会被自动删除。 193 | 194 | 195 | 196 | ​ change, 以替代的方式修改某些条目。除了句柄(handle)和祖先不能修改以外,change命令的语法和add命令相同。换句话说,change命令不能指定节点的位置。 197 | 198 | ​ replace, 对一个现有节点进行近于原子操作的删除/添加。如果节点不存在,这个命令就会建立节点。 199 | 200 | ​ link, 只适用于qdisc,替代一个现有的节点。 201 | 202 | ​ tc命令的格式: 203 | 204 | ```shell 205 | tc qdisc [ add | change | replace | link ] dev DEV [ parent qdisc-id | root ] [ handle qdisc-id ] qdisc [ qdisc specific parameters ] 206 | 207 | tc class [ add | change | replace ] dev DEV parent qdisc-id [ classid class-id ] qdisc [ qdisc specific parameters ] 208 | 209 | tc filter [ add | change | replace ] dev DEV [ parent qdisc-id | root ] protocol protocol prio priority filtertype [ filtertype specific param‐eters ] flowid flow-id 210 | 211 | tc [ FORMAT ] qdisc show [ dev DEV ] 212 | 213 | tc [ FORMAT ] class show dev DEV 214 | 215 | tc filter show dev DEV 216 | 217 | ###### FORMAT := { -s[tatistics] | -d[etails] | -r[aw] | -p[retty] | i[ec] } 218 | ``` 219 | 220 | # 具体操作 221 | 222 | ​ Linux流量控制主要分为建立队列、建立分类和建立过滤器三个方面。 223 | 224 | ## ** 基本实现步骤**。 225 | 226 | ​ 1) 针对网络物理设备(如以太网卡eth0)绑定一个队列qdisc; 227 | 228 | ​ 2) 在该队列上建立分类class; 229 | 230 | ​ 3) 为每一分类建立一个基于路由的过滤器filter; 231 | 232 | ​ 4) 最后与过滤器相配合,建立特定的路由表。 233 | 234 | ​ 235 | 236 | ## ** 环境模拟实例**。 237 | 238 | ​ 流量控制器上的以太网卡(eth0)的IP地址为 192.168.1.66, 在其上建立一个CBQ队列。假设包的平均大小为1000字节,包间隔发送单元的大小 为8字节,可接收冲突的发送最长包的数目为20字节。 239 | 240 | 241 | 242 | ​ 加入有三种类型的流量需要控制: 243 | 244 | ​ 1)是发往主机1的,其IP地址为192.168.1.24。其流量带宽控制在8Mbit,优先级为 2; 245 | 246 | ​ 2)是发往主机2的,其IP地址为192.168.1.30。其流量带宽控制在1Mbit,优先级为1; 247 | 248 | ​ 3)是发往子网1的,其子网号为192.168.1.0。子网掩码为255.255.255.0。流量带宽控制在1Mbit,优先级为6。 249 | 250 | 251 | 252 | ## 建立队列 253 | 254 | ​ 一般情况下,针对一个网卡只需建立一个队列。 255 | 256 | ```shell 257 | ###将一个cbq队列绑定到网络物理设备eth0上,其编号为1:0;网络物理设备eth0的实际带宽为10Mbit,包的平均大小为1000字节;包间隔发送单元的大小为8字节,最小传输包大小为64字节。 258 | 259 | tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64 260 | ``` 261 | 262 | 263 | 264 | ## 建立分类 265 | 266 | ​ 一般情况下,针对一个队列需建立一个根分类,然后再在其上建立子分类。对于分类,按其分类的编号顺序起作用,编号小的优先;一但符合某个分类匹配规则,通过该分类发送数据包,则其后的分类不在起作用。 267 | 268 | ```shell 269 | ##创建根分类1:1;分配带宽为10Mbit,优先级别为8. 270 | 271 | tc class add dev eth0 parent 1:0 classid 1:1 cbq bandidth 10Mbit rate 10Mbit maxburst 20 allot 1514 prio 8 avpkt 1000 cell 8 weight 1Mbit 272 | 273 | ###该队列的最大可用带宽为10Mbit,实际分配的带宽为10Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为1514字节,优先级别为8,包的平均大小为1000字节,包间隔发送单元的大小为8字节,相当于实际带宽的加权速率为1Mbit。 274 | 275 | ## 创建分类1:2,其父类为1:1,分配带宽为 8Mbit, 优先级别为2. 276 | 277 | tc class add dev eth0 parent 1:1 cbq bandwidth 10Mbit rate 8Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 800Kbit split 1:0 bounded 278 | 279 | ## 该队列的最大可用带宽为10Mbit,实际分配的带宽为8Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为1514字节,优先级别为2,包的平均大小为1000字节,包间隔发送单元的大小为8字节,相当于实际带宽的加权速率为800Kbit,分类的分离点为1:0,且不可借用未使用带宽。 280 | 281 | ## 创建分类 1:4,其父分类为1:1,分配带宽为1Mbit,优先级别为6。 282 | 283 | tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 10 Mbit rate 1Mbit maxburst 20 allot 1514 prio 6 avpkt 1000 cell 8 weight 100Kbit split 1:0 284 | 285 | ## 该队列的最大可用带宽为10Mbit,实际分配的带宽为1Mbit,可接收冲突的发送最长包数目为20字节;最大传输单元加MAC头的大小为1514字节,优先级别为6,包的平均大小为1000字节,包间隔发送单元的大小为8字节,相当于实际带宽的加权速率为100Kbit,分类的分离点为1:0 286 | 287 | ``` 288 | 289 | 290 | 291 | ## 创建过滤器 292 | 293 | ​ 过滤器主要服务于分类。 294 | 295 | ​ 一般只需针对根分类提供一个过滤器,然后为每个子分类提供路由映射。 296 | 297 | ```shell 298 | ## 1)应用路由分类器到cbq队列的根,父分类编号为1:0;过滤协议为ip,优先级别为100,过滤器为基于路由表。 299 | tc filter add dev eth0 parent 1:0 protocol ip prio 100 route 300 | 301 | ## 2)建立路由映射分类 1:2 , 1:3 , 1:4 302 | tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 2 flowid 1:2 303 | tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 3 flowid 1:3 304 | tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 4 flowid 1:4 305 | ``` 306 | 307 | ## 建立路由 308 | 309 | ​ 该路由是与前面所建立的路由映射一一对应。 310 | 311 | ```shell 312 | # 1)发往主机192.168.1.24的数据包通过分类2转发(分类2的速率8Mbit) 313 | ip route add 192.168.1.24 dev eth0 via 192.168.1.66 realm 2 314 | # 2)发往主机192.168.1.30的数据包通过分类3转发(分类3的速率1Mbit) 315 | ip route add 192.168.1.30 dev eth0 via 192.168.1.66 realm 3 316 | # 3)发往子网192.168.1.0/24 的数据包通过分类4转发(分类4的速率1Mbit) 317 | ip route add 192.168.1.0/24 dev eth0 via 192.168.1.66 realm 4 318 | 319 | 注:一般对于流泪控制器所直接连接的网段建议使用IP主机地址流量控制限制,不要使用子网流量控制限制。如一定需要对直连子网使用子网流量控制限制,则在建立该子网的路由映射前,需将原先由系统建立的路由删除,才可以完成相应步骤 320 | ``` 321 | 322 | 323 | 324 | ## 监视 325 | 326 | ​ 主要包括对现有队列、分类、过滤器和路由状况进行监视。 327 | 328 | ​ 1)显示队列的状况 329 | 330 | ```shell 331 | ## 简单显示指定设备(这里为eth0)的队列状况 332 | tc qdisc ls dev eth0 333 | qdisc cbq 1: rate 10Mbit (bounded,isolated) prio no-transmit 334 | 335 | ## 详细显示指定设备(这里为eth0)的队列状况 336 | tc -s qdisc ls dev eth0 337 | qdisc cbq 1: rate 10Mbit (bounded,isolated) prio no-transmit 338 | Sent 7646731 bytes 13232 pkts (dropped 0, overlimits 0) 339 | borrowed 0 overactions 0 avgidle 31 undertime 0 340 | 341 | ## 这里主要显示了通过该队列发送了13232个数据包,数据流量为7646731个字节,丢弃的包数目为0,超过速率限制的包数目为0。 342 | ``` 343 | 344 | 345 | 346 | 2)显示分类的状况 347 | 348 | ```shell 349 | ## 简单显示指定设备(这里为eth0)的分类状况 350 | tc class ls dev eth0 351 | class cbq 1: root rate 10Mbit (bounded,isolated) prio no-transmit 352 | class cbq 1:1 parent 1: rate 10Mbit prio no-transmit 353 | class cbq 1:2 parent 1:1 rate 8Mbit prio (bounded) prio 2 354 | class cbq 1:3 parent 1:1 rate 1Mbit prio 1 355 | class cbq 1:4 parent 1:1 rate 1Mbit prio 6 356 | 357 | ## 详细显示指定设备(这里为eth0)的分类状况 358 | tc -s class ls dev eth0 359 | class cbq 1: root rate 10000Kbit (bounded,isolated) prio no-transmit 360 | Sent 17725304 bytes 32088 pkt (dropped 0, overlimits 0 requeues 0) 361 | backlog 0b 0p requeues 0 362 | borrowed 0 overactions 0 avgidle 31 undertime 0 363 | class cbq 1:1 parent 1: rate 10000Kbit prio no-transmit 364 | Sent 16627774 bytes 28884 pkts (dropped 0, overlimits 0 requeues 0) 365 | backlog 0b 0p requeues 0 366 | borrowed 16163 overactions 0 avgidle 587 undertime 0 367 | class cbq 1:2 parent 1:1 rate 8000Kbit (bounded) prio 2 368 | Sent 628829 bytes 3130 pkts (dropped 0, overlimits 0 requeues 0) 369 | backlog 0b 0p requeues 0 370 | borrowed 0 overactions 0 avgidle 4137 undertime 0 371 | class cbq 1:3 parent 1:1 rate 1000Kbit prio 1 372 | Sent 0 bytes 0 pkts (dropped 0, overlimits 0 requeues 0) 373 | backlog 0b 0p requeues 0 374 | borrowed 0 overactions 0 avgidle 3.19309e+06 undertime 0 375 | class cbq 1:4 parent 1:1 rate 1000Kbit prio 6 376 | Sent 552879 bytes 8076 pkts (dropped 0, overlimits 0 requeues 0) 377 | backlog 0b 0p requeues 0 378 | borrowed 3797 overactions 0 avgidle 159557 undertime 0 379 | 380 | ##这里主要显示了通过不同分类发送的数据包,数据流量,丢弃的包数目,超过速率限制的包数目等等。其中根分类(class cbq 1:0)的状况与队列的状况类似 381 | 382 | ##例如,分类class cbq 1:4 发送了8076个包,数据流量为 552879 个字节,丢弃的包数目为0,超过速率限制的包数目为0。 383 | ``` 384 | 385 | 386 | 387 | 3)显示过滤器的状况 388 | 389 | ```shell 390 | tc -s filter ls dev eth0 391 | filter parent 1: protocol ip pref 100 route 392 | filter parent 1: protocol ip pref 100 route fh 0xffff0002 flowid 1:2 to 2 393 | filter parent 1: protocol ip pref 100 route fh 0xffff0003 flowid 1:3 to 3 394 | filter parent 1: protocol ip pref 100 route fh 0xffff0004 flowid 1:4 to 4 395 | 396 | ## 这里flowid 1:2 代表分类 class cbq 1:2, to 2 代表通过路由 2 发送 397 | ``` 398 | 399 | 4)显示现有路由的状况 400 | 401 | ```shell 402 | ip route 403 | default via 192.168.1.1 dev eth0 404 | 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.66 405 | 192.168.1.24 via 192.168.1.66 dev eth0 realm 2 406 | 192.168.1.30 via 192.168.1.66 dev eth0 realm 3 407 | 408 | #如上所示,结尾包含有realm 的显示行是起作用的路由过滤器。 409 | ``` 410 | 411 | ## 维护 412 | 413 | ​ 主要包括对队列、分类、过滤器和路由的增添、修改和删除。 414 | 415 | ​ 增添动作一般依照 队列 -> 分类 -> 过滤器 -> 路由 的顺序进行;修改动作则没有什么要求;删除则依照 路由 -> 过滤器 -> 分类 -> 队列 的顺序进行。 416 | 417 | ​ 1)队列的维护 418 | 419 | ​ 一般对于一台流量控制器来说,出厂时针对每个以太网卡均已配置好一个队列了,通常情况下对队列无需进行增添、修改和删除动作。 420 | 421 | ​ 2)分类的维护 422 | 423 | ​ 增添,增添动作通过 tc class add 命令实现,如前面所示。 424 | 425 | ​ 修改,修改动作通过 tc class change 命令实现,如下所示: 426 | 427 | ```shell 428 | tc class change dev eth0 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 7Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weigth 700Kbit split 1:0 bounded 429 | ``` 430 | 431 | 对于bounded 命令应慎用,一旦添加后就进行修改,只可通过删除后再添加来实现。 432 | 433 | ​ 删除,删除动作只在该分类没有工作前才可以进行,一旦通过该分类发送过数据,则无法删除它。因此,需要通过shell文件方式来修改,通过重新启动来完成删除动作。 434 | 435 | ​ 3)过滤器的维护 436 | 437 | ​ 增添,增添动作通过 tc filter add 命令实现,如前面所示。 438 | 439 | ​ 修改,修改动作通过 tc filter change 命令实现,如下所示: 440 | 441 | ```shell 442 | tc filter change dev eth0 parent 1:0 protocol ip prio 100 route to 10 flowid 1:8 443 | ``` 444 | 445 | ​ 删除,删除动作通过 tc filter del 命令实现,如下所示: 446 | 447 | ```shell 448 | tc filter del dev eth0 parent 1:0 protocol ip prio 100 route to 10 449 | ``` 450 | 451 | 4)与过滤器————映射路由的维护 452 | 453 | ​ 增添,增添动作通过 ip route add 命令实现,如前面所示。 454 | 455 | ​ 修改,修改动作通过 ip route change 命令实现,如下所示: 456 | 457 | ```shell 458 | ip route change 192.168.1.30 dev eth0 via 192.168.1.66 realm 8 459 | ``` 460 | 461 | 462 | 463 | ​ 删除,删除动作通过 ip route del 命令实现,如下所示: 464 | 465 | ```shell 466 | ip route del 192.168.1.30 dev eth0 via 192.168.1.66 realm 8 467 | 468 | ip route del 192.168.1.0/24 dev eth0 via 192.168.1.66 realm 4 469 | ``` 470 | 471 | 472 | 473 | 474 | 475 | -------------------------------------------------------------------------------- /linux网络相关/Linux网络IO并行化技术概览.md: -------------------------------------------------------------------------------- 1 | # Linux网络IO并行化技术概览 2 | 3 | 4 | 5 | 首先讲讲cpu掩码是如何设置,二进制里一个cpu占一位,假如有2个cpu processor,则掩码有两位, 6 | 7 | 想配置cpu0就是 01 , cpu1就是 10,如果两个cpu都想对应上则掩码为 11, 8 | 9 | 如此类推,8个cpu processor则掩码有8位来进行配置。 10 | 11 | 12 | 13 | 本文主要来聊一聊关于Linux网络IO(协议栈)的相关技术。记得大约十年前,单box最大性能可以处理上万qps,十万并发连接,而如今单box可处理数十万qps,数百万并发连接。这里除了硬件性能的提升以外,内核的优化技术也起到很大作用。另外,这些优化特性并非总是默认有效或最佳的,时有需要tuning的场景,我在近几年工作中也多次碰到服务器性能问题而通过简单tuning可以有效改善,所以理解它们挺有必要。或许你已经听说过下面这些内核支持特性:中断亲和,多队列网卡、RPS、RFS、XFS、SO_REUSEPORT.. 对它们的介绍资料在网上也有不少,但我发现很少有从全局角度地系统性介绍,所以有了我总结此篇的动机。 14 | 15 | 首先对上面这些特性我认为可以归纳一个主线:并行化。因为网络协议栈处理,本质来说是CPU密集的计算,所以多年来各种关键优化补丁的共同思路,基本都是怎么充分利用多核的资源达到计算并行。为啥这么简单一个思路会搞出那么多概念呢,因为协议栈计算本身是一个复杂的分层的处理过程,在各个层各处理环节都有并行优化的空间,上述这些优化补丁正是在这些不同的层次的工作。下面我将按此脉络展开对这些技术点做一个介绍。 16 | 17 | #### Linux协议栈 18 | 19 | 首先我们先回顾一下Linux协议栈的分层结构,如下图: 20 | 21 | [![img](../Images/linux_net_overview.png) 22 | 23 | 最底层是硬件网卡(NIC),它通常通过两个内存环型队列(rx_ring/tx_ring)加上中断机制与操作系统进行通讯。当NIC收到数据包后,它将数据包写入rx_ring并产生中断。CPU收到中断后OS将陷入中断处理程序中执行,这在Linux内核中叫Hard-IRQ。 24 | 25 | [![img](https://3.bp.blogspot.com/-RxjAni2Jwl4/WYp-dyyWGKI/AAAAAAAAO_8/lCh5U901cX4DU8fltiXKr_iZmpMgmUINgCLcBGAs/s1600/QQ20170808-221548.png)](../Images//QQ20170808-221548.png) 26 | 27 | 在较老版内核中,网卡Hard-IRQ程序将数据包从rx_ring中取出并放入PerCPU的一个叫backlog的队列,然后发起一个Soft-IRQ来处理backlog队列 (内核将中断处理中无需实时同步完成的工作delay到一个准实时的异步时机去执行,这个异步机制即Soft-IRQ)。 28 | 29 | 较新版的内核中,通常并不使用backlog队列,而是使用叫NAPI的改进机制,区别是Hard-IRQ不再直接读取每个数据包,而是直接启动Soft-IRQ,在Soft-IRQ中通过batch poll的方式将数据包从rx_ring中取出并处理 (可大大减少中断数)。 30 | 31 | 不管是backlog还是NAPI,它们都在Soft-IRQ上下文中执行,并把数据包提交给IP层进行处理(为简单我们都以TCP/IP协议为例)。IP层处理完分片和路由后将提交给传输层(TCP或UDP)进行处理。协议相关逻辑我们不在这里详述,最终它将此数据包放入对应的socket对象的接收队列中,并唤醒阻塞在socket上的进程。 32 | 33 | 用户态进程通过socket fd来操作内核中的socket对象,经常它会阻塞在socket相关的read/write或epoll/select的操作上。还需注意到Linux的文件机制允许多个进程通过各自的fd同时竞争操作同一个socket对象。典型地场景如,多个进程竞争accept一个listening的socket,再如,多个进程竞争读一个udp socket。 34 | 35 | #### 中断调度 36 | 37 | 38 | 协议栈对入包的软件部分处理,总是从硬中断(Hard-IRQ)处理开始的。关于中断处理你需要了解几个事实: 39 | 40 | - 计算机系统中有很多不同作用的中断请求,由中断号唯一标识,比如每块网卡有自己的中断号 41 | - 对每个中断号,系统都会注册一个handler(也就我们通常说的中断处理程序) 42 | - 在Hard-IRQ handler中(如网卡中断处理程序)通常将无需立即完成的工作(如TCP/IP协议栈处理)通过Soft-IRQ异步地执行 43 | - Soft-IRQ顾名思义就是软件构造的类似的中断机制,它也根据用途区分不同的类型,并有对应的handler。它存在的主要意义是让中断对系统实时性的影响尽可能小 44 | 45 | 不管是Hard-IRQ还是Soft-IRQ handler,它们都是一段需要调度的执行流(就像线程一样),那么问题来了:如何高效地调度这些执行流在多核下运行,对系统性能非常关键。下面介绍一下目前的一些调度机制: 46 | 47 | - 对同一个中断号的Hard-IRQ handler,在全局上是串行执行的,即同时只能在一个核上执行 48 | - 对不同的中断号的Hard-IRQ handler,可以在不同的核上并行执行 49 | - 某个中断号在哪个核上执行,通常由系统中的I/O APIC(高级可编程中断控制器)来决定,内核提供了配置接口(也有一种称为irqbalance的动态调整工具可选) 50 | - Hard-IRQ handler中发起的Soft-IRQ,一般在同一个core上执行 51 | 52 | 我们可以使用下面的命令观察系统中所有中断号以及它们在各core上的调度情况: 53 | 54 | ```shell 55 | cat /proc/interrupts 56 | 57 | ``` 58 | 59 | 下面回到网络IO的主题上,从网卡中断的角度看协议栈处理,如下图: 60 | 61 | [![img](https://2.bp.blogspot.com/-omkjV0UX-7o/WYqAYIq8U7I/AAAAAAAAPAM/QUJFWJudSckGUrc_HJzIacm3BqzdfBInwCLcBGAs/s400/linux_net_irqsched.png)](https://2.bp.blogspot.com/-omkjV0UX-7o/WYqAYIq8U7I/AAAAAAAAPAM/QUJFWJudSckGUrc_HJzIacm3BqzdfBInwCLcBGAs/s1600/linux_net_irqsched.png) 62 | 63 | 传统的网卡每块设备有一个中断号,它由上述的调度机制为每个中断请求分配给一个唯一的core来执行。在此场景下,你会发现协议栈处理的并行化是以网卡设备为粒度的。 64 | 65 | 如果只有单块网卡,就会发现中断处理CPU消耗集中于单个核上(记得默认对应的Soft-IRQ会在同一个core上执行);更坏的情况是,如果只有一个处理socket的应用进程,很可能你会看到所有CPU负载集中于一个core上(实际上进程调度策略会优化将唤醒的进度调度在相同的core上执行)。怎么优化?别着急下面的内容中会有很多方法。 66 | 67 | #### 1,多块网卡 68 | 69 | 如果有多块网卡,通常来因为利用多核并行而会获得性能的提升,如下图所示: 70 | 71 | [![img](https://3.bp.blogspot.com/-aHmi8Ry__O4/WYqBdD3AqLI/AAAAAAAAPAY/J-Gah3zgUWstnR4ggaACRkCt9HkTro6bgCLcBGAs/s400/linux_net_irqsched2.png)](https://3.bp.blogspot.com/-aHmi8Ry__O4/WYqBdD3AqLI/AAAAAAAAPAY/J-Gah3zgUWstnR4ggaACRkCt9HkTro6bgCLcBGAs/s1600/linux_net_irqsched2.png) 72 | 73 | 但我们也曾发现过例外,虽然两块网卡的中断请求被调度到2个core上,但它们是[超线程](https://en.wikipedia.org/wiki/Hyper-threading)技术对同一物理core虚拟出的2个逻辑core,并不能有效地并行处理。解决方法是手工配置中断亲和(绑定中断号与具体的core),如下命令: 74 | 75 | ```shell 76 | echo 02 > /proc/irq/123/smp_affinity 77 | #写入cpu mask 到 中断号是123的smp_affinity文件,不同网卡的中断号可以通过cat /proc/interrupts来看 78 | ``` 79 | 80 | #### 81 | 82 | #### 2,多队列网卡 83 | 84 | 如前所述,传统的单网卡默认无法充分利用多核,即使是多网卡,在数量小于核数的情况下也是一样。于是产生了在今天广泛使用的多队列网卡(Multi-Queue NIC),在一些资料里这种技术也叫RSS(Receive-Side Scaling)。这种技术概括来说是从硬中断的层面支持了单网卡IO的并行,其工作原理如下图所示: 85 | 86 | [![img](https://1.bp.blogspot.com/-s_sI-m---2I/WYqBuB_zm-I/AAAAAAAAPAc/zjDaF5bFJaYOf1AZes85cSTDWPS7YtFqACLcBGAs/s400/linux_net_multiqueue.png)](https://1.bp.blogspot.com/-s_sI-m---2I/WYqBuB_zm-I/AAAAAAAAPAc/zjDaF5bFJaYOf1AZes85cSTDWPS7YtFqACLcBGAs/s1600/linux_net_multiqueue.png) 87 | 88 | 多队列网卡通过引入RX-Queue的机制,将输入流量水平分到多个“虚拟的网卡”也是RX-Queue,每个RX-Queue像一个独立设备一样有自己的中断号并可以独立并行地工作。RX-Queue的数量一般可以配置为与核数一致,这样可以充分利用多核资源。 89 | 90 | 值得注意的是,输入流量拆分到RX-Queue的算法是根据Hash(SrcIP, SrcPort, DstIP, DstPort)来计算的(可以试想下为什么是用hash而不是类似随机分发?对,主要是为了避免乱序)。如果出现大部分流量来自少量IP:Port的场景,多队列网卡并就爱莫能助了。 91 | 92 | 你可以使用前面提到的interrupts文件来观察多队列网卡的分发效果: 93 | 94 | ```shell 95 | cat /proc/interrupts 96 | 97 | ``` 98 | 99 | 这里也[有篇文章](http://blog.csdn.net/turkeyzhou/article/details/7528182)较详细介绍了这个主题可作参考。 100 | 101 | #### 3,RPS 102 | 103 | 在没有多队列网卡的服务器上,比如一个典型的场景是虚拟机或云主机,如何优化网络IO呢?下面要介绍的是纯软件的优化方案:RPS & RFS, 这是在2.6.35内核加入的由Google工程师Tom Herbert开发的优化补丁。它的工作原理如下图所示: 104 | 105 | [![img](https://2.bp.blogspot.com/-8FbyO5FZU8s/WYqCIqBxHbI/AAAAAAAAPAg/Npsye0dWXRwuOFUcIP8YCouUOJ3KrZp-QCLcBGAs/s400/linux_net_rps.png)](https://2.bp.blogspot.com/-8FbyO5FZU8s/WYqCIqBxHbI/AAAAAAAAPAg/Npsye0dWXRwuOFUcIP8YCouUOJ3KrZp-QCLcBGAs/s1600/linux_net_rps.png) 106 | 107 | RPS是工作在NAPI层(或者说在Soft-IRQ处理中接近入口的位置)的入流量分发机制,它利用了前面提到的Per-CPU的backlog队列来将数据包分发到目标core上。默认的分发算法与多队列机制类似,也是使用IP,Port四元组的哈希来映射到某一个core。与多队列机制类似,若是流量来自少量IP:Port的场景,负载将无法很好地均衡在多核上。我们目前在AWS虚拟机上普遍配置启用了RPS,优化效果还是非常明显。 108 | 109 | 配置RPS的方法也很简单: 110 | 111 | ```shell 112 | echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus 113 | 114 | ``` 115 | 116 | 更详细的配置说明可[参考这里](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/network-rps.html) 117 | 118 | 如何观察RPS的分发效果呢?由于RPS分发会多做一次Soft-IRQ调度,我们可以通过观察Soft-IRQ的统计接口来观察调度效果: 119 | 120 | ```shell 121 | cat /proc/softirqs | grep NET_RX 122 | 123 | ``` 124 | 125 | 前面我们说到在没有多队列网卡的服务器,RPS可以发挥重要作用,那如果已经有多队列网卡了是否还需要RPS呢?根据我目前的经验来说,一般情况下有队列网卡的环境下配置RPS不会再有明显的提升。但我认为仍存在一些情况结合RPS是有意义的,比如队列数明显少于核数,再比如某些RFS(下面会介绍)可以优化的场景可以打开RPS+RFS。 126 | 127 | 如果你有兴趣看一下RPS的关键内核代码,可以[查看这里](http://lxr.free-electrons.com/source/net/core/dev.c#L4202)。 128 | 129 | 这里也[有篇文章](http://simohayha.iteye.com/blog/720850)介绍了一些内核实现细节可作参考。 130 | 131 | #### 4, RFS 132 | 133 | RFS是在RPS分发机制基础上的一个扩展。它尝试解决这么一个问题,即然我在软件层面做数据包的分发,能不能比硬件多队列方案的近似随机的Hash分发方式更智能更高效一些呢?比如按Hash分发的一个问题就是,准备接收这个数据包的进程所在core很可能跟按Hash选择的core不是同一个,这样会导致cache miss及cache line bouncing,在多核高并发场景这对性能影响会十分可观。 134 | 135 | RFS尝试优化这个问题,它尽力将收到数据包分发给接收它的进程所在的core上,先看一下原理图: 136 | 137 | [![img](https://3.bp.blogspot.com/-DfHw6IivkZM/WYqCvllORxI/AAAAAAAAPAo/YFd3sM7qSTYtB_psMLqhn1cZx00LyC9lgCLcBGAs/s400/linux_net_rfs.png)](https://3.bp.blogspot.com/-DfHw6IivkZM/WYqCvllORxI/AAAAAAAAPAo/YFd3sM7qSTYtB_psMLqhn1cZx00LyC9lgCLcBGAs/s1600/linux_net_rfs.png) 138 | 139 | 首先RFS会维护一张全局的路由表(图中SockFlowTable),表中记录了一个FlowHash(四元组的Hash值)到对应CPU核的路由项。表项怎么建立呢?是在进程调用某socket的recvmsg系统调用(也包括recv/recvfrom)时,将该socket的FlowHash值(由最后一次收到包的FlowHash决定)与当前的CPU核关联起来。在RPS做包转发时,实际它会先判断是否启用了RFS,并且能找到有效的RFS路由项,否则的话仍使用默认RPS逻缉进行转发。 140 | 141 | 另外RFS还维护一张Per-Queue的局部路由表(图中Per-Queue FlowTable),它有什么用呢?主要作用是为了在全局路由表发生变化时,避免原路由路径上的包还没有被完全处理完而导致的乱序。它的原理并不复杂,在局部路由表中会记录某FlowHash(实际实现是FlowHash的hash)最后一次包转发时的关联CPU核,同时会记录当时该核对应的backlog队列的队尾标号(qtail)。当下次转发该flow上下一个包时,如果全局路由给出的CPU核发生了变化,则判断当前backlog队列的队首标号是否大于qtail,如果是说明上一次转发的包已经被处理完了,可以安全地切换到全局路由给出的新的CPU核,否则的话为了保证有序仍选择上一次使用的CPU核。 142 | 143 | 从原理上可以看出,在每个socket有唯一的进程/线程处理时RFS会有较好地效果,同时,对于在同一进程/线程内多路复用操作多个socket的场景,建议结合绑定进程/线程到固定CPU核的方式可以进一步发挥RFS的作用(让转发路由规则固定,进一步减少cache line bouncing)。 144 | 145 | RFS的配置也比较简单,有两处,一个是全局路由表的大小,另一个是局部路由表的大小(一般设为前者大小/RX-Queue数),如下例: 146 | 147 | ```shell 148 | echo 32768 > /proc/sys/net/core/rps_sock_flow_entries 149 | echo 2048 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt 150 | 151 | ``` 152 | 153 | 更详细的配置说明可[参考这里](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/network-rfs.html)。 154 | 155 | 如果有兴趣看一下它的实现,可从[这里(记录路由)](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c#L762)和[这里(查询路由)](http://lxr.free-electrons.com/source/net/core/dev.c#L3481)入手。 156 | 157 | 这里也[有篇文章](http://www.pagefault.info/?p=115)介绍了实现可供参考。 158 | 159 | #### 5, XPS 160 | 161 | 作者也是Google的Tom Herbert,内核2.6.38被引入。XPS解决的是一个在多队列网卡场景下才存在的问题:默认情况下当协议栈处理到需要向一个网卡设备发包时,如果是多队列网卡(有多个TX-Queue),会使用四元组hash的方式选择一个TX-Queue进行发送。这里有一个性能损耗是,在多核的场景下,可能会存多个核同时向一个TX-Queue发送数据的情况,因为这个操作需要写相应的tx_ring等内存,会引发cache line bouncing的问题,带来系统整体性能的下降。而XPS提供这样一种机制,可以将不同的TX-Queue固定地分配给不同的CPU集合去操作,这样对于某一个TX-Queue,仅有一个或少数几个CPU核会去写,可以避免或大大减少冲突写带来的cache line bouncing问题。 162 | 163 | 设置XPS非常简单,与RPS类似,如下示例: 164 | 165 | ```shell 166 | echo 11 > /sys/class/net/eth0/queues/tx-0/xps_cpus 167 | echo 22 > /sys/class/net/eth0/queues/tx-1/xps_cpus 168 | echo 44 > /sys/class/net/eth0/queues/tx-2/xps_cpus 169 | echo 88 > /sys/class/net/eth0/queues/tx-3/xps_cpus 170 | 171 | ``` 172 | 173 | 可以注意的是,根据原理,对于非多队列网卡设置XPS是没有意义和效果的。如果一个CPU核没有出现在任何一个TX-Queue的xps_cpus设置里,当该CPU核对该设备发包时,会退回使用默认hash的方式去选择TX-Queue。 174 | 175 | 如果你对它的实现有兴趣,可以从[这里看起](http://lxr.free-electrons.com/source/net/core/dev.c#L3203)。 176 | 177 | 这里是一篇[原作者对此的简介](https://lwn.net/Articles/412062/)。 178 | 179 | #### 6, SO_REUSEPORT 180 | 181 | 前面我们讨论的都是协议栈偏底层的并行优化,然而在上层也就是socket层,同样有一个重要的优化补丁:SO_REUSEPORT socket选项(注意不要与SO_REUSEADDR搞混)。它的作者还是Tom Herbert~(本文应感谢该伙计:),在内核3.9被引入。 182 | 183 | 它解决了什么样的问题呢?考虑一个监听唯一端口的TCP服务器,如果想利用多核并发以提升总体吞吐,需要考虑使用多进程/多线程。一个简单直接的方法是多个进程竞争accept监听socket,你可能有经验这种方法的一个缺陷是,各个进程/线程无法保证负载均衡地accept到新socket。直接解决这个问题可能需要写比较麻烦的workaround(比如我们曾使用连接数表+sched_yield的方法来保证负载均衡)。还有一个流行的处理模式是,使用一个线程负责listen和accept,然后将socket负载均衡地dispatch到一个worker线程组,每个worker线程处理一个socket子集的IO。这种模式对于长连接服务还是比较适合的,但如果是有大量connect请求的短连接场景,单个线程accept将可能成为瓶颈。解决这个问题可能又需要考虑使用复杂的多线程竞争accept的方式,但依然有socket访问竞争、cache line bouncing等效率问题。 184 | 185 | 对于UDP服务器,也有类似的问题,单进程读容易达到单核瓶颈,多进程竞争读又会有一定的性能损耗。多进程竞争读的原理如下图所示: 186 | 187 | [![img](https://3.bp.blogspot.com/-66Pc3vGQ_9A/WYqDAEPQhII/AAAAAAAAPAs/o6606rE5ZTYruzD1y-sNBvvNDJEdahhvACLcBGAs/s400/linux_net_reuseport.png)](https://3.bp.blogspot.com/-66Pc3vGQ_9A/WYqDAEPQhII/AAAAAAAAPAs/o6606rE5ZTYruzD1y-sNBvvNDJEdahhvACLcBGAs/s1600/linux_net_reuseport.png) 188 | 189 | SO_REUSEPORT很好地解决了多进程读写同一端口场景的2个问题:负载均衡和访问竞争。通过这个选项,多个用户进程/线程可以各自创建一个独立socket,但它们又共享同一端口,该端口的流量默认按四元组hash的方式分发各socket上(最新内核还支持使用bpf方式自定义分发策略),思路是不是非常熟悉。原理示意图如下: 190 | 191 | [![img](https://4.bp.blogspot.com/-itTsUpvGw6g/WYqDH25spdI/AAAAAAAAPAw/GZxQleTIYxI-Xj1LuI9N-WdyYT89RdGRQCLcBGAs/s400/linux_net_reuseport2.png)](https://4.bp.blogspot.com/-itTsUpvGw6g/WYqDH25spdI/AAAAAAAAPAw/GZxQleTIYxI-Xj1LuI9N-WdyYT89RdGRQCLcBGAs/s1600/linux_net_reuseport2.png) 192 | 193 | 使用此方式,TCP/UDP服务器编程模式都非常简单了,多进程/线程创建socket设置SO_REUSEPORT后bind,后面像单进程一样处理就可以了。同时性能也可获得明显提升,我们较早前一个经验是UDP改造后qps提升一倍。 194 | 195 | 如果你对它的实现有兴趣,可以从[这里(UDP)](http://lxr.free-electrons.com/source/net/ipv4/udp.c#L614)和[这里(TCP)](http://lxr.free-electrons.com/source/net/ipv4/inet_hashtables.c#L238)看看源码。 196 | 197 | 这里有一篇介绍得也比较详细的[文章](https://lwn.net/Articles/542629/)。 198 | 199 | #### 总结 200 | 201 | 本文介绍了Linux内核关于网络IO并行化的一系列技术(多队列、RPS、RFS、XPS、SO_REUSEPORT),它们是在协议栈不同的层面,但都使用了类似的方法提升了网络IO的并行性,并尽量减少了cache line bouncing。这些出色的工具可以帮助我们在任何Linux平台上构建高性能的网络服务器。 202 | 这里把一些常规性的CPU、网卡驱动、网络队列情况检查单独抽取出来, 203 | 204 | 直接查看CPU核数:grep -c processor /proc/cpuinfo 205 | 查看网卡软接收队列数:ls /sys/class/net/eth0/queues | grep -c rx 206 | 查看网卡软发生队列数:ls /sys/class/net/eth0/queues | grep -c tx 207 | 查看当前网卡硬件队列数:egrep -c eth0 /proc/interrupts 208 | 查看网卡名称和版本号:lspci -vvv | grep Ethernet | sed "s/Ethernet controller: //g" 209 | 查看网卡驱动名称:ethtool -i eth0 | grep driver 210 | 设置中断单位秒内吞吐量: ethtool -C eth0 rx-usecs 333 > /dev/null 2>&1 211 | 212 | 213 | 214 | 215 | 216 | PS: 217 | 218 | 一些可参考的优化配置: 219 | 220 | ```shell 221 | 222 | IRQ 223 | 224 | The first core on each of the two NUMA nodes is configured to receive interrupts from NIC. 225 | 226 | To match a CPU to a NUMA node, use lscpu: 227 | 228 | $ lscpu | grep NUMA 229 | NUMA node(s): 2 230 | NUMA node0 CPU(s): 0-8,18-26 231 | NUMA node1 CPU(s): 9-17,27-35 232 | 233 | This is done by writing 0 and 9 to /proc/irq//smp_affinity_list, where IRQ numbers are obtained with grep eth0 /proc/interrupts: 234 | 235 | $ echo 0 > /proc/irq/265/smp_affinity_list 236 | $ echo 9 > /proc/irq/266/smp_affinity_list 237 | 238 | RPS 239 | 240 | Several combinations for RPS have been tested. To improve latency, we offloaded the IRQ handling processors by using only CPUs 1–8 and 10–17. Unlike IRQ’s smp_affinity, the rps_cpus sysfs file entry doesn’t have a _list counterpart, so we use bitmasks to list the CPUs to which RPS can forward traffic1: 241 | 242 | $ echo "00000000,0003fdfe" > /sys/class/net/eth0/queues/rx-0/rps_cpus 243 | $ echo "00000000,0003fdfe" > /sys/class/net/eth0/queues/rx-1/rps_cpus 244 | 245 | Transmit Packet Steering (XPS) 246 | 247 | All NUMA 0 processors (including HyperThreading, i.e. CPUs 0-8, 18-26) were set to tx-0 and NUMA 1 (CPUs 9-17, 27-37) to tx-12: 248 | 249 | $ echo "00000000,07fc01ff" > /sys/class/net/eth0/queues/tx-0/xps_cpus 250 | $ echo "0000000f,f803fe00" > /sys/class/net/eth0/queues/tx-1/xps_cpus 251 | 252 | Receive Flow Steering (RFS) 253 | 254 | We’re planning to use 60k permanent connections, official documentation suggests to round up it to the nearest power of two: 255 | 256 | $ echo 65536 > /proc/sys/net/core/rps_sock_flow_entries 257 | $ echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt 258 | $ echo 32768 > /sys/class/net/eth0/queues/rx-1/rps_flow_cnt 259 | 260 | Nginx 261 | 262 | Nginx uses 18 workers, each worker has it’s own CPU (0-17). This is set by the worker_cpu_affinity option: 263 | 264 | workers 18; 265 | worker_cpu_affinity 1 10 100 1000 10000 ...; 266 | 267 | Tcpkali 268 | 269 | Tcpkali doesn’t have built-in CPU affinity support. In order to make use of RFS, we run tcpkali in a taskset and tune the scheduler to make thread migrations happen rarely: 270 | 271 | $ echo 10000000 > /proc/sys/kernel/sched_migration_cost_ns 272 | $ taskset -ac 0-17 tcpkali --threads 18 ... 273 | 274 | 275 | ``` 276 | 277 | -------------------------------------------------------------------------------- /linux网络相关/Tc 网卡多队列时每个队列配置公平队列sfq.md: -------------------------------------------------------------------------------- 1 | Tc 网卡多队列时每个队列配置公平队列sfq 2 | 3 | qdisc是队列的概念,在没有自己手动给dev增加队列的时候,默认有一个mq 0:的队列, 4 | 5 | class是类的概念,在没有使用tc命令给dev增加队列的时候,默认对所有网卡硬件队列对应了一条 6 | 7 | class,而此时默认的队列是fifo,可以参看《Linux的高级路由和流量控制HOWTO》进行理解。 8 | 9 | 如果我们想单独给各个硬件队列配置qos,可以在对应队列的class里增加sfq qdisc并配置 10 | 11 | ```shell 12 | [root@node2 ~]# tc -s q ls dev ens11f0 13 | 14 | qdisc mq 0: root 15 | 16 | Sent 0 bytes 0 pkt (dropped 17 | 18 | [root@node2 ~]# tc c ls dev ens11f0 19 | class mq :1 root 20 | class mq :2 root 21 | class mq :3 root 22 | class mq :4 root 23 | class mq :5 root 24 | class mq :6 root 25 | class mq :7 root 26 | class mq :8 root 27 | 28 | [root@node2 ~]# tc qd add dev ens11f0 parent :1 sfq 29 | 30 | #可以看到class上增加的对应的qdisc 31 | [root@node2 ~]# tc cl sh dev ens11f0 32 | class mq :1 root leaf 8001: 33 | class mq :2 root 34 | class mq :3 root 35 | class mq :4 root 36 | class mq :5 root 37 | class mq :6 root 38 | class mq :7 root 39 | class mq :8 root 40 | 41 | #可以对应的qdisc是sfq, 8001是主序号 42 | [root@node2 ~]# tc qd sh dev ens11f0 43 | qdisc mq 0: root 44 | qdisc sfq 8001: parent :1 limit 127p quantum 1514b depth 127 divisor 1024 45 | ``` 46 | 47 | ```shell 48 | #如果给dev的root配置新的队列,原默认队列会不见了 49 | [root@node2 ~]# tc qdisc add dev ens11f0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64 50 | [root@node2 ~]# 51 | [root@node2 ~]# tc cl sh dev ens11f0 52 | class cbq 1: root rate 10000Kbit (bounded,isolated) prio no-transmit 53 | [root@node2 ~]# 54 | [root@node2 ~]# tc qd sh dev ens11f0 55 | qdisc cbq 1: root refcnt 9 rate 10000Kbit (bounded,isolated) prio no-transmit 56 | [root@node2 ~]# tc qdisc del dev ens11f0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64 57 | [root@node2 ~]# tc cl sh dev ens11f0 58 | class mq :1 root 59 | class mq :2 root 60 | class mq :3 root 61 | class mq :4 root 62 | class mq :5 root 63 | class mq :6 root 64 | class mq :7 root 65 | class mq :8 root 66 | [root@node2 ~]# 67 | ``` 68 | 69 | 70 | 71 | 也可以对对应队列配置更为复杂的qdisc: 72 | 73 | ```shell 74 | [root@node2 ~]# tc qdisc add dev ens11f0 parent :1 cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64 75 | [root@node2 ~]# tc cl sh dev ens11f0 76 | class mq :1 root leaf 8003: 77 | class mq :2 root 78 | class mq :3 root 79 | class mq :4 root 80 | class mq :5 root 81 | class mq :6 root 82 | class mq :7 root 83 | class mq :8 root 84 | class cbq 8003: root rate 10000Kbit (bounded,isolated) prio no-transmit 85 | [root@node2 ~]# 86 | [root@node2 ~]# 87 | [root@node2 ~]# tc cl sh dev ens11f0 88 | class mq :1 root leaf 8003: 89 | class mq :2 root 90 | class mq :3 root 91 | class mq :4 root 92 | class mq :5 root 93 | class mq :6 root 94 | class mq :7 root 95 | class mq :8 root 96 | class cbq 8003: root rate 10000Kbit (bounded,isolated) prio no-transmit 97 | ``` 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /linux网络相关/二层报文处理与tcpdump增加hook方式.md: -------------------------------------------------------------------------------- 1 | ###### 首先分析正常收包流程 2 | 3 | 正常收包流程如下: 4 | 5 | 进入函数netif_receive_skb()后,skb正式开始协议栈之旅。 6 | 7 | ![img](http://blog.chinaunix.net/attachment/201108/3/24148050_1312338439sZa0.jpg) 8 | 9 | 10 | 11 | 跟OSI七层模型不同,linux根据包结构对网络进行分层。 12 | 比如,arp头和ip头都是紧跟在以太网头后面的,所以在linux协议栈中arp和ip地位相同(如上图) 13 | 但是在OSI七层模型中,arp属于链路层,ip属于网络层..... 14 | 我们就说arp,ip都属于第二层。下面是网络第二层的处理流程: 15 | 16 | **一、相关数据结构** 17 | 内核处理网络第二层,有下面2个重要list_head变量 (文件linux_2_6_24/net/core/dev.c) 18 | list_head 链表上挂了很多packet_type数据结构 19 | 20 | ``` 21 | 22 | 23 | static struct list_head ptype_base[16] __read_mostly; /* 16 way hashed list */ 24 | 25 | static struct list_head ptype_all __read_mostly; /* Taps */ 26 | 27 | struct packet_type { 28 | 29 | __be16 type; /* This is really htons(ether_type).*/ 30 | 31 | struct net_device dev; / NULL is wildcarded here */ 32 | 33 | int (*func) (struct sk_buff *, 34 | 35 | struct net_device *, 36 | 37 | struct packet_type *, 38 | 39 | struct net_device *); 40 | 41 | struct sk_buff (gso_segment)(struct sk_buff *skb, int features); 42 | 43 | int (*gso_send_check)(struct sk_buff *skb); 44 | 45 | void *af_packet_priv; 46 | 47 | struct list_head list; 48 | 49 | }; 50 | 51 | ``` 52 | 53 | 54 | 55 | **type** 成员保存了二层协议类型,ETH_P_IP、ETH_P_ARP等等 56 | **func **成员就是钩子函数了,如 ip_rcv()、arp_rcv()等等 57 | 58 | 59 | 60 | **二、操作packet_type的API** 61 | //把packet_type结构挂在与type对应的list_head上面 62 | 63 | ``` 64 | 65 | 66 | void **dev_add_pack**(struct packet_type *pt){ 67 | int hash; 68 | 69 | spin_lock_bh(&ptype_lock); 70 | 71 | if (pt->type == htons(ETH_PALL)) //type为ETH_PALL时,挂在ptype_all上面 72 | 73 | list_add_rcu(&pt->list, &ptype_all); 74 | 75 | else { 76 | 77 | hash = ntohs(pt->type) & 15; //否则,挂在ptype_base[type&15]上面 78 | 79 | list_add_rcu(&pt->list, &ptype_base[hash]); 80 | 81 | } 82 | 83 | spin_unlock_bh(&ptype_lock); 84 | 85 | } 86 | 87 | //把packet_type从list_head上删除 88 | 89 | void dev_remove_pack(struct packet_type *pt){ 90 | 91 | __dev_remove_pack(pt); 92 | 93 | synchronize_net(); 94 | 95 | } 96 | 97 | void __dev_remove_pack(struct packet_type *pt){ 98 | 99 | struct list_head *head; 100 | 101 | struct packet_type *pt1; 102 | 103 | spin_lock_bh(&ptype_lock); 104 | 105 | if (pt->type == htons(ETH_P_ALL)) 106 | 107 | head = &ptype_all; //找到链表头 108 | 109 | else 110 | 111 | head = &ptype_base[ntohs(pt->type) & 15]; // 112 | 113 | list_for_each_entry(pt1, head, list) { 114 | 115 | if (pt == pt1) { 116 | 117 | list_del_rcu(&pt->list); 118 | 119 | goto out; 120 | 121 | } 122 | 123 | } 124 | 125 | printk(KERN_WARNING "dev_remove_pack: %p not found.\n", pt); 126 | 127 | out: 128 | 129 | spin_unlock_bh(&ptype_lock); 130 | 131 | } 132 | 133 | ``` 134 | 135 | 136 | 137 | **三、进入二层协议处理函数** 138 | 139 | 140 | 141 | ``` 142 | int netif_receive_skb(struct sk_buff *skb) 143 | 144 | { 145 | 146 | //略去一些代码 147 | 148 | rcu_read_lock(); 149 | 150 | //第一步:先处理 ptype_all 上所有的 packet_type->func() 151 | 152 | //所有包都会调func,对性能影响严重!内核默认没挂任何钩子函数 153 | 154 | list_for_each_entry_rcu(ptype, &ptype_all, list) { //遍历ptye_all链表 155 | 156 | if (!ptype->dev || ptype->dev == skb->dev) { //上面的paket_type.type 为 ETH_P_ALL 157 | 158 | if (pt_prev) //对所有包调用paket_type.func() 159 | 160 | ret = deliver_skb(skb, pt_prev, orig_dev); //此函数最终调用paket_type.func() 161 | 162 | pt_prev = ptype; 163 | 164 | } 165 | 166 | } 167 | 168 | //第二步:若编译内核时选上BRIDGE,下面会执行网桥模块 169 | 170 | //调用函数指针 br_handle_frame_hook(skb), 在动态模块 linux26_24/net/bridge/br.c中 171 | 172 | //br_handle_frame_hook = br_handle_frame; 173 | 174 | //所以实际函数 br_handle_frame。 175 | 176 | //注意:在此网桥模块里初始化 skb->pkt_type 为 PACKET_HOST、PACKET_OTHERHOST 177 | 178 | skb = handle_bridge(skb, &pt_prev, &ret, orig_dev); 179 | 180 | if (!skb) goto out; 181 | 182 | //第三步:编译内核时选上MAC_VLAN模块,下面才会执行 183 | 184 | //调用 macvlan_handle_frame_hook(skb), 在动态模块linux26_24/drivers/net/macvlan.c中 185 | 186 | //macvlan_handle_frame_hook = macvlan_handle_frame; 187 | 188 | //所以实际函数为 macvlan_handle_frame。 189 | 190 | //注意:此函数里会初始化 skb->pkt_type 为 PACKET_BROADCAST、PACKET_MULTICAST、PACKET_HOST 191 | 192 | skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev); 193 | 194 | if (!skb) goto out; 195 | 196 | //第四步:最后 type = skb->protocol; &ptype_base[ntohs(type)&15] 197 | 198 | //处理ptype_base[ntohs(type)&15]上的所有的 packet_type->func() 199 | 200 | //根据第二层不同协议来进入不同的钩子函数,重要的有:ip_rcv() arp_rcv() 201 | 202 | type = skb->protocol; 203 | 204 | list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { 205 | 206 | if (ptype->type == type && //遍历包type所对应的链表 207 | 208 | (!ptype->dev || ptype->dev == skb->dev)) { //调用链表上所有pakcet_type.func() 209 | 210 | if (pt_prev) 211 | 212 | ret = deliver_skb(skb, pt_prev, orig_dev); //就这里!arp包会调arp_rcv() 213 | 214 | pt_prev = ptype; // ip包会调ip_rcv() 215 | 216 | } 217 | 218 | } 219 | 220 | if (pt_prev) { 221 | 222 | ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev); 223 | 224 | } else { //下面就是数据包从协议栈返回来了 225 | 226 | kfree_skb(skb); //注意这句,若skb没进入socket的接收队列,则在这里被释放 227 | 228 | ret = NET_RX_DROP; //若skb进入接收队列,则系统调用取包时skb释放,这里skb引用数减一而已 229 | 230 | } 231 | 232 | out: 233 | 234 | rcu_read_unlock(); 235 | 236 | return ret; 237 | 238 | } 239 | 240 | int deliver_skb(struct sk_buff *skb,struct packet_type *pt_prev, struct net_device *orig_dev){ 241 | 242 | atomic_inc(&skb->users); //这句不容忽视,与后面流程的kfree_skb()相呼应 243 | 244 | return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调函数ip_rcv() arp_rcv()等 245 | 246 | } 247 | 248 | 249 | ``` 250 | 251 | 252 | 253 | 254 | 255 | 这里只是将大致流程,arp_rcv(), ip_rcv() 什么的具体流程,以后再写。 256 | 257 | 258 | 259 | ##### 其次我们分析tcpdump加入hook的流程: 260 | 261 | 1、首先用一个别的例子来帮助理解; 262 | 263 | 下面是一个使用RAW socket获取802.1x协议(0x888e)的例子,可以帮助理解! 264 | 265 | ``` 266 | int init_sockets() 267 | 268 | { 269 | 270 | struct ifreq ifr; 271 | 272 | struct sockaddr_ll addr; 273 | 274 | struct sockaddr_in addr2; 275 | 276 | 277 | 278 | drv->sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_PAE));//802.1x协议(0x888e) 279 | 280 | if (drv->sock < 0) { 281 | 282 | perror("socket[PF_PACKET,SOCK_RAW]"); 283 | 284 | return -1; 285 | 286 | } 287 | 288 | 289 | 290 | os_memset(&ifr, 0, sizeof(ifr)); 291 | 292 | os_strlcpy(ifr.ifr_name, “eth0”, sizeof(ifr.ifr_name));//指定接口eth0 293 | 294 | if (ioctl(drv->sock, SIOCGIFINDEX, &ifr) != 0) {//获取eth0接口的index 295 | 296 | perror("ioctl(SIOCGIFINDEX)"); 297 | 298 | return -1; 299 | 300 | } 301 | 302 | 303 | 304 | os_memset(&addr, 0, sizeof(addr)); 305 | 306 | addr.sll_family = AF_PACKET; 307 | 308 | addr.sll_ifindex = ifr.ifr_ifindex; 309 | 310 | 311 | 312 | if (bind(drv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {//绑定sock 313 | 314 | perror("bind"); 315 | 316 | return -1; 317 | 318 | } 319 | 320 | ... 321 | 322 | return 0; 323 | 324 | } 325 | 326 | 327 | 328 | void read(int sock...) 329 | 330 | { 331 | 332 | int len; 333 | 334 | unsigned char buf[3000]; 335 | 336 | len = recv(sock, buf, sizeof(buf), 0); 337 | 338 | ... 339 | 340 | } 341 | 342 | int send(int sock, char *buf, int bufsize) 343 | 344 | { 345 | 346 | return send(sock, buf, bufsize, 0); 347 | 348 | ... 349 | 350 | } 351 | ``` 352 | 353 | 354 | 355 | 2、tcpdump使用RAW socket从内核中抓取指定协议的数据包流程分析 356 | 357 | tcpdump也是在二层抓包的,用的是libpcap库,它的基本原理是 358 | 1.先创建socket,内核dev_add_packet()挂上自己的钩子函数 359 | 2.然后在钩子函数中,把skb放到自己的接收队列中, 360 | 3.接着系统调用recv取出skb来,把数据包skb->data拷贝到用户空间 361 | 4.最后关闭socket,内核dev_remove_packet()删除自己的钩子函数 362 | 363 | 下面是一些重要的数据结构,用到的钩子函数都在这里初始化好了 364 | 365 | ``` 366 | static const struct proto_ops packet_ops = { 367 | .family = PF_PACKET, 368 | .owner = THIS_MODULE, 369 | .release = packet_release, //关闭socket的时候调这个 370 | .bind = packet_bind, 371 | .connect = sock_no_connect, 372 | .socketpair = sock_no_socketpair, 373 | .accept = sock_no_accept, 374 | .getname = packet_getname, 375 | .poll = packet_poll, 376 | .ioctl = packet_ioctl, 377 | .listen = sock_no_listen, 378 | .shutdown = sock_no_shutdown, 379 | .setsockopt = packet_setsockopt, 380 | .getsockopt = packet_getsockopt, 381 | .sendmsg = packet_sendmsg, 382 | .recvmsg = packet_recvmsg, //socket收包的时候调这个 383 | .mmap = packet_mmap, 384 | .sendpage = sock_no_sendpage, 385 | }; 386 | 387 | static struct net_proto_family packet_family_ops = { 388 | .family = PF_PACKET, 389 | .create = packet_create, //创建socket的时候调这个 390 | .owner = THIS_MODULE, 391 | }; 392 | 393 | ``` 394 | 395 | **1 系统调用socket** 396 | libpcap系统调用socket,内核最终调用 packet_create 397 | 398 | 这里有个映射表可以参考: 399 | 400 | | 用户空间 | 内核空间 | 说明 | 401 | | ----------- | ----------------------- | ------------ | 402 | | socket() | packet_create() | 创建特定类型socket | 403 | | ioctl() | packet_ioctl() | | 404 | | bind[()]() | **packet_bind()** | 绑定socket | 405 | | recv() | packet_recvmsg() | | 406 | | send() | packet_sendmsg() | | 407 | | setsocket() | **packet_setsockopt**() | | 408 | | getsocket() | **packet_getsockopt**() | | 409 | 410 | 411 | 412 | 下面是内核态packet_create是如何将钩子函数载入二层的: 413 | 414 | ``` 415 | static int packet_create(struct net *net, struct socket *sock, int protocol){ 416 | 417 | po->prot_hook.func = packet_rcv; //初始化钩子函数指针 418 | 419 | po->prot_hook.af_packet_priv = sk; 420 | 421 | if (protocol) { 422 | 423 | po->prot_hook.type = protocol; //类型是系统调用socket形参指定的 424 | 425 | dev_add_pack(&po->prot_hook);//这里将hook放入了二层收函数里 426 | 427 | sock_hold(sk); 428 | 429 | po->running = 1; 430 | 431 | } 432 | 433 | return(0); 434 | 435 | } 436 | 437 | ``` 438 | 439 | 440 | 441 | **2 钩子函数 packet_rcv 将skb放入到接收队列** 442 | 文件 linux_2_6_24/net/packet/af_packet.c 443 | 简单来说,packet_rcv中,skb越过了整个协议栈,直接进入队列 444 | 445 | 446 | 447 | **3 系统调用recv** 448 | 449 | tcpdump在建立好socket后,可以使用系统调用recv、read、recvmsg, 450 | 451 | 内核最终会调用**packet_recvmsg** 452 | 从接收队列中取出skb,将数据包内容skb->data拷贝到用户空间,后续处理由tcpdump自己处理: 453 | 454 | 455 | 456 | **4 系统调用close** 457 | 内核最终会调用packet_release 458 | 459 | ``` 460 | static int packet_release(struct socket *sock){ 461 | 462 | struct sock *sk = sock->sk; 463 | 464 | struct packet_sock *po; 465 | 466 | if (!sk) return 0; 467 | 468 | po = pkt_sk(sk); 469 | 470 | write_lock_bh(&packet_sklist_lock); 471 | 472 | sk_del_node_init(sk); 473 | 474 | write_unlock_bh(&packet_sklist_lock); 475 | 476 | // Unhook packet receive handler. 477 | 478 | if (po->running) { 479 | 480 | dev_remove_pack(&po->prot_hook); //就是这句!!把packet_type从链表中删除 481 | 482 | po->running = 0; 483 | 484 | po->num = 0; 485 | 486 | __sock_put(sk); 487 | 488 | } 489 | 490 | packet_flush_mclist(sk); 491 | 492 | // Now the socket is dead. No more input will appear. 493 | 494 | sock_orphan(sk); 495 | 496 | sock->sk = NULL; 497 | 498 | /* Purge queues */ 499 | 500 | skb_queue_purge(&sk->sk_receive_queue); 501 | 502 | sk_refcnt_debug_release(sk); 503 | 504 | sock_put(sk); 505 | 506 | return 0; 507 | 508 | } 509 | 510 | 511 | 512 | ``` 513 | 514 | 515 | 516 | -------------------------------------------------------------------------------- /linux网络相关/巨页总结.md: -------------------------------------------------------------------------------- 1 | # 巨页总结 2 | 3 | 4 | 5 | ##1、hugepage永久 配置 6 | 7 | 修改/etc/default/grub 中的 GRUB_CMDLINE_LINUX,然后运行 grub 更新并重启系统: 8 | 9 | ```shell 10 | [root@~]# cat /etc/default/grub 11 | GRUB_TIMEOUT=5 12 | GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)" 13 | GRUB_DEFAULT=saved 14 | GRUB_DISABLE_SUBMENU=true 15 | GRUB_TERMINAL_OUTPUT="console" 16 | GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet default_hugepagesz=1G hugepagesz=1G hugepages=16 hugepagesz=2M hugepages=2048 intel_iommu=off isolcpus=0,20,1,21,10,30,11,31" 17 | GRUB_DISABLE_RECOVERY="true" 18 | 19 | ``` 20 | 21 | hugepage 的数量和类型可根据系统中的可用内存进行调整。 `isolcpus` 参数支持我们将某些 CPU 与 Linux 调度程序隔离,以便基于 DPDK 的应用能够锁定到这些 CPU 上。 22 | 23 | 注意:上面设置的是系统预留内存,如果是单cpu,预留大小肯定就是上面设置的值,如果是numa系统,每个cpu node平分上面设置的大小(比如系统有cpu node 0和node1,`hugepages``=``2048,`在查看/sys/devices/system/node/node0/hugepages/nr_hugepages时,大小肯定是1024)命令 echo ‘vm.nr_hugepages=2048' > /etc/sysctl.d/hugepages.conf也可以设置,但一定注意,有时发现grub设置的大页大小和实际大小不一样时,可能就是/etc/sysctl.d/hugepages.conf里的参数在作怪。默认情况下就是系统当前所有在线NUMA节点平均分配这些HugePages,除非那个NUMA节点本身没有足够的可用连续内存来生成 HugePages,那么此时HugePages将由另外一个NUMA节点生成 24 | 25 | 重启系统后,查看内核 cmdline 并按照如下方式分配 hugepage。 26 | 27 | ```shell 28 | [root@~]# cat /proc/meminfo |grep Hug 29 | AnonHugePages: 626688 kB 30 | HugePages_Total: 0 31 | HugePages_Free: 0 32 | HugePages_Rsvd: 0 33 | HugePages_Surp: 0 34 | Hugepagesize: 2048 kB 35 | ``` 36 | 37 | 接下来是安装 hugepage 文件系统,加载 `vfio-pci` 用户空间驱动程序(如果系统已经支持巨页,则可以省略该步)。 38 | 39 | 使用巨页需要挂载巨页系统到`/``mnt``/``huge` 40 | 41 | ```shell 42 | mkdir -p /mnt/huge 43 | 44 | mkdir -p /mnt/huge_2mb 45 | 46 | mount -t hugetlbfs hugetlbfs /mnt/huge 47 | 48 | mount -t hugetlbfs none /mnt/huge_2mb -o pagesize=2MB 49 | ``` 50 | 51 | 52 | 53 | ##2、临时配置 54 | 55 | Hugepage能够动态预留,执行命令: 56 | 57 | ``` 58 | $ echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 59 | ``` 60 | 61 | 62 | 63 | 64 | 65 | 上面通过没有节点关联的系统分配内存页。如果希望强制分配给指定的NUMA节点,你必须做: 66 | 67 | ``` 68 | echo 1024 >/sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages 69 | 70 | #echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages 71 | 72 | hugepages-2048kB指预留2M的大小为1024个,一共2*1024M,hugepages-1048576kB指预留1G的大小 73 | 74 | ``` 75 | 76 | 77 | 78 | ## 3、查看挂载情况: 79 | 80 | ```shell 81 | [root@ ~]# cat /proc/mounts 82 | rootfs / rootfs rw 0 0 83 | proc /proc proc rw,relatime 0 0 84 | sysfs /sys sysfs rw,relatime 0 0 85 | devtmpfs /dev devtmpfs rw,relatime,size=65988392k,nr_inodes=16497098,mode=755 0 0 86 | devpts /dev/pts devpts rw,relatime,gid=5,mode=620,ptmxmode=000 0 0 87 | tmpfs /dev/shm tmpfs rw,relatime 0 0 88 | /dev/mapper/vg_xzpcpsitj02n010253-lv_root / ext4 rw,relatime,barrier=1,data=ordered 0 0 89 | /proc/bus/usb /proc/bus/usb usbfs rw,relatime 0 0 90 | /dev/sda1 /boot ext4 rw,relatime,barrier=1,data=ordered 0 0 91 | none /proc/sys/fs/binfmt_misc binfmt_misc rw,relatime 0 0 92 | /etc/auto.misc /misc autofs rw,relatime,fd=7,pgrp=3585,timeout=300,minproto=5,maxproto=5,indirect 0 0 93 | -hosts /net autofs rw,relatime,fd=13,pgrp=3585,timeout=300,minproto=5,maxproto=5,indirect 0 0 94 | /dev/loop0 /mnt/centos7.3 iso9660 ro,relatime 0 0 95 | /dev/loop1 /mnt/centos6.5 iso9660 ro,relatime 0 0 96 | /dev/loop2 /mnt/redhat6.3 iso9660 ro,relatime 0 0 97 | 98 | ``` 99 | 100 | 会看见hugetlbfs /mnt/huge hugetlbfs rw,relatime 0 0这样的,这就是挂载了大页,在free不够时,在确定可以umount时,把这些umount一下就可以了,vpp代码自动挂载大页的,ovs+dpdk需要手动挂载. 101 | 102 | 103 | 104 | ## 4、ovs+dpdk大页问题 105 | 106 | 有时没法重启服务器只能配置临时大页时,但启动ovs+dpdk会报错, 107 | 108 | A: 109 | 110 | ![img](../Images/20170214105014180.png) 111 | 112 | 这是因为没有手动挂载大页造成的,需要执行mount -t hugetlbfs hugetlbfs /mnt/huge 113 | 114 | B: 115 | 116 | ![img](../Images/20170214105218009.png)) 117 | 118 | 这是因为配置的大页大小不满足ovs+dpdk大页需求,需要增大临时大页配置 119 | 120 | C: 121 | 122 | ![img](../Images/20170214105343056.png) 123 | 124 | 这种情况具体原因我还没有搞清楚,但可以通过先设置很小的配置,然后再设置大的配置可以解决 125 | 126 | 比如原来的设置是4096 echo 4096 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 127 | 128 | AnonHugePages: 5218304 kB 129 | HugePages_Total: 4096 130 | HugePages_Free: 4096 131 | HugePages_Rsvd: 0 132 | HugePages_Surp: 0 133 | Hugepagesize: 2048 kB 134 | 135 | 现在先设置 echo 128 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 136 | 137 | 再设置echo 4096 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 138 | 139 | 估计是内存连续问题造成的 140 | 141 | ##5、其他问题 142 | 143 | 有时,你会发现大页 144 | 145 | ![img](../Images/20170306140550331.png) 146 | 147 | 不管怎样配置,free就是0,top或是free命令,查看内存基本使用完了,但是我们并没有启动vpp或是ovs,这个很大可能就是其他进程使用了hugepage,使用以下命令查找 148 | 149 | find /proc/*/smaps | xargs grep -ril "anon_hugepage" 150 | 151 | 原理请查看里hugepage内容 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /openstack/KVM上如何绑定虚拟机vcpu与物理CPU.md: -------------------------------------------------------------------------------- 1 | # KVM上如何绑定虚拟机vcpu与物理CPU 2 | 3 | 4 | 5 | 两种方式: 6 | 7 | 1、taskset 8 | 9 | **Taskset命令设置某虚拟机在某个固定cpu上运行** 10 | 11 | ``` 12 | taskset -p000000000000000000000000000000000000100 95090 13 | 解释:设置95090这个进程,在cpu8上运行 14 | ``` 15 | 16 | 17 | 18 | 可以使用命令来查看对应进程在哪个cpu上运行 19 | 20 | ``` 21 | ps -e -o pid,args,psr 22 | ``` 23 | 24 | 25 | 26 | 2、使用virsh的命令vcpupin来绑定(Pin guest domain virtual CPUs to physical host CPUs) 27 | 28 | ​ 29 | 30 | ``` 31 | 绑定命令: 32 | 33 | virsh vcpupin 4 0 8 34 | 35 | :绑定domain 4 的vcpu0 到物理CPU8 36 | ``` 37 | 38 | 39 | 40 | 41 | 42 | Taskset和vcpupin区别: 43 | 44 | Taskset是以task(也就是虚拟机)为单位,也就是以虚拟机上的所有cpu为一个单位,与物理机上的cpu进行绑定,它不能指定虚拟机上的某个vcpu与物理机上某个物理cpu进行绑定,其粒度较大。 45 | 46 | Vcpupin命令就可以单独把虚拟机上的vcpu与物理机上的物理cpu进行绑定。 47 | 48 | 比如vm1有4个vcpu(core),物理机有8个cpu(8个core,假如每个core一个线程),taskset能做到把4个vcpu同时绑定到一个或者多个cpu上,但vcpupin能把每个vcpu与每个cpu进行绑定。 -------------------------------------------------------------------------------- /openstack/Ovs bond.md: -------------------------------------------------------------------------------- 1 | #Ovs bond 2 | 3 | ```c 4 | bond_choose_output_slave 5 | choose_output_slave 6 | e = lookup_bond_entry(bond, flow, vlan); 7 | ``` 8 | 9 | 10 | 11 | 12 | 13 | 静态汇聚原理 14 | 15 | ​ 动态汇聚和静态汇聚原理类似,只是动态汇聚中所有端口都是通过协议确定,而不是像静态汇聚通过协议在指定端口中确定汇聚相关端口。 -------------------------------------------------------------------------------- /openstack/Sriov 下一些命令.md: -------------------------------------------------------------------------------- 1 | Sriov 下一些命令 2 | 3 | 4 | 1.使用如下命令将网卡的VF数量设置为63个,如果该数量是0,则代表没有激活vf口的功能 5 | ```shell 6 | echo 63 > /sys/class/net/eth48/devices/SR-IOV_numfs 7 | ``` 8 | 2.使用如下命令确认VF数量为63个 9 | 10 | ```shell 11 | ip l sh eth48 |grep vf |wc -l 12 | ``` 13 | 14 | 15 | 16 | 3、设置sriov的vf口的mac地址,qos,vlan等等信息 17 | 18 | ```shell 19 | ip link set dev eth1 vf 1 20 | 可使用ip link help 查看 21 | ``` 22 | 23 | 24 | 25 | 4、查看vf口mac地址,等等 26 | 27 | ```shell 28 | ip link sh #查看网卡信息 29 | 30 | lspci |grep Eth #查看pci总线信息 31 | 32 | ethtool -S eth1 #查看sriov 网卡的收发统计,收发队列等等信息 33 | ``` 34 | 35 | 5、查看网口在的numa节点,等等信息(ls /sys/class/net/里面的文件都是软链接,使用ls -l可以看到每个网卡对应的pci号) 36 | 37 | ```shell 38 | cat /sys/class/net/enp130s0f0/device/numa_node 39 | 40 | cat /sys/class/net/enp130s0f0/device/virtfn1/numa_node 41 | ``` 42 | 43 | 6、查看pf口下的vf口的pci地址,以及查看vf口对应的pf口 44 | 45 | ```shell 46 | [root@controller net]# ll /sys/class/net/enp130s0f0/device/virtfn1 47 | lrwxrwxrwx 1 root root 0 Nov 10 15:27 /sys/class/net/enp130s0f0/device/virtfn1 -> ../0000:82:10.2 48 | 49 | #这个软链接的pci号代表了这个vf1口对应的pci号 50 | #反之也可以看vf->pf的关系 51 | 52 | ll /sys/bus/pci/devices/0000\:82\:10.2/physfn #可以看到该vf口对应的pf口的pci号 53 | 54 | ``` 55 | 56 | 7、网络性能测试等工具 57 | 58 | ```shell 59 | iperf -c $IP -u -t 3000 -i 1 -l 64 -b 1000m #客户端 60 | iperf -s -u #服务端 61 | nmon #查看各种监控 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /openstack/流表的类型与查看.md: -------------------------------------------------------------------------------- 1 | ## 流表的类型: 2 | 3 | 4 | 5 | dp层的流表是精确流表,并且会被超时,暂时认为是控制层有线程会定期获取dp层的流表,并判断是否一段时间没有流量然后控制层去让他删除流表。 6 | 7 | 8 | 9 | 第一条报文在没有精确流表的情况下,会upcall上送到控制层,控制层查of流表来下发精确流表。 10 | 11 | 后面在dp层就可以直接根据流表转发到各个口并且做剥离vlan或什么的操作。 12 | 13 | 14 | 15 | ### 1、在of层的流表 16 | 17 | ​ 使用命令: 18 | 19 | ![img](http://images0.cnblogs.com/blog2015/697113/201507/091805147052687.jpg) 20 | 21 | ## 22 | 23 | ``` 24 | ovs-ofctl dump-flows br-int 25 | ovs-ofctl dump-flows br-bond1 26 | ``` 27 | 28 | 可以看到流表,在控制层的流表有table的信息,会根据流表转到不同的table去处理, 29 | 30 | 查看table也可以有命令: 31 | 32 | ``` 33 | ovs-ofctl dump-tables br-int 34 | ovs-ofctl dump-tables br-bond1 35 | ``` 36 | 37 | 38 | 39 | 40 | 41 | ### 2、在dp层的流表: 42 | 43 | 可用用两个命令来查看,如果要分桥可以使用ovs-appctl 44 | 45 | ``` 46 | ovs-appctl dpif/dump-flows br-int 47 | 48 | ovs-appctl dpif/dump-flows br-bond1 49 | 50 | 51 | ``` 52 | 53 | 如果不分桥,可以直接用 54 | 55 | ``` 56 | ovs-dpctl show 57 | ``` 58 | 59 | 60 | 在流表中会有in_port信息,如何知道该input_port是什么呢,同样可以使用两个命令,分别是: 61 | ``` 62 | ovs-appctl dpif/show 63 | ``` 64 | 65 | ``` 66 | ovs-dpctl show 67 | ``` 68 | 69 | 70 | 71 | 下面是普通的一条流表: 72 | 73 | ``` 74 | skb_priority(0),in_port(38),eth(src=fa:16:3e:69:fb:5a,dst=48:7a:da:68:1c:01),eth_type(0x0800),ipv4(src=10.37.215.251/255.255.255.255,dst=10.37.76.188/255.255.255.255,proto=6/0xff,tos=0/0,ttl=64/0,frag=no/0xff),tcp(src=6379,dst=56276), packets:0, bytes:0, used:never, actions:push_vlan(vid=3854,pcp=0),3 75 | 76 | ``` 77 | 78 | 上面的in_port 可以通过上面的命令查出来对应的口,actions 是一连串的动作,比如先转换vlan,再从3口发出去。这里的actions里面的3就是几号口。 --------------------------------------------------------------------------------