如何获取容器的 pid 信息

共有两种场景,一种是想知道容器的pid,第二种通过宿主机确定了某pid,想确定属于哪个容器。

对于场景一:

docker inspect <CONTAINER ID> 可以获取容器的pid。配合以下命令可以快速得到容器的pid:

1595217719704

该方法得到的容器内的 pid=1 的进程。

image-20200719171414650

对于场景二:

使用脚本 DingGuodong/LinuxBashShellScriptForOps

该脚本可以查找进程树:

1.查找pid所对应的容器id,并打印容器的详细信息

2.获取此进程的进程树和含有命令行的进程树

image-20200719171524191

参考资料


在shell脚本中使用系统 alias - xspace

原文:在shell脚本中使用alias,有改动。

Linux shell有交互式与非交互式两种工作模式。我们日常使用shell输入命令得到结果的方式是交互式的方式,而shell脚本使用的是非交互式方式。

shell提供了alias功能来简化我们的日常操作,使得我们可以为一个复杂的命令取一个简单的名字,从而提高我们的工作效率。在交互式模式下,shell的alias扩展功能是打开的,因此我们可以键入自己定义的alias别名来执行对应的命令。

但是,在非交互式模式下alias扩展功能默认是关闭的,此时仍然可以定义alias别名,但是shell不会将alias别名扩展成对应的命令,而是将alias别名本身当作命令执行,如果shell内置命令和PATH中均没有与alias别名同名的命令,则shell会“抱怨”找不到指定的命令。

那么,有没有办法在非交互式模式下启用alias扩展呢?答案是使用shell内置命令shopt命令来开启alias扩展选项。shopt是shell的内置命令,可以控制shell功能选项的开启和关闭,从而控制shell的行为。shopt的使用方式如下:

shopt -s opt_name         Enable (``set``) opt_name.
shopt -u opt_name         Disable (``unset``) opt_name.
shopt opt_name          Show current status of opt_name.

alias扩展功能的选项名称是expand_aliases,我们可以在交互式模式下查看此选项是否开启:

sw@gentoo ~ $ shopt expand_aliases
expand_aliases on
sw@gentoo ~ $

在非交互式模式下alias扩展功能默认是关闭的,但是我们可以用shopt来将其开启。

如果我们要让执行shell脚本的子shell读取.bashrc的话,有两种方式可以达到这样的效果

  1. 给shell脚本第一行的解释器加上参数

    #!/bin/bash --login
    
  2. 在脚本中主动source ~/.bashrc


比较kube-proxy模式 iptables或IPVS? - tigera

这是calico母公司的一篇分享博客。讲得挺好,转载过来。机翻警告⚠️

kube-proxy 是 Kubernetes部署的关键组件。它的作用是使发往service 的流量(通过群集IP和节点端口)负载均衡到正确的后端 Pod。Kube-proxy可以三种模式之一运行,每种模式都使用不同的数据平面技术实现: userspaceiptablesIPVS

userspace非常陈旧,缓慢,不建议使用!但是,如何权衡使用iptables还是IPVS模式呢?在本文中,我们将比较两者,评估它们在真正的微服务环境中的性能,并解释何时选择一个与另一个。

首先,我们将从两种模式的背景知识入手,然后深入了解下面的测试和结果…

背景:iptables代理模式

iptables是一种Linux内核功能,旨在成为高效的防火墙,并具有足够的灵活性来处理各种常见的数据包操作和过滤需求。它允许将灵活的规则序列附加到内核的数据包处理管道中的各种钩子上。在iptables模式下,kube-proxy 将规则附加到“ NAT pre-routing” 钩子上以实现 NAT和负载平衡功能。它的工作原理很简单,并且与其他也使用 iptables 进行过滤的程序(例如Calico!)一同运行而不互相影响。

但是,kube-proxy 下增加的iptables条目空间复杂度是 O(n),条目数在 service的数量和每个 service 背后的 Pod的数量增长而成比例地增长。

背景:IPVS代理模式

IPVS是专门用于负载平衡的Linux内核功能。在IPVS模式下,kube-proxy 对IPVS负载均衡器进行编程,而不是使用iptables。IPVS 专 为负载均衡大量服务而设计;它优化了API和查找目标的方式,而不是顺序查找。

IPVS模式下的kube-proxy的连接处理的计算复杂度为O(1)。换句话说,在大多数情况下,其连接处理性能将保持恒定,与群集大小无关。

此外,作为专用的负载平衡器,IPVS拥有多种不同的调度算法,例如循环调度,最短期望延迟,最少连接和各种哈希方法。iptables 中的 kube-proxy 只能使用随机等价选择算法。

IPVS的一个潜在缺点是,与正常情况下的数据包相比,由IPVS处理的数据包通过iptables filter hooks的路径非常不同。如果您打算将IPVS与其他使用iptables的程序一起使用,则需要研究它们是否可以一起正常工作。

性能比较

名义上kube-proxy在iptables模式下的连接处理为O(n),在IPVS模式下为O(1)。但是,在微服务场景中的实际表现怎么样呢?

在大多数情况下,在应用程序和微服务的上下文中,当涉及到kube-proxy的性能时,您可能会关心两个关键属性:

  1. 对往返响应时间的影响。 当一个微服务对另一个微服务进行API调用时,第一个微服务平均向第二个微服务发送请求并从第二个微服务接收响应的时间为多长时间?
  2. 对总CPU使用率的影响。 在支持微服务(包括kube-proxy)所需的所有进程中,运行微服务时主机的总CPU使用量是多少,包括用户空间和内核/系统使用情况?

为了说明这一点,我们在专用节点上运行一个专门用来发请求的pod,它每秒生成1000个请求,所有的请求打到一个service上,这个service后边挂着10个pod。然后,我们在iptables和IPVS模式下,使用若干个的Kubernetes service(每个服务有10个Pod支持),最多10,000个service(带有100,000个endpoints)来测量客户端节点上的性能。对于微服务,我们使用了用golang编写的简单测试工具作为客户端微服务,并用标准NGINX作为服务器微服务的后端容器。

往返响应时间

考虑往返响应时间时,了解连接和请求之间的区别很重要。通常,大多数微服务将使用持久性或“ keepalive”连接,这意味着每个连接可在多个请求中重用,而不是每个请求都需要一个新的连接。这很重要,因为大多数新连接都需要通过网络进行三向TCP握手(这需要时间),Linux网络堆栈中的更多处理(这需要更多时间和CPU)。

为了说明这些差异,我们在有和没有保持连接的情况下进行了测试。对于保持连接,我们使用了NGINX的默认配置,该配置使每个连接保持活动状态,最多可重复使用100个请求。请参见下图,请注意,响应时间越短越好。

img

图表显示了两个关键事项:

  • 在获得超过1,000个服务(10,000个后端Pod)之前,iptables和IPVS之间的平均往返响应时间的差异微不足道。
  • 仅当不使用keepalive连接时,才能确定平均往返响应时间的差异。即当为每个请求使用新连接时。

对于iptables和IPVS模式,kube-proxy的响应时间开销与建立连接相关,而不是与您在这些连接上发送的数据包或请求的数量有关。这是因为Linux使用的连接跟踪(conntrack)能够非常有效地将数据包与现有连接进行匹配。如果数据包在conntrack中匹配,则无需通过kube-proxy的iptables或IPVS规则来确定如何处理它。Linux conntrack是您的朋友! (几乎所有时间……。请关注我们的下一篇博客文章“当Linux conntrack不是您的朋友时!”)

值得注意的是,在此示例中,对于“服务器”微服务,我们使用NGINX Pod服务于一个小的静态响应主体。许多微服务需要做的工作远远超过此,这将导致相应的响应时间更长,这意味着与该图表相比,用于kube-proxy处理的增量将占响应时间的百分比较小。

最后有一个奇怪的解释:如果IPVS中新连接的处理是O(1)复杂性,为什么在10,000个服务上IPVS的非Keepalive响应时间会变慢?我们需要做更多的挖掘工作才能真正深入了解此问题,但其中一个因素是,由于主机上CPU使用率的增加,整个系统的运行速度会变慢。这使我们很好地讨论了下一个主题。

总CPU

为了说明总的CPU使用率,下表重点介绍了不使用持久/ keepalive连接的最坏情况,在这种情况下,kube-proxy连接的处理开销影响最大。

img

图表显示了两个关键事项:

  • iptables和IPVS之间的CPU使用率差异不明显,直到获得超过1,000个服务(带有10,000个后端Pod)为止。
  • 在10,000个服务(具有100,000个后端pod)的情况下,使用iptables的CPU的增加量约为内核的35%,而使用IPVS的CPU的增加量约为内核的8%。

影响此CPU使用模式的主要因素有两个。

第一个贡献者是默认情况下,kube-proxy以30秒的间隔对所有服务进行内核重新编程。这解释了为什么IPVS模式在CPU上会略有增加,即使IPVS对新连接的处理名义上的复杂度为O(1)。此外,在较旧内核版本中对iptables进行重新编程的API比现在慢得多。因此,如果您在iptables模式下使用带有kube-proxy的较旧内核,则CPU增长将比该图表还要高。

第二个原因是kube-proxy使用iptables或IPVS处理新连接所花费的时间。对于iptables,名义上是O(n)。在大量服务中,这极大地影响了CPU使用率。例如,在10,000个服务(带有100,000个后端Pod)上,iptables为每个新连接执行约20,000个规则。请记住,尽管在此图表中,我们显示的是微服务的最坏情况,该微服务针对每个请求使用新的连接。如果我们使用NGINX的默认keepalive(每个连接100个请求),那么kube-proxy的iptables规则执行的频率将减少100倍,从而大大减少了使用iptables而不是IPVS可能对CPU造成的影响,降低到接近内核的2%。

值得注意的是,本示例中使用的“客户端”微服务只是丢弃了从“服务器”微服务收到的每个响应。真正的微服务将需要做的工作远不止于此,这将增加此图表中的基本CPU使用率,但不会改变与服务数量相关的CPU绝对增加量。

结论

kube-proxy的IPVS模式可扩展到超过1,000个服务,可以提供一些不错的性能改进。您的工作量可能会有所不同,但是作为一般指南,对于使用持久性“ keepalive”风格连接且在现代内核上运行的微服务,其收益可能相对较小。对于不使用持久连接的微服务,或者在较旧的内核上运行时,将kube-proxy切换到IPVS模式将是一个不错的选择。

与性能无关,如果您需要比kube-proxy的iptables模式随机负载平衡更复杂的负载平衡调度算法,则还应该考虑使用IPVS模式。

如果不确定IPVS是否会为您取胜,请在iptables模式下坚持使用kube-proxy。它在生产中进行了更多的强化,尽管它并不完美,但您可以说这是默认设置,这是有原因的。

后记:比较kube-proxy和Calico对iptables的使用

在本文中,我们已经看到kube-proxy对iptables的使用会如何在非常大的范围内导致性能影响。有时我会被问到为什么Calico没有同样的挑战。答案是,Calico对iptables的使用与kube-proxy的显着不同。Kube-proxy使用非常长的规则链,该规则链与群集大小大致成比例增长,而Calico使用非常短的优化规则链并广泛使用ipset,ipset的查找与大小无关。

为了更好地理解这一点,下表显示了kube-proxy与Calico每次连接执行的iptables规则的平均数量,假设集群中的节点平均托管30个Pod,集群中的每个Pod平均拥有3个网络策略适用于它。

img

即使在具有10,000个服务和100,000个后端Pod的完全扩展集群中运行时,Calico每个连接也只能执行与kube-proxy在200个后端Pod上执行20个服务时大致相同数量的iptables规则。换句话说,Calico对iptables的使用规模!


python error command x86_64-linux-gnu-gcc failed with exit status 1

今天在试用一个python应用-glances,挺好用的,可惜在我服务器上安装:

pip install glances

运行出现了这个问题:

error: command 'x86_64-linux-gnu-gcc' failed with exit status 1 '

恼人。参考GitHub issues给出的办法,解决办法如下:

python3 运行:

sudo apt-get install python3 python-dev python3-dev \
     build-essential libssl-dev libffi-dev \
     libxml2-dev libxslt1-dev zlib1g-dev \
     python-pip

python2 运行:

sudo apt-get install python-dev  \
     build-essential libssl-dev libffi-dev \
     libxml2-dev libxslt1-dev zlib1g-dev \
     python-pip

参考资料


linux 网络学习备忘

血衫目前专注于 kubernetes 网络相关的工作,但由于自己的关注点从纯粹的应用层开发到应用运维,再到现在的底层网络,非网络科班出身,对于网络相关的东西仍然觉得无论如何都摸不清楚。

这篇文章一些内容是机翻过来的,原文链接参考文末。希望这篇文章能理清自己的思路,也不断完善修正。

目录:

  1. 通信协议
  2. 协议栈
  3. 网络协议族
  4. tcp/ip概述
    1. 网络流量路径
    2. 协议栈
    3. 路由
    4. 消息发送
    5. 消息接收
    6. IP Forwarding
    7. IP 路由
  5. 内核态\用户态\ 进程上下文\中断上下文
  6. Netfilter

一、通信协议 communications protocol

通信协议也称传输协议,一般指电信领域,在任何物理介质中允许两个或多个在传输系统中的终端之间传播信息的系统标准,也是指计算机通信或网络设备的共同语言。

通信协议在硬件、软件或两者之间皆可实现。

编程语言是为了模式化的计算而传输协议为了更畅通的交流。

  1. 网络传输协议(Internet communication protocol)是互联网工程任务组 (IETF)制定的。Internet Engineering Task Force,互联网工程任务组。 我们平时提到的RFC文档都是他们的产物,例如常见的网络协议: IP/791、TCP/793、UDP/768、FTP/959、HTTP1.1/2616。

    目前,IETF共包括八个研究领域,131个处于活动状态的工作组。

    • 应用研究领域(app— Applications Area),含15个工作组(Work Group)
    • 通用研究领域(gen—General Area) 
    • 网际互联研究领域(int—Internet Area),含25个工作组
    • 操作与管理研究领域(ops—Operations and Management Area),含16个工作组
    • 实时应用与基础设施领域(rai—Real-time Applications and Infrastructure Area)含28个工作组
    • 路由研究领域(rtg—Routing Area),含17个工作组
    • 安全研究领域(sec—Security Area),含13个工作组
    • 传输研究领域(tsv—Transport Area),含17个工作组
  2. 电气电子工程师学会(IEEE)负责有线无线传输:IEEE 754 浮点算法规范、IEEE 802 局域网标准

  3. ITU-T 负责电信通讯传输以及公共交换电话网 (PSTN)的格式,International Telecommunication Union,国际电信联盟

  4. 国际标准化组织 (ISO) 负责其他类别,International Organization for Standardization。

ITU最老,IETF最新,这方面也体现电信向互联网的转变。

二、协议栈 Protocol_stack

协议栈是指网络中各层协议的总和。

每个协议模块通常都要和上下两个其他协议模块通信,它们通常可以想象成是协议栈中的层。最低级的协议总是描述与硬件的物理交互。每个高级的层次增加更多的特性。用户应用程序只是处理最上层的协议。

在实际中,协议栈通常分为三个主要部分:媒体,传输和应用。一个特定的操作系统或平台往往有两个定义良好的软件接口:一个在媒体层与传输层之间,另一个在传输层和应用程序之间。

媒体到传输接口定义了传输协议的软件怎样使用特定的媒体和硬件(“驱动程序”)。例如,此接口定义的TCP/IP传输软件怎么与以太网硬件对话。

应用到传输接口定义了应用程序如何利用传输层。例如,此接口定义一个浏览器怎样和TCP/IP传输软件对话。这些接口的例子包括socks套接字和微软的Winsock。

一些著名的协议栈如下:

  • TCP/IP
  • OSI model
  • ATM
  • AppleTalk

三、网络协议族 Internet_protocol_suite

通称为TCP/IP协议,TCP/IP Protocol,简称TCP/IP。

因为该协议家族的两个核心协议:TCP(传输控制协议)IP(网际协议),为该家族中最早通过的标准。这些协议最早发源于美国国防部的ARPA网项目。

TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。

0. 一些报文流向图参考

本部分参考:linux网络协议栈(一)报文流向总览

img

另一个视角下的报文流向图:

img

1. 物理层

本部分参考:linux网络协议栈(三)网卡驱动层

由于物理层太过底层,这里就记录到网卡和网卡驱动好了。

在报文接收方向上,网卡驱动把接收到的数据按照其对应的链路层协议(如以太网)组装成报文,然后把它上交给链路层,接口是netif_receive_skb,至此网卡驱动的任务就结束了,报文交给链路层处理;

在报文发送方向上,网卡驱动受链路层驱使,链路层告知其有报文要发送时,网卡驱动才开始工作,接口是dev_queue_xmit。

在linux中,所有的网络传输媒介,都用一个叫接口的东西来定义,如eth0、wlan1、usb2、vlan3、br4、eth0.1、lo等等,所谓传输媒介:

  • 可以是一个实际存在的以太网卡设备比如eth0,
  • 也可以是其他可以传输以太网格式报文的“网卡设备”比如wlan1、usb2,
  • 也可以是vlan或桥比如vlan3、eth0.1、br4等依赖于某个有物理设备支持的宿主接口,
  • 还可以是根本不存在物理设备支撑的东西如lo

但无论哪种,在内核中均以接口来定义,由结构体struct net_device描述,它包括了一切的接口的属性和方法。

这是一个庞大的结构体,它抽象了每个接口的属性和能力,诸如open、close、ioctl、接收、发送等等,也就是说每个接口的属性和能力都封装在结构体net_device中。

报文的实际收发动作是在网卡驱动中完成的,不同情况的网卡会有不同的驱动,即便都是以太网卡,具体驱动也是各式各样:

  • 它们最终发送出去的肯定必须是以太网格式的报文
  • 它们收到的数据不论最开始是否是以太网格式的报文,最终交给链路层时肯定必须是以太网格式的报文

以上两点都是网卡驱动编写者的工作。

2. 数据链路层

本部分参考:linux网络协议栈(四)链路层原理

从功能上看,链路层完成以下任务:

1、 实现与网卡驱动和协议栈上层的接口;

2、 实现网桥,即实现按MAC地址转发的二层交换机功能;

3、 实现vlan处理,实现带vlan报文的传输;

4、 实现邻居子系统,即实现链路层和网络层的映射;

5、 根据以太网类型,实现根据链路层协议类型收发报文;

6、实现二层隧道功能(典型如依托于网络层IP地址转发的eoip隧道);

链路层不一定是报文最终的归宿。

从报文接收角度看,绝大多数报文都要最终给目的主机的应用层,协议报文会在不同协议所属层次终结。大多数报文由链路层处理后(或不处理)再向上层转发:

  • 比如IP报文,在链路层由ip_rcv函数进入网络层处理,
  • 比如由应用程序通过原始套接字获取的报文,将在链路层被直接送往socket层,
  • 比如arp协议报文会被送往arp模块去处理

再从报文发送角度看,不同的处理模块,会通过其专有接口通知链路层要发送报文:

  • 比如对于网络层,通过ip_finish_output (最终是ip_finish_output2) 告诉链路层要发送报文
  • 比如原始套接字,情况则比较简单,直接调用dev_queue_xmit通知网卡驱动,
  • 对于网桥、vlan子接口的收发,链路层的处理就完全是逻辑链路层的内容,属于linux链路层的核心工作。

img

如上图,报文接收方向上,链路层由netif_receive_skb收到报文后,依次进行

  • 网桥处理

    网桥处理中会根据报文是否发给本机还是转发做不同的处理,发给本机将修改报文输入接口为桥端口并返回netif_receive_skb重新进协议栈处理,转发则直接调用dev_queue_xmit从对应接口发送

  • L2隧道处理

    L2隧道处理则根据所属隧道的目的IP寻找路由,并根据路由结果指示的出接口转发报文

  • (可能会有其他处理)

  • 按以太网类型处理

    根据以太网类型调用在之前注册过的协议处理函数,如vlan报文调用vlan_skb_recv,IP报文调用ip_rcv,arp报文调用arp_rcv等等

需要注意,网桥、vlan报文对于上层协议栈可见的是桥端口、vlan子接口(上层协议栈关心报文的“逻辑”输入接口,而不是“物理”输入接口)。

对于网桥和 vlan 的上本机的报文,会将报文的输入接口改为对应的桥端口或vlan子接口(vlan报文还会去除vlan头),反向报文也是由上层协议栈发送到桥端口或vlan子接口,再继而转换为其原始的报文输入接口通往网卡驱动。

接口的概念

接口可以理解为链路层的每一个收发单元:

  • 每一个接口可以对应实际的一个网卡
  • 也可以是一个没有实际物理设备驱动对应的“虚”接口,比如网桥接口、vlan子接口

不论是“虚”接口还是“实”接口,在内核中都是以net_device结构体描述;

对于网络层,最重要的工作是选路,即确定路由,而确定路由就是确定报文是转发还是上送本机:

  • 如果是转发的话从哪个接口发送(skb->_skb_dst),由路由判决所需条件
  • 如果是输入的话,即报文是从哪个接口上到网络层的(skb->dev)
  • 除路由之外,上层协议栈其他部分也经常关注报文的输入接口和路由转发接口;

所以接口是报文实际输入输出的逻辑路径,但不一定是物理路径:

  • 如某报文的路由结果是从eth0.5接口转发出去,那么这个eth0.5就是转发的逻辑路径,但它并没有实际的物理驱动,而是再通过vlan子接口处理最终仍由eth0实际转发报文
  • 再如某报文由网桥br0上送到网络层,那么br0是该报文的逻辑路径,上层协议栈认为该报文就是从br0接收的,而实际该报文可能是由eth1实际接收的,经过网桥处理后输入接口变为br0的。

之所以会有“虚”接口,都是因为一些特殊的目的,linux需要实现网桥,需要实现vlan,而这些都是逻辑上的概念,并不真的存在某种物理设备叫网桥或者叫vlan,所以需要人为的制造出这些“虚”接口,以满足上层协议栈向下处理的统一性,而这些“虚”接口和“实接口的“虚实转换”,就是需要链路层实现的内容。

  • “实”接口一般由网卡驱动生成,因为网卡驱动管理实际的物理设备,所以一般诸如eth0、wlan1、usb2之类“实”接口由网卡驱动生成,网卡驱动负责这些“实”接口的ops实现
  • “虚”接口一般由应用程序生成,如网桥、vlan子接口,内核源码负责这些类型的“虚”接口的ops。

可以把linux机器想象成一台能够实现交换机、路由器、主机的L2/3/4/5功能的结合体机器,“实”接口就是这个结合体机器的每个物理端口,“虚”接口表面上实现一些二层网络功能,如vlan子接口实现物理端口的vlan功能,网桥接口实现二层交换功能,而实际上这些“虚”接口同样被这个结合体机器的L3/4/5所识别。

3. 网络层

4. 传输层

5. 应用层

四、tcp/ip概述

参考资料:https://www.cs.unh.edu/cnrg/people/gherrin/linux-net.html

1. 网络流量路径

网络数据包在Linux内核中移动的简化图

当应用程序产生流量时,它将通过套接字将数据包发送到传输层(TCP或UDP),然后再发送到网络层(IP)。在IP层中,内核在路由缓存或其转发信息库(FIB)中查找到主机的路由。如果数据包是用于另一台计算机的,则内核将其寻址,然后将其发送到链路层输出接口(通常是以太网设备),该接口最终将数据包通过物理介质发送出去。

当数据包到达介质上时,输入接口将接收到该数据包,并检查该数据包是否确实适用于主机计算机。如果是这样,它将把数据包发送到IP层,该IP层将查找到数据包目的地的路由。如果数据包必须转发到另一台计算机,则IP层会将其向下发送回输出接口。如果该数据包用于某个应用程序,它将通过传输层和套接字将其向上发送,以供应用程序准备就绪时读取。

2. 协议栈

网络设备构成了协议栈的底层;他们使用链路层协议(通常是以太网)与其他设备进行通信,以发送和接收流量。

在Linux设备模型中,Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。 为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个内部Bus、虚拟Bus或者Platform Bus.

输入接口从介质复制数据包,执行一些错误检查,然后将其转发到网络层。输出接口从网络层接收数据包,执行一些错误检查,然后通过介质将其发送出去。

IP是标准的网络层协议。它检查传入的数据包,以查看它们是否用于主机计算机或是否需要转发。如有必要,它将对数据包进行碎片整理并将其传送到传输协议。它维护出站数据包路由的数据库;在将它们发送到链路层之前,如有必要,它会对其进行寻址和分段。

TCP和UDP是最常见的传输层协议。UDP只是提供了一种用于将数据包寻址到计算机内端口的框架,而TCP则允许基于更复杂的连接的操作,包括用于数据包丢失和流量管理实现的恢复机制。可以在用户空间和内核空间之间复制数据包的有效负载。但是,两者都只是应用程序和网络之间的中间层的一部分。

用户空间中运行的应用程序构成了协议栈的顶层。

3. 路由

IP层处理计算机之间的路由。它保留两个数据结构:

  • 转发信息库(FIB)跟踪每个已知路由的所有详细信息,
  • 为当前使用的目的地提供更快的路由缓存。

4. 消息发送

s_tx.gif

4.1 应用层:写入套接字(socket)

  • 将数据写入套接字(应用程序)
  • 填写消息头
  • 检查基本错误(套接字是否绑定到端口?套接字可以发送消息吗?套接字有错没?)
  • 将消息头传递到适当的传输协议(INET套接字)

4.2 传输层:使用UDP创建数据包

  • 检查错误(数据太大吗?是UDP连接吗?)
  • 确保存在到目的地的路由(如果尚未建立路由,则调用 IP routing routines;如果没有路由返回失败)
  • 创建一个UDP header(用于数据包)
  • 调用IP build and transmit方法

4.2 传输层:使用TCP创建数据包

  • 检查连接(是否建立?它是打开?套接字进程正常吗?)
  • 检查数据并将其与部分数据包合并(可选)
  • 创建一个数据包缓冲区
  • 从用户空间复制payload
  • 将数据包添加到出站队列
  • 将当前TCP header构建到数据包中(包含 ACK,SYN 等)
  • 调用 IP transmit 方法

4.3 网络层:在IP中包装数据包

  • 创建一个数据包缓冲区(UDP可能不需要)
  • 查找到目的地的路由(如有必要-TCP)
  • 填写数据包 IP header
  • 从用户空间复制 header和 payload
  • 将数据包发送到目的地的路由所指向的设备(iface)

4.4 数据链路层:发送数据包

  • 将数据包放在设备输出队列中
  • 唤醒设备
  • 等待调度程序运行设备驱动程序
  • 测试设备
  • 发送 link header
  • 告诉总线通过媒介传输数据包

4.5 物理层

5. 消息接收

r_rx.gif

参考文末参考资料,这一部分还有两个步骤,但是我目前还不理解,先留空着。

5.1 物理层:接收报文

  • 唤醒接收设备(系统中断)
  • 设备测试介质
  • 接收链接头
  • 为数据包分配空间
  • 告诉总线将数据包放入缓冲区
  • 将数据包放入积压队列
  • 设置标志位,系统空闲时进行下一步
  • 结束中断

5.2 数据链路层

  • 调度程序发现有网络任务要执行
  • 发送先前积压的等待发送的数据包
  • 循环遍历积压队列中的所有数据包,并将数据包传递到其网络层
  • 刷新发送队列

5.3 网络层:解IP包

  • 检查数据包是否有错误(太短了?太长?版本无效?校验和错误?)
  • 如果必要,对数据包进行碎片整理
  • 获取数据包的路由(判断是本机或者需要转发)
  • 将数据包发送到目的地相关的进程(接收为本机TCP或UDP,或者重传到另一台主机)

5.4 在UDP中接受数据包

  • 检查UDP标头是否存在错误
  • 将目标与套接字匹配
  • 如果没有这样的套接字,请发送回错误消息
  • 将数据包放入适当的套接字接收队列
  • 唤醒等待该套接字的进程

5.4 在TCP中接受数据包

  • 检查顺序和标志;尽可能将数据包存储在正确的空间中
  • 如果收到过这个包,立即发送ACK并丢弃当前的数据包
  • 确定数据包属于哪个套接字
  • 将数据包放入适当的套接字接收队列
  • 唤醒并处理等待来自该套接字的数据

6. IP Forwarding

IP forwarding

7.2.1 接收数据包

  • 唤醒接收设备(系统中断)
  • 设备测试介质
  • 接收链接头
  • 为数据包分配空间
  • 告诉总线将数据包放入缓冲区
  • 将数据包放入积压队列
  • 设置标志,系统空闲时进行下一步
  • 结束中断

6.2 数据链路层

  • 调度程序发现有网络任务要执行
  • 发送先前积压的等待发送的数据包
  • 循环遍历积压队列中的所有数据包,并将数据包传递到其网络层
  • 刷新发送队列

6.3 网络层:解IP包

  • 检查数据包是否有错误-太短了?太长?版本无效?校验和错误?
  • 必要时对数据包进行碎片整理
  • 获取数据包的路由(判断是本机或者需要转发)
  • 将数据包发送到其目的地处理例程(接收为本机TCP或UDP,或者重传到另一台主机,在这种情况下,将数据包重新传输到另一个主机)

6.4 网络层:转发IP报文

  • 检查TTL字段(并将其减少)
  • 检查数据包是否有错误的路由
  • 如有错误,发送ICMP包给发件人
  • 将数据包复制到新缓冲区中并释放旧缓冲区
  • 设置 IP options
  • 如果对于新目的地而言太大,则打包
  • 将数据包发送到目的地的路由所指向的设备(iface)。

6.5 发送数据包

  • 将数据包放在设备输出队列中
  • 唤醒设备
  • 等待调度程序运行设备驱动程序
  • 设备测试介质
  • 发送 link header
  • 告诉总线通过媒介传输数据包

7. IP 路由

Linux维护三组路由数据:

  • 一组用于直接连接到主机的计算机(例如,通过LAN),邻居表
  • 两组用于间接连接的计算机(通过IP网络)。
    • 具有指向每个可能地址的方向的通用转发信息库(FIB)
    • 具有较小频率(且速度更快)的路由缓存,其中包含经常使用的路由上的数据。

r_overview.gif

邻居表

邻居表包含物理连接到主机的计算机的地址信息(因此称为邻居)。

它包括有关哪个设备连接到哪个邻居以及在交换数据时使用什么协议的信息。Linux使用地址解析协议(ARP)来维护和更新该表。它是动态的,因为需要时会添加条目,但如果在一定时间内不再次使用则最终会消失(但是,管理员可以将条目设置为永久条目)

显示邻居:ip neighbour show
增加ipv4邻居: arp -s 192.168.0.2 00:01:02:03:04:05
删除ipv4邻居: arp -d 192.168.0.2
FIB 转发信息库

r_fib_gen.gif

转发信息库(FIB)是内核中最重要的路由结构,包含通过网络掩码到达任何有效 IP 地址所需的路由信息。

IP 层使用数据包的目标地址进入表,并将其与最特定的网络掩码进行比较以查看它们是否匹配。如果没有,则IP转到下一个最通用的网络掩码,然后再次比较两者。当它最终找到一个匹配项时,IP会将到远程主机的方向复制到路由缓存中并按其方式发送数据包。

路由缓存

路由高速缓存是Linux查找路由的最快方法。它会将当前正在使用或最近使用过的每条路由保留在哈希表中。当IP需要路由时,它将转到适当的哈希存储桶并搜索缓存的路由链,直到找到匹配项,然后沿着该路径发送数据包。

路由按顺序链接,最先使用,并且有计时器和计数器,这些计时器和计数器在不再使用它们时将其从表中删除。

更新路由信息

Linux 仅在必要时更新路由信息,但是表以不同的方式更改。路由缓存是最易变的,而 FIB通常根本不会改变。

五、内核态\用户态\ 进程上下文\中断上下文

参考资料:Linux 网络协议栈之内核锁

这里写图片描述

1. 为什么有这些概念?

在现在操作系统中,内核功能模块运行在内核空间,而应用程序运行在用户空间。

现代的CPU都具有不同的操作模式,代表不同的级别,不同的级别具有不同的功能,其所拥有的资源也不同;在较低的级别中将禁止使用某些处理器的资源。

Linux系统设计时利用了这种硬件特性,使用了两个级别,最高级别和最低级别:

  • 内核运行在最高级别(内核态),这个级别几乎可以使用处理器的所有资源,
  • 而应用程序运行在较低级别(用户态),在这个级别的用户不能对硬件进行直接访问以及对内存的非授权访问。

内核态和用户态有自己的内存映射,即自己的地址空间。

当工作在用户态的进程想访问某些内核才能访问的资源时,必须通过系统调用或者中断切换到内核态,由内核代替其执行。

进程上下文和中断上下文就是完成这两种状态切换所进行的操作总称。我将其理解为保存用户空间状态是上文,切换后在内核态执行的程序是下文。

系统调用是对系统内置子例程的调用,而中断是一个事件,它使处理器暂时保留当前执行。

一个主要区别是系统调用是同步的,而中断不是同步的。

这意味着系统调用在固定时间(通常由程序员确定)发生,但是由于意外事件(例如用户在键盘上按下键)而可能在任何时间发生中断。

2. 内核态和用户态

内核态:在内核空间执行,通常是驱动程序,中断相关程序,内核调度程序,内存管理及其操作程序。

用户态:用户程序运行空间。

这里写图片描述

3. 进程上下文与中断上下文

  1. 进程上下文:
    • 进程上文:其是指进程由用户态切换到内核态是需要保存用户态时cpu寄存器中的值,进程状态以及堆栈上的内容,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
    • 进程下文:其是指切换到 内核态后执行的程序,即进程运行在内核空间的部分。
  2. 中断上下文:
    • 中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境。
    • 中断下文:执行在内核空间的中断服务程序。

运行于进程上下文的内核代码是可抢占的,但中断上下文则会一直运行至结束,不会被抢占。

所以使用中断时要特别注意,中断上下文占用CPU时间太长会严重影响系统功能。中断处理程序的任务尽可能放在中断下半部执行。

4. 使用场景

  1. 进程上下文主要是异常处理程序和内核线程。内核之所以进入进程上下文是因为进程自身的一些工作需要在内核中做。例如,系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。

  2. 中断上下文是由于硬件发生中断时会触发中断信号请求,请求系统处理中断,执行中断服务子程序。

六、Netfilter

参考资料: Linux 网络协议栈开发(七)—— Netfilter 概述及其hook点

Netfilter/IPTables 是Linux2.4.x之后新一代的Linux防火墙机制,是linux内核的一个子系统。Netfilter采用模块化设计,具有良好的可扩充性。其重要工具模块IPTables从用户态的iptables连接到内核态的Netfilter的架构中,Netfilter与IP协议栈是无缝契合的,并允许使用者对数据报进行过滤、地址转换、处理等操作。

Netfilter在内核中位置如下图所示:

1347070184_4903

在每个关键点上,有很多已经按照优先级预先注册了的回调函数(称为“钩子函数”)埋伏在这些关键点,形成了一条链。对于每个到来的数据包会依次被回调函数“处理再视情况是将其放行,丢弃还是怎么滴。每个钩子函数最后必须向Netfilter框架返回下列几个值其中之一:

  1. NF_ACCEPT 继续正常传输数据报。
  2. NF_DROP丢弃该数据报,不再传输。
  3. NF_STOLEN 模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。
  4. NF_QUEUE 对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)
  5. NF_REPEAT 再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。

数据报经过各个HOOK的流程如下:

1347070206_8627

  • 数据报从进入系统,进行IP校验
  • 经过第一个HOOK函数NF_IP_PRE_ROUTING进行处理;
  • 进入路由判决,其决定该数据报是需要转发还是发给本机的;
    • 若该数据报是发被本机的,则该数据经过HOOK函数NF_IP_LOCAL_IN处理以后然后传递给上层协议;
    • 若该数据报应该被转发则它被NF_IP_FORWARD处理;
      • 经过转发的数据报经过最后一个HOOK函数NF_IP_POST_ROUTING处理以后,再传输到网络上。
  • 本地产生的数据经过HOOK函数NF_IP_LOCAL_OUT 处理
  • 进行路由判决处理
  • 经过NF_IP_POST_ROUTING处理后发送出去。

Linux内核中Netfilter框架的HOOK机制可以概括如下: img

在数据包流经内核协议栈的整个过程中,在一些已预定义的关键点上PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING 会根据数据包的协议簇 PF_INET 到这些关键点去查找是否注册有钩子函数。

  • 如果没有,则直接返回 okfn 函数指针所指向的函数继续走协议栈;
  • 如果有,则调用 nf_hook_slow 函数,从而进入到Netfilter框架中去进一步调用已注册在该过滤点下的钩子函数,再根据其返回值来确定是否继续执行由函数指针okfn所指向的函数。

参考资料


linux 离线安装 docker

在这个网址下有安装包: https://download.docker.com/

对于debian,访问https://download.docker.com/linux/debian/dists/, 选择 Debian 版本, 然后进入 pool/stable/ 文件夹📁, 选择对应架构(amd64、armhf或 arm64)后下载想安装版本的.deb文件。

例如我目前是x86的 Debian 9,那我最终下载的地址是: https://download.docker.com/linux/debian/dists/stretch/pool/stable/amd64/,注意要把三个相关deb文件都下载下来:

  • containerd.io_1.2.13-2_amd64.deb
  • docker-ce_19.03.9~3-0~debian-stretch_amd64.deb
  • docker-ce-cli_19.03.9~3-0~debian-stretch_amd64.deb

然后运行命令:

dpkg -i *.deb

便完成安装。