docker,containerd,runc,docker-shim 之间的关系

在看系统进程的时候,发现了这样的进程关系:

systemd 里调用了 

- containerd -> containerd-shim
- dockerd -> docker-proxy

引起了我的兴趣。

ps: 另外上边只是在 docker 19.03 上发现了这样的调用关系,在 docker 17.06 上调用的关系实际上是这样:

-dockerd --registry-mirror=http:/xxx
  | -docker-containe -l unix:///xxx
      | -docker-containe   
  | -docker-proxy -proto xxx

在 19.03 版本中,docker相关的可执行文件如下:

image-20201011113355555

其中以docker开头的,docker, dockerd, docker-init, docker-proxy 是 docker 公司专属的,并非标准。

因为 docker 一直在开发中,网上很多资料都比较陈旧,不过八九不离十,无非是几个组件变了个名字:

  • docker,是一个客户端工具,用来把用户的请求发送给 docker daemon(dockerd)。
  • dockerd, docker daemon,一般也会被称为 docker engine。dockerd 启动时会启动 containerd 子进程。
  • Containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性,几乎囊括了单机运行一个容器运行时所需要的一切:执行,分发,监控,网络,构建,日志等。主要作用是:
    • 管理容器的生命周期(从创建容器到销毁容器)
    • 拉取/推送容器镜像
    • 存储管理(管理镜像及容器数据的存储)
    • 调用 runC 运行容器(与 runC 等容器运行时交互)
    • 管理容器网络接口及网络
  • ctr 是 containerd 的 cli。
  • 为了能够支持多种 OCI Runtime,containerd 内部使用 containerd-shim,每启动一个容器都会创建一个新的 containerd-shim 进程,指定容器 ID,Bundle 目录,运行时的二进制(比如 runc)。
  • RunC 是一个轻量级的工具,用来运行容器的,我们可以不用通过 docker 引擎,直接运行容器。事实上,runC 是标准化的产物,它根据 OCI 标准来创建和运行容器。

调用链主要就是下面这张图:

img

docker历史

至于容器架构为什么做的这么复杂,个人认为是技术、政治和历史等因素共同的结果。这里简单记录一下docker的历史:

  1. 自从 2013 年 docker 发布。
  2. 为了能够降低项目维护的成本,内部代码能够回馈社区,docker 公司提出了 “基础设施管道宣言” (Infrastructure Plumbing Manifesto),自行拆分自己项目中的管道代码并形成一个个新的开源项目:libcontainer, libnetwork, notary, hyperkit。
  3. 2015年 OCI 项目启动(开放容器标准),OCI 的技术委员会成员包括 Red Hat,Microsoft,Docker,Cruise,IBM,Google,Red Hat 和 SUSE,其中 Docker 公司有两名成员,且其中的一位是现任主席,具体的细节可以查看 OCI Technical Oversight Board
  4. docker 公司将 libcontainer 的实现移动到 runC 并捐赠给了 OCI。runC 包括了所有之前 docker 所使用的容器相关的与系统特性的代码。
  5. 2016 年,docker 开源并将 containerd 捐赠给了 CNCF。

与k8s相关

在容器标准的大战中,docker公司围绕docker swarm推出了CNM,Google等以屠龙者的姿态围绕 k8s 推出了CNI,目前来看,k8s已经奠定了在 PaaS 事实的地位。

CRI 是一套通过 protocol buffers 定义的 API,如下图:

img

kubelet 实现了 client 端,CRI shim 实现 server 端。只要实现CRI对应的接口,就能接入 k8s 作为 Container Runtime。

  1. k8s 1.5 中自己实现了 docker CRI shim,此时启动容器的流程如下:

    img

  2. 从 containerd 1.0 开始,为了能够减少一层调用的开销(废掉docker,也就是把上边的docker cri shim和docker踢掉),containerd 开发了一个新的 daemon,叫做 CRI-Containerd,直接与 containerd 通信,从而取代了 dockershim:

    img

  3. 但是这仍然多了一个独立的 daemon,从 containerd 1.1 开始,社区选择在 containerd 中直接内建 CRI plugin,通过方法调用来进行交互,从而减少一层 gRPC 的开销,最终的容器启动流程如下:

    img

    最终的结果是 k8s 的 Pod 启动延迟得到了降低,CPU 和内存占用率都有不同程度的降低。

  4. 但是这还不是终点,为了能够直接对接 OCI 的 runtime 而不是 containerd,社区孵化了 CRI-O 并加入了 CNCF。CRI-O 的目标是让 kubelet 与运行时直接对接,减少任何不必要的中间层开销。CRI-O 运行时可以替换为任意 OCI 兼容的 Runtime,镜像管理,存储管理和网络均使用标准化的实现。

@xuxinkun 的文章中有个图将他们之间的关系描绘的很清楚:

img

以下更新于2021年4月9日

关于Mesos

mesos于前天发起了关闭

参考资料


善用 nsenter 命令,更好地 debug 容器网络

https://staight.github.io/2019/09/23/nsenter%E5%91%BD%E4%BB%A4%E7%AE%80%E4%BB%8B/

在做容器 debug 的时候,经常出现容器里没有某个命令的尴尬局面。比如ping,telnet等常用命令。如何才能快速debug网络问题,又不需要安装软件呢?

那必须是nsenter命令了。 nsenter是一个可以在指定进程的命令空间下运行指定程序的命令,它最典型的用途就是进入容器的网络命令空间。具体可以参考官方文档:https://github.com/jpetazzo/nsenter.

nsenter 和 docker exec 之间的区别在于,nsenter不进入cgroup,因此规避了资源限制。

此外,nsenter也可以进入mnt, uts, ipc, pid, user命令空间,以及指定根目录和工作目录。

一、安装

我所在的系统默认装有 nsenter 命令。如果没有,按照官方文档如下安装:

$ wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz
$ tar -xzvf util-linux-2.24.tar.gz
$ cd util-linux-2.24/
$ ./configure --without-ncurses
$ make nsenter
$ sudo cp nsenter /usr/local/bin

然后可以查看nsenter自带的帮助查看命令使用方式:

nsenter -h

Usage:
nsenter [options] [<program> [<argument>...]]
 
Run a program with namespaces of other processes.
 
Options:
-a, --all enter all namespaces
-t, --target <pid> target process to get namespaces from
-m, --mount[=<file>] enter mount namespace
-u, --uts[=<file>] enter UTS namespace (hostname etc)
-i, --ipc[=<file>] enter System V IPC namespace
-n, --net[=<file>] enter network namespace
-p, --pid[=<file>] enter pid namespace
-C, --cgroup[=<file>] enter cgroup namespace
-U, --user[=<file>] enter user namespace
-S, --setuid <uid> set uid in entered namespace
-G, --setgid <gid> set gid in entered namespace
--preserve-credentials do not touch uids or gids
-r, --root[=<dir>] set the root directory
-w, --wd[=<dir>] set the working directory
-F, --no-fork do not fork before exec'ing <program>
-Z, --follow-context set SELinux context according to --target PID

二、 用法

首先,找出容器的PID:

PID=$(docker inspect --format {{.State.Pid} <容器名或id>)

然后:

nsenter --target $PID --mount --uts --ipc --net --pid -n /bin/sh

即可进入容器中,这个命令和 docker exec 命令效果几乎等同。当然也可以合并成一个命令。

比如进入 nginx 容器内,因为原本是没有telnet命令的,为了使用telnet命令,上面命令中的 –mount 参数不能带上,否则进去之后就找不到这个命令啦。最终使用的命令如下:

nsenter -t $(docker inspect --format '{{.State.Pid}}' nginx) -n /bin/bash

实际在这里我运行了宿主机的 bash 程序,获得了nginx容器的pid,然后进入该pid进程的network namespace,此时可以统计一下容器内有哪些网络端口:

image-20201010232030433

三、原理说明

以下参考:https://staight.github.io/2019/09/23/nsenter命令简介/

namespace

namespace是Linux中一些进程的属性的作用域,使用命名空间,可以隔离不同的进程。

Linux在不断的添加命名空间,目前有:

  • mount:挂载命名空间,使进程有一个独立的挂载文件系统,始于Linux 2.4.19
  • ipc:ipc命名空间,使进程有一个独立的ipc,包括消息队列,共享内存和信号量,始于Linux 2.6.19
  • uts:uts命名空间,使进程有一个独立的hostname和domainname,始于Linux 2.6.19
  • net:network命令空间,使进程有一个独立的网络栈,始于Linux 2.6.24
  • pid:pid命名空间,使进程有一个独立的pid空间,始于Linux 2.6.24
  • user:user命名空间,是进程有一个独立的user空间,始于Linux 2.6.23,结束于Linux 3.8
  • cgroup:cgroup命名空间,使进程有一个独立的cgroup控制组,始于Linux 4.6

Linux的每个进程都具有命名空间,可以在/proc/PID/ns目录中看到命名空间的文件描述符。

[root@staight ns]# pwd
/proc/1/ns
[root@staight ns]# ll
total 0
lrwxrwxrwx 1 root root 0 Sep 23 19:53 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Sep 23 19:53 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Sep 23 19:53 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Sep 23 19:53 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Sep 23 19:53 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Sep 23 19:53 uts -> uts:[4026531838]

clone

clone是Linux的系统调用函数,用于创建一个新的进程。

clone和fork比较类似,但更为精细化,比如说使用clone创建出的子进程可以共享父进程的虚拟地址空间,文件描述符表,信号处理表等等。不过这里要强调的是,clone函数还能为新进程指定命名空间。

clone的语法:

 #define _GNU_SOURCE
#include <sched.h>

int clone(int (*fn)(void *), void *child_stack,
        int flags, void *arg, ...
        /* pid_t *ptid, void *newtls, pid_t *ctid */ );

其中flags即可指定命名空间,包括:

  • CLONE_NEWCGROUP:cgroup
  • CLONE_NEWIPC:ipc
  • CLONE_NEWNET:net
  • CLONE_NEWNS:mount
  • CLONE_NEWPID:pid
  • CLONE_NEWUSER:user
  • CLONE_NEWUTS:uts

使用示例:

pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);

setns

clone用于创建新的命令空间,而setns则用来让当前线程(单线程即进程)加入一个命名空间。

语法:

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sched.h>

int setns(int fd, int nstype);

fd参数是一个指向一个命名空间的文件描述符,位于/proc/PID/ns/目录。

nstype指定了允许进入的命名空间,一般可设置为0,表示允许进入所有命名空间。

因此,往往该函数的用法为:

  1. 调用setns函数:指定该线程的命名空间。
  2. 调用execvp函数:执行指定路径的程序,创建子进程并替换父进程。

这样,就可以指定命名空间运行新的程序了。

代码示例:

#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)

int
main(int argc, char *argv[])
{
    int fd;

    if (argc < 3) {
        fprintf(stderr, "%s /proc/PID/ns/FILE cmd args...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    fd = open(argv[1], O_RDONLY); /* Get file descriptor for namespace */
    if (fd == -1)
        errExit("open");

    if (setns(fd, 0) == -1)       /* Join that namespace */
        errExit("setns");

    execvp(argv[2], &argv[2]);    /* Execute a command in namespace */
    errExit("execvp");
}

使用示例:

./ns_exec /proc/3550/ns/uts /bin/bash

nsenter

那么,最后就是nsenter了,nsenter相当于在setns的示例程序之上做了一层封装,使我们无需指定命名空间的文件描述符,而是指定进程号即可。

指定进程号 PID 以及需要进入的命名空间后,nsenter会帮我们找到对应的命名空间文件描述符/proc/PID/ns/FD,然后使用该命名空间运行新的程序。

参考资料


创建自定义 iptables 链

来自:How to create iptables firewall using custom chains

使用自定义链创建iptables防火墙,该自定义链将用于控制传入和传出流量。

创建iptables防火墙,该防火墙将允许已建立的连接,给定源地址的传入ssh,传出icmpntpdnssshhttphttps

# Flush rules and delete custom chains
iptables -F
iptables -X

# Define chain to allow particular source addresses
iptables -N chain-incoming-ssh
iptables -A chain-incoming-ssh -s 192.168.1.148 -j ACCEPT
iptables -A chain-incoming-ssh -s 192.168.1.149 -j ACCEPT
iptables -A chain-incoming-ssh -j DROP

# Define chain to allow particular services
iptables -N chain-outgoing-services
iptables -A chain-outgoing-services -p tcp --dport 53  -j ACCEPT
iptables -A chain-outgoing-services -p udp --dport 53  -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 123 -j ACCEPT
iptables -A chain-outgoing-services -p udp --dport 123 -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 80  -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 443 -j ACCEPT
iptables -A chain-outgoing-services -p tcp --dport 22  -j ACCEPT
iptables -A chain-outgoing-services -p icmp            -j ACCEPT
iptables -A chain-outgoing-services -j DROP

# Define chain to allow established connections
iptables -N chain-states
iptables -A chain-states -p tcp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -p udp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -p icmp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A chain-states -j RETURN

# Drop invalid packets
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Accept everything on loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept incoming/outgoing packets for established connections
iptables -A INPUT  -j chain-states
iptables -A OUTPUT -j chain-states

# Accept incoming ICMP
iptables -A INPUT -p icmp -j ACCEPT

# Accept incoming SSH
iptables -A INPUT -p tcp --dport 22 -j chain-incoming-ssh

# Accept outgoing 
iptables -A OUTPUT -j chain-outgoing-services

## Drop everything else
iptables -P INPUT   DROP
iptables -P FORWARD DROP
iptables -P OUTPUT  DROP

列出所有防火墙规则,以验证是否按需应用了执行的命令。

$ sudo iptables -L -v -n

Chain INPUT (policy DROP 2 packets, 92 bytes)
 pkts bytes target              prot opt in     out     source               destination         
    0     0 DROP                all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate INVALID
    1    29 ACCEPT              all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
  190 15639 chain-states        all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT              icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    60 chain-incoming-ssh  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target        prot opt in     out     source               destination         
    1    29 ACCEPT        all  --  *      lo      0.0.0.0/0            0.0.0.0/0           
  150 39893 chain-states  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
    1    62 chain-outgoing-services  all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-incoming-ssh (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       192.168.1.148        0.0.0.0/0           
    1    60 ACCEPT     all  --  *      *       192.168.1.149        0.0.0.0/0           
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-outgoing-services (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:53
    1    62 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:53
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:123
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:123
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           

Chain chain-states (2 references)
 pkts bytes target     prot opt in     out     source               destination         
  335 55240 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    1    78 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    4   214 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           

我已经使用了RETURNtarget,chain-states因此数据包将继续通过防火墙。

编辑地址清单

在源地址列表中列出防火墙规则。

$ sudo iptables -n --list chain-incoming-ssh --line-numbers

Chain chain-incoming-ssh (1 references)
num  target     prot opt source               destination         
1    ACCEPT     all  --  192.168.1.148        0.0.0.0/0           
2    ACCEPT     all  --  192.168.1.149        0.0.0.0/0           
3    DROP       all  --  0.0.0.0/0            0.0.0.0/0           

删除第一个条目。

$ sudo iptables -D chain-incoming-ssh  1

在链的开头添加新规则。

$ sudo iptables -I chain-incoming-ssh 1 -s 192.168.1.140 -j ACCEPT 

在上一个条目之前添加一个新规则。

$ sudo iptables -I chain-incoming-ssh 3 -s 192.168.1.150 -j ACCEPT 

在源地址列表中列出防火墙规则。

$ sudo iptables -n --list chain-incoming-ssh --line-numbers
Chain chain-incoming-ssh (1 references)
num  target     prot opt source               destination         
1    ACCEPT     all  --  192.168.1.140        0.0.0.0/0           
2    ACCEPT     all  --  192.168.1.149        0.0.0.0/0           
3    ACCEPT     all  --  192.168.1.150        0.0.0.0/0           
4    DROP       all  --  0.0.0.0/0            0.0.0.0/0           

补充说明

您可以使用以下命令检查特定的链条。

$ sudo iptables -n --list chain-outgoing-services

Chain chain-outgoing-services (1 references)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:123
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:123
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:443
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
DROP       all  --  0.0.0.0/0            0.0.0.0/0  

您还可以在shell脚本中使用它根据其存在来执行不同的操作。

iptables --list chain-outgoing-services &>/dev/null
if [ "$?" -eq "0" ]; then
  iptables -F chain-outgoing-services
else
  iptables -N chain-outgoing-services
fi

iptables/ipset 备忘 - vqiu.cn

转自:https://vqiu.cn/ipset-note/,文章有点机翻的痕迹,不过不影响理解。

简介

ipset是iptables的扩展,允许创建一次匹配整个“地址”集的防火墙规则。 与通过线性存储和遍历的普通iptables链不同,IP集存储在索引数据结构中,即使在处理大型集时,查找也非常高效。

ipset只是iptables的扩展,所以本篇文章不仅是ipset的使用笔记,同时也是iptables的使用笔记。

安装

// Debian
apt-get install ipset

//RHEL
yum -y install ipset
12345

快速入门

需求,禁1.1.1.12.2.2.2IP访问服务器,使用以下iptables 命令可实现:

iptables -A INPUT -s 1.1.1.1 -j DROP
iptables -A INPUT -s 2.2.2.2 -j DROP

同时,我们如果使用ipset也可以实现

ip -N myset iphash
ipset -A myset 1.1.1.1
ipset -A myset 2.2.2.2
iptables -A INPUT -m set --set myset src -j DROP

上面的ipset命令创建了一个带有两个地址(1.1.1.1和2.2.2.2)的新集合( ipset类型为iphash )。

然后iptables命令引用带有匹配规范的-m set --set myset src ,这意味着“匹配源头与之匹配(即包含在内)名为myset的集合的数据包”

标志src表示匹配“源”。 标志dst将匹配“destination”,并且标志src,dst将在源和目标上匹配。

在上面的第二个版本中,只需要一个iptables命令,无论该组中包含多少额外的IP地址。 尽管此示例仅使用两个地址,但您可以轻松定义1,000个地址,而基于ipset的配置仍然只需要一个iptables规则,而前一种方法,如果没有ipset的优势,则需要1,000个iptables规则。

应用场景

ipset优点

除了性能提升之外,ipset还允许在许多场景中进行更直接的配置。

如果要定义一个防火墙条件,该条件与1.1.1.1或2.2.2.2中的所有数据包相匹配,并在mychain中继续处理,请注意以下内容不起作用:

iptables -A INPUT -s ! 1.1.1.1 -g mychain
iptables -A INPUT -s ! 2.2.2.2 -g mychain

如果一个数据包来自1.1.1.1,它将与第一个规则不匹配(因为源地址 1.1.1.1),但它将匹配第二个规则(因为源地址不是 2.2.2.2)。 如果数据包来自2.2.2.2,它将匹配第一个规则(因为源地址不是 1.1.1.1)。 规则相互抵消,所有数据包都匹配,包括1.1.1.1和2.2.2.2。

虽然还有其他方法可以正确构建规则并在没有ipset的情况下实现所需的结果,但没有一种方法可以直观或直接:

ipset -N myset iphash
ipset -A myset 1.1.1.1
ipset -A myset 2.2.2.2
iptables -A INPUT -m set ! --set myset src -g mychain

在上面,如果一个数据包来自1.1.1.1,它将与规则不匹配(因为源地址1.1.1.1与设置的myset匹配)。 如果数据包来自2.2.2.2,则它与规则不匹配(因为源地址2.2.2.2与设置的myset匹配)。

虽然这是一个简单的例子,但它说明了在单个规则中拟合完整条件的基本好处。 在许多方面,单独的iptables规则彼此是相互独立的,并且将单独的规则合并为单个逻辑条件并不总是直截了当,直观或最优,特别是当涉及混合正常和反向测试时。 ipset只是让这些情况下的生活更轻松。

ipset的另一个好处是可以独立于活动的iptables规则来操作集合。 添加/更改/删除条目是一件小事,因为信息简单且顺序无关紧要。 编辑平面列表不需要经过深思熟虑。 另一方面,在iptables中,除了每个规则是一个明显更复杂的对象这一事实之外,规则的顺序至关重要,因此就地规则修改更加繁重且可能容易出错。

配合NAT使用

出站NAT(SNAT或IP伪装)允许专用LAN内的主机访问Internet。 适当的iptables NAT规则匹配源自专用LAN的Internet绑定数据包,并将源地址替换为网关本身的地址(使网关看起来是源主机并隐藏其后面的私有“真实”主机)。

NAT会自动跟踪活动连接,以便将返回的数据包转发回正确的内部主机(通过将目标地址从网关地址更改回原始内部主机的地址)。

以下是执行此操作的简单出站NAT规则的示例,其中10.0.0.0/24是内部LAN:

iptables -t nat -A POSTROUTING \ -o eth0 -j MASQUERADE 

事实证明,这是ipset的另一个伟大的应用程序。 假设除了充当本地专用LAN(10.0.0.0/24)的Internet网关之外,您的盒子还可以直接路由到其他四个专用网络(10.30.30.0/24,10.40.40.0/24,192.168.4.0/23和172.22.0.0/22)。 运行以下命令:

ipset -N routed_nets nethash
ipset -A routed_nets 10.30.30.0/24
ipset -A routed_nets 10.40.40.0/24
ipset -A routed_nets 192.168.4.0/23
ipset -A routed_nets 172.22.0.0/22
iptables -t nat -A POSTROUTING \
         -s 10.0.0.0/24 \
         -m set ! --set routed_nets dst \
         -j MASQUERADE

正如您所看到的,ipset可以很容易地确定您想要匹配的内容和不需要的内容。 此规则将伪装从内部LAN(10.0.0.0/24)通过该框的所有流量,除了绑定到routed_nets集中任何网络的数据包,保留对这些网络的正常直接IP路由。 由于此配置完全基于网络地址,因此您不必担心适当的连接类型(VPN的类型,跳数等),也不必担心物理接口和拓扑。

这应该是这样的。 因为这是纯粹的第3层(网络层)实现,所以实现它所需的基础分类也应该是纯层3。

限制某些PC只能访问某些公共主机

让我们说老板担心某些员工在互联网上玩而不是工作,并要求你限制他们的PC访问他们需要能够工作的特定网站,但他不希望这样。影响所有PC(例如他)。

要限制三台PC(10.0.0.5,10.0.0.6和10.0.0.7)只能访问worksite1.com,worksite2.com和worksite3.com,请运行以下命令:

ipset -N limited_hosts iphash
ipset -A limited_hosts 10.0.0.5
ipset -A limited_hosts 10.0.0.6
ipset -A limited_hosts 10.0.0.7
ipset -N allowed_sites iphash
ipset -A allowed_sites worksite1.com
ipset -A allowed_sites worksite2.com
ipset -A allowed_sites worksite3.com
iptables -I FORWARD \
         -m set --set limited_hosts src \
         -m set ! --set allowed_sites dst \
         -j DROP

此示例与单个规则中的两个集匹配。 如果源匹配limited_hosts且目标与allowed_sites不匹配,则丢弃该数据包(因为limited_hosts仅允许与allowed_sites通信)。

请注意,因为此规则位于FORWARD链中,所以它不会影响与防火墙本身之间的通信,也不会影响内部流量(因为该流量甚至不会涉及防火墙)。

阻止除某些PC之外的所有主机的访问(反向场景)

假设老板想阻止访问局域网内所有主机上的一组站点,除了他的PC和他的助手的PC。 对于多样性,在这个例子中,让我们通过MAC地址而不是IP匹配boss和助手PC。 让我们说MAC是11:11:11:11:11:11和22:22:22:22:22:22,其他人阻止的网站是badsite1.com,badsite2.com和badsite3.com 。

代替使用第二个ipset来匹配MAC,让我们利用带有MARK目标的多个iptables命令来标记数据包,以便在同一链中的后续规则中进行处理:

ipset -N blocked_sites iphash
ipset -A blocked_sites badsite1.com
ipset -A blocked_sites badsite2.com
ipset -A blocked_sites badsite3.com
iptables -I FORWARD -m mark --mark 0x187 -j DROP
iptables -I FORWARD \
         -m mark --mark 0x187 \
         -m mac --mac-source 11:11:11:11:11:11 \
         -j MARK --set-mark 0x0
iptables -I FORWARD \
         -m mark --mark 0x187 \
         -m mac --mac-source 22:22:22:22:22:22 \
         -j MARK --set-mark 0x0
iptables -I FORWARD \
         -m set --set blocked_sites dst \
         -j MARK --set-mark 0x187

正如您所看到的,因为您没有使用ipset来执行上一个示例中的所有匹配工作,所以这些命令更复杂。 因为有多个iptables命令,所以必须认识到它们的顺序非常重要。

请注意,这些规则是使用-I选项(插入)而不是-A(追加)添加的。 插入规则后,它将添加到链的顶部,将所有现有规则向下推。 因为正在插入这些规则中的每一个,所以有效顺序是相反的,因为随着每个规则的添加,它将被插入到前一个规则之上。

上面的最后一个iptables命令实际上成为FORWARD链中的第一个规则。 此规则匹配目标与blocked_sites ipset匹配的所有数据包,然后使用0x187(任意选择的十六进制数)标记这些数据包。 接下来的两条规则仅匹配要排除的主机的数据包以及已标记为0x187的数据包。 然后这两个规则将这些数据包上的标记设置为0x0,“清除”0x187标记。

最后,最后一个iptables规则(由上面的第一个iptables命令表示)丢弃所有具有0x187标记的数据包。 这应该匹配在blocked_sites集中具有目的地的所有数据包,除了来自任一排除的MAC的数据包,因为这些数据包上的标记在达到DROP规则之前被清除。

这只是解决问题的一种方法。 除了使用第二个ipset之外,另一种方法是利用用户定义的链。

如果你想使用第二个ipset而不是mark技术,你将无法达到上述的确切结果,因为ipset没有machash集类型。 但是,有一个macipmap集类型,但这需要在IP和MAC上匹配,而不是像上面那样单独使用MAC。

注意事项:在大多数实际情况中,此解决方案实际上不适用于网站,因为许多可能成为blocked_sites集合的主机(如Facebook,MySpace等)可能有多个IP地址,这些IP可能经常变化。 iptables / ipset的一般限制是只有在解析为单个IP时才应指定主机名。

此外,主机名查找仅在命令运行时发生,因此如果IP地址更改,防火墙规则将不会知道更改,仍将引用旧IP。 因此,实现这些类型的Web访问策略的更好方法是使用HTTP代理解决方案,例如Squid。 该主题显然超出了本文的范围。

自动禁止尝试访问无效服务的主机

ipset还为iptables提供了“目标扩展”,它提供了一种基于任何iptables规则动态添加和删除集合条目的机制。 您无需使用ipset命令手动添加条目,而是可以让iptables随时为您添加条目。

例如,如果远程主机尝试连接到端口25,但您没有运行SMTP服务器,则可能没有任何好处。 要拒绝主持人有机会主动尝试其他任何操作,请使用以下规则:

ipset -N banned_hosts iphash
iptables -A INPUT \
         -p tcp --dport 25 \
         -j SET --add-set banned_hosts src
iptables -A INPUT \
         -m set --set banned_hosts src \
         -j DROP

如果数据包到达端口25,比如源地址为1.1.1.1,则会立即将其添加到banned_hosts,就像运行此命令一样:

ipset -A banned_hosts 1.1.1.1

由于DROP规则,从1.1.1.1开始的所有流量都被阻止。

请注意,这也将禁止尝试运行端口扫描的主机,除非他们知道要避免端口25。

清除运行配置

如果要清除ipset和iptables配置(设置,规则,条目)并重置为新的打开防火墙状态(在防火墙脚本的顶部有用),请运行以下命令:

iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -t filter -F
iptables -t raw -F
iptables -t nat -F
iptables -t mangle -F
ipset -F
ipset -X

无法销毁“正在使用”的集合,这意味着由一个或多个iptables规则引用(使用ipset -X )。 因此,为了确保从任何状态完全“重置”,必须首先刷新iptables链(如上所示)。

生产实例

结合生产的SSH,配置如下:

ipset create whitelist-ssh hash:net hashsize 4096
ipset add whitelist-ssh 192.168.10.0/23

iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m set --match-set whitelist-ssh src -p tcp --dport 22 -m comment --comment "Allow Access SSH" -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m comment --comment "Deny Access SSH" -j DROP

ipset list
iptables -t filter -vxnL

ipset save > /etc/ipset.up.rules
iptables-save > /etc/iptables/rules.v4

//往/etc/rc.local加入以下:
ipset restore < /etc/ipset.up.rules
iptables-restore < /etc/iptables/rules.v4

模拟fail2ban 功能

60秒内访问3次SSH,将源IP禁止60秒

ipset create badguys hash:net timeout 900
ipset add badguys 192.168.0.0/16 nomatch
ipset add badguys 172.16.0.0/16 nomatch

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m set --match-set badguys -j DROP

iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 3 --rttl --name SSH -j set --add-set badguys src

或者

ipset create badguys hash:net timeout 900

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m set --match-set badguys src -j DROP
iptables -A INPUT -p tcp --dport 22 -m hashlimit --hashlimit-above 5/min --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name SSH-bruteforce -j SET --add-set badguys src

模拟Port Knocking 功能

ipset create knock hash:ip timeout 10
ipset create knock2 hash:ip timeout 10
ipset create whosthere hash:ip timeout 300

iptables -A INPUT -m set --match-set whosthere src -j ACCEPT
iptables -A INPUT -p tcp --dport 1989 -j SET --add-set knock src
iptables -A INPUT -p tcp -m set --match-set knock src --dport 2016 -j SET --add-set knock2 src
iptables -A INPUT -p udp -m set --match-set knock2 src --dport 1864 -j SET --add-set whosthere src

依次按以下敲门

  • 1989/TCP
  • 2016/TCP
  • 1864/UDP

批量禁端口

ipset -N BAD-PORTS bitmap:port range 135-1512
ipset -A BAD-PORTS 135
ipset -A BAD-PORTS 137-139
ipset -A BAD-PORTS 144
ipset -A BAD-PORTS 445
ipset -A BAD-PORTS 1512

iptables -A FORWARD -p tcp -m set --match-set BAD-PORTS dst -j DROP
iptables -A FORWARD -p udp -m set --match-set BAD-PORTS dst -j DROP

或者 对TCP与UDP分别对应

ipset create bl-tcp-ports bitmap:port range 0-65535
ipset create bl-udp-ports bitmap:port range 0-65535
ipset add bl-tcp-ports 23
ipset add bl-tcp-ports 1433
ipset add bl-tcp-ports 2323
ipset add bl-tcp-ports 3306
ipset add bl-tcp-ports 3389
ipset add bl-tcp-ports 5060

定义开放服务端口

  • 简单版本
ipset create services bitmap:port range 0-65535
ipset add services 443

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t filter -A INPUT -p tcp -i ens192 -d 172.16.4.109 -m conntrack --ctstate NEW -m set --match-set services dst -j ACCEPT
  • IP + 端口结合 iptables 实现如下:
-A INPUT -s 192.0.2.42 -d 0/0 -p tcp --dport 22 -j ACCEPT 
-A INPUT -s 192.0.2.42 -d 0/0 -p tcp --dport 25 -j ACCEPT
-A INPUT -s 198.51.100.54 -d 0/0 -p tcp --dport 22 -j ACCEPT
-A INPUT -s 198.51.100.54 -d 0/0 -p tcp --dport 25 -j ACCEPT

使用ipset 简化

-A INPUT -p tcp -m set --match-set ADMIN_ADDR src -m set --match-set RESTRICTED_PORTS dst -j ACCEPT

ipset create ADMIN_ADDR hash:ip
ipset create RESTRICTED_PORTS bitmap:port
ipset add ADMIN_ADDR 192.0.2.42,198.51.100.54
ipset add RESTRICTED_PORTS 22,25

ipset add ADMIN_ADDR $friend_ip
ipset del ADMIN_ADDR $friend_ip 

实现管理员IP白名单功能

ipset create trustedAdminsIP hash:ip
ipset add trustedAdminsIP 192.0.2.1
ipset add trustedAdminsIP 192.0.2.2
ipset list trustedAdminsIP

iptables -A INPUT -i lo -j ACCEPT

iptables -A INPUT -m state ---state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m set --match-set trustedAdminsIP src -p tcp -m tcp --dport 22 -j ACCEPT

ipset del trustedAdminsIP 192.0.2.2
ipset flush trustedAdminsIP
ipset destroy trustedAdminsIP 

或优雅的写法

ipset create whitelist -exist hash:net family inet
ipset add whitelist 119.147.144.176/29
ipset add whitelist 39.108.115.157

iptables -I  INPUT 2 -p tcp --dport 22 -m set --match-set whitelist src -j LOG --log-prefix "IP Whitelisted INPUT "
iptables -t filter -I INPUT 3 -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -m set --match-set whitelist src -m comment --comment "Allow Access SSH" -j ACCEPT

IP黑名单功能

ipset create blacklist hash:net
ipset add blacklist 185.93.185.237 -exist
ipset add blacklist 204.2.134.0/24 -exist
ipset add blacklist 208.100.26.228 -exist
iptables -I INPUT 1 -m set --match-set blacklist src -j LOG --log-prefix "IP Blacklisted INPUT"
iptables -I INPUT 2 -m set --match-set blacklist src -j DROP

使用systemd 管理

Unit]
Description=ipset persistent rule service
Before=firewalld.service
ConditionFileNotEmpty=/etc/sysconfig/ipset
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ipset -exist -file /etc/sysconfig/ipset restore
ExecStop=/usr/sbin/ipset -file /etc/sysconfig/ipset save
[Install]
WantedBy=multi-user.target

引用

  • [1] http://ipset.netfilter.org/iptables-extensions.man.html
  • [2] https://blog.manasg.com/fun-with-ipset-and-iptables/
  • [3] http://ipset.netfilter.org/ipset.man.html

iptables 限速

从知乎这个问题开始,限速命令如下:

iptables -A OUTPUT -d 192.168.4.204 -m limit --limit 35/s --limit-burst 40 -j ACCEPT
iptables -A OUTPUT -d 192.168.4.204 -j DROP

命令行中主要用到了 iptables 的扩展模块 limit,也就是 -m limit

Limit match

从 iptables 指南上看出 limit 和 limit-burst 最初不是拿来限速的。是拿来限制日志记录的次数的。

这个匹配操作必须由-m limit明确指定才能使用。有了它的帮助,就可以对指定的规则的日志数量加以限制,以免你被信息的洪流淹没哦。比如,你可以事先设定一个限定值,当符合条件 的包的数量不超过它时,就记录;超过了,就不记录了。我们可以控制某条规则在一段时间内的匹配次数 (也就是可以匹配的包的数量),这样就能够减少DDoS syn flood攻击的影响。这是它的主要作用,当然,还有很多其他作用(译者注:比如,对于某些不常用的服务可以限制连接数量,以 免影响其他服务)。limit match也可以用英文感叹号取反,如:-m limit ! --limit 5/s 表示在数量超过限定值后,所有的包都会被匹配。

在开头的命令行中,--limit 35/s 意思是每秒钟允许35个包出栈。

limit-burst

limit-burst 是个初始值,匹配次数过了这个初始值之后,就由 --limit xxx/s 来控制。

在开头的命令行中,--limit-burst 40 意思是允许40个包作为缓冲区,用满了再匹配 --limit xxx/s

参考资料


word 压缩文件 - 某乎

https://zhuanlan.zhihu.com/p/61727378

先打开这个需要压缩的Word文档,然后在Word表格里上方功能区中找到“文件”选项,左侧的菜单栏这里找到并点击 另存为

img

Word 会自动弹出“另存为”对话框,在这里我们先不要点击保存。在保存的旁边有个“工具”选项,我们点击后选择最下面的“压缩图片”;

img

在弹出的“图片压缩”对话框中,我们可以看到下面有很多分类,它可以把Word文档的图片压缩成各种品质,选择以后点击“保存”就可以了。

img

在压缩Word文件时候,可以选择将需要操作的文件保存在一个文件夹中,便于方便后面的操作;

img