Keep learning, keep living...

0%

Linux内核中conntrack模块使用哈希表来存储连接跟踪条目,当哈希表条目达到上限时,系统会将新分配conntrack条目的数据包DROP掉,从而导致网络受到影响。此时,日志中会记录:

1
nf_conntrack: table full, dropping packet

哈希表条目上限由参数net.netfilter.nf_conntrack_max设置。

网上文章对这个问题的解决方法往往是调大该参数。但在涉及多个network namespace的场景下,不能简单的这样做,还是要根据自身场景分析清楚具体原因。

根据CentOS7 3.10.0-957版本内核源码,实际上每个network namespaceconntrack哈希表是独立的。在表示network namespace的结构体net中的成员ct表示conntrack相关信息:

1
2
3
4
5
struct net {
...
struct netns_ct ct;
...
}

netns_ct结构中保存有独立的哈希表相关信息:

1
2
3
4
5
6
7
8
9
struct netns_ct {
atomic_t count;
...
unsigned int htable_size;
RH_KABI_DEPRECATE(seqcount_t, generation)
struct kmem_cache *nf_conntrack_cachep;
struct hlist_nulls_head *hash;
...
}
阅读全文 »

Linux内核提供了sysctl机制用于动态配置内核及内核模块的参数, 每个参数对应/proc/sys/下的一个文件,可以通过sysctl命令或直接操作/proc/sys/下的文件对参数进行读写。比如,net.ipv4.ip_forward对应文件/proc/sys/net/ipv4/ip_forwardsysctl命令实际是是对/proc/sys/操作的封装。

如果需要将sysctl参数持久化,可以将参数写入文件/etc/sysctl.conf文件中,这样参数在系统重启后依然生效。这是如何实现的呢?实际是由systemd-sysctl(或其他类似功能的服务)在系统完成内核模块的加载后,再来加载/etc/sysctl.conf里的参数。

如果需要立即生效/etc/sysctl.conf中的参数,可以执行sysctl -p。但如果此时内核模块未并加载,由于/proc/sys/目录下并不存在对应的参数文件,因而执行会失败。尽管这种场景下sysctl -p执行失败,但通过modprobe命令加载内核模块完成后,查看对应的sysctl参数,却发现sysctl参数已经生效。那这种场景下是如何令/etc/sysctl.conf中的参数生效的呢?

阅读全文 »

近期遇到一个C++程序退出的问题,经过调查发现,程序是由于接收到SIGPIPE信号而退出。该程序是多线程程序,使用libcurl进行HTTPS访问,同时设置了CURLOPT_NOSIGNAL选项,但没有自己处理SIGPIPE信号。在一些情况下,连接已经处理关闭状态,但应用程序不知道,依然向连接发送数据,就会导致SIGPIPE信号产生,进而导致程序退出。修复方法比较简单,只要应用程序设置SIGPIPE信号的处理程序即可。

上述问题要发生的一个前提是CURLOPT_NOSIGNAL选项需要被设置,什么情况下需要设置它呢?libcurl的作者的博文上写到过这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
12. Understanding CURLOPT_NOSIGNAL
Signals is a Unix concept where an asynchronous notification is sent to a process or to a specific thread within the same process in order to notify it of an event that occurred.

What does libcurl use signals for?
When using the synchronous name resolver, libcurl uses alarm() to abort slow name resolves (if a timeout is set), which ultimately sends a SIGALARM to the process and is caught by libcurl

By default, libcurl installs its own sighandler while running, and restores the original one again on return – for SIGALARM and SIGPIPE.

Closing TLS (with OpenSSL etc) can trigger a SIGPIPE if the connection is dead.

Unless CURLOPT_NOSIGNAL is set! (default)

What does CURLOPT_NOSIGNAL do?
It prevents libcurl from triggering signals

When disabled, it prevents libcurl from installing its own sighandler and…

Generated signals must then be handled by the libcurl-using application itself
阅读全文 »

业务场景中,需要创建指定的network namespace, 并且内核模块中的netfilter逻辑只应生效在该network namespace中。这就需要我们创建network namespace之后,将指定的namespace传递给内核模块。

用户态创建network namespace可以使用ip命令指定名称,如:

1
ip netns add ns1

但实际上在内核中,network namespace并不具备名称信息,名称信息只存在于用户态。可以参考man ip-netns:

1
2
3
4
By convention a named network namespace is an object at /var/run/netns/NAME that can be opened. The file descriptor resulting
from opening /var/run/netns/NAME refers to the specified network namespace. Holding that file descriptor open keeps the network
namespace alive. The file descriptor can be used with the setns(2) system call to change the network namespace associated with a
task.
阅读全文 »

最近发现kolla安装的openstack(Ocata版本)环境中,某虚拟网络上的虚拟机对外访问异常。经过调查,发现虚拟机外发数据包经过安全组的网桥后源地址被修改为宿主机的IP

简化的网络拓扑如图:

阅读全文 »

我们的系统使用ansible执行部署过程,然而部署的目标操作系统和CPU架构环境多种多样,存在大量适配工作。之前调研过是否有Golang开发的ansible替代物,但没有找到合适的方案。这种到处运行的场景很适合使用容器,但如果使用docker,  在不同操作系统上也需要准备不同的docker安装包,依然需要适配,因而也不是很满足。今天想到runc本身就是Golang开发的纯静态二进制,非常适合到处分发,因而尝试调研直接使用runc来做为运行时运行ansible容器。

在容器领域,OCI:Open Container Initiative制定了两个规范,镜像规范: image-spec和运行时规范:runtime-spec。镜像规范规定了镜像文件的格式,而运行时规范规定了如何根据配置运行容器。它们之间通过filesystem bundle联系在一起,镜像可以通过工具转换成filesystem bundle, 运行时根据filesystem bundle运行容器进程。

阅读全文 »

我们的主机网络防护是基于netfilter实现。最近遇到需要对访问主机上Docker容器的流量进行防护。几年前其实就处理过这个场景,时间久远忘记了,重新梳理一下记录下来。

我们的主机网络防护模块的hooknumLOCAL_INPOST_ROUTING, 并且hook的优先级为NF_IP_PRI_FIRST, 也就是在hooknum位置最先运行。

从宿主机外部访问主机上容器的场景

之前的文章<<从外部访问Docker桥接网络容器路径分析>>分析了从外部访问Docker桥接网络的网络路径。

  • 从外部到容器的数据包会流经PRE_ROUTING, FORWARDPOST_ROUTING阶段,在PRE_ROUTING阶段会进行DNAT, 将目的IP/PORT, 修改为容器的IP/PORT。 
  • 从容器到外部的数据包会流经PRE_ROUTING, FORWARDPOST_ROUTING阶段,在POST_ROUTING阶段会进行SNAT, 将源IP/PORT修改为外部宿主机的IP/PORT

从网络路径来看,在POST_ROUTING阶段数据包上的地址是容器本身的地址, 因而我们可以简单的将容器IP/PORT端口在规则中配置,就可以实现对于访问容器内部流量的防护。

阅读全文 »

我们的网络防护功能是基于netfilter框架实现,依赖于nf_conntrack模块用于跟踪网络连接。在网络连接的维度,我们需要存储一些业务相关的数据。最简单直接的方法就是将这些内容存储在nf_conn结构中。

查看nf_conn结构,发现nf_conn结构中有一个指针ext可以支持扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct nf_conn {
/* Usage count in here is 1 for hash table, 1 per skb,
* plus 1 for any connection(s) we are `master' for
*
* Hint, SKB address this struct and refcnt via skb->_nfct and
* helpers nf_conntrack_get() and nf_conntrack_put().
* Helper nf_ct_put() equals nf_conntrack_put() by dec refcnt,
* beware nf_ct_get() is different and don't inc refcnt.
*/
struct nf_conntrack ct_general;

......

/* Extensions */
struct nf_ct_ext *ext;

/* Storage reserved for other modules, must be the last member */
union nf_conntrack_proto proto;
};

nf_ct_ext结构:

1
2
3
4
5
6
7
/* Extensions: optional stuff which isn't permanently in struct. */
struct nf_ct_ext {
struct rcu_head rcu;
u8 offset[NF_CT_EXT_NUM];
u8 len;
char data[0];
};
阅读全文 »

ctrcrictl都是Kubernetes环境中管理容器的命令行工具。但它们的目的和使用方法有所不同。

crictl是基于KubernetesCRI: Container Runtime Interface接口规范来管理容器, ctr是containerd自带的容器管理工具, 本身和Kubernetes无关。

Kubernetes使用crictl来管理任意兼容CRI接口的容器运行时。

containerd相比于docker,增加了namespace的概念,每个imagecontainer都在各自的namespace下可见。目前kubernetes使用k8s.io作为namespace名称。

阅读全文 »

LSM: Linux Security Modules是内核中对象访问控制机制。最早的基于访问主体(subject)的身份或者所属组(User,Group,Other)的访问控制机制被称为DAC: Discretionary Access Control, 在许多安全性要求较高的场景下不能适用。于是Linux内核中实现了MAC: Mandatory Access Control机制,来表达访问主体(Subject)是否有权限对访问客体(Object)进行相应操作(Operation), 这个实现就是LSM框架。

在具体实现上,内核会在相应对象访问前进行相应操作的检测,以系统调用为例来看, 如图:

来自链接

阅读全文 »