0%

参考来源:

https://mp.weixin.qq.com/s/KFpY1mZSy-KhdMDg3C328g

线程间共享了哪些资源

线程私有资源(线程上下文)

线程运行的本质其实就是函数的执行,函数的执行是有源头的,这个源头就是入口函数,CPU从入口函数开始执行从而形成一个执行流,我们人为地给这个执行流赋予一个名字,这个名字就叫线程。

所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器是线程私有的。这些信息有一个统一的名字,叫线程上下文(Thread context)。

栈帧

我们知道,函数运行时的信息保存在栈帧中,栈帧中保存了函数的返回值、调用其他函数的参数、该函数使用的局部变量以及该函数使用的寄存器信息。

stack-frame

程序计数器

此外,CPU执行指令的信息保存在一个叫做程序计数器的寄存器中,通过这个寄存器我们就知道接下来要执行哪一条指令。

寄存器

由于操作系统随时可以暂停线程的运行,因此我们保存以及恢复程序计数器中的值就能知道线程是从哪里暂停的以及该从哪里继续运行了。

线程局部存储(Thread Local Storage)

所谓线程局部存储,是指存放在该区域中的变量有两个含义:

  • 存放在该区域中的变量是全局变量,所有线程都可以访问
  • 虽然看上去所有线程访问的都是同一个变量,但该全局变量独属于一个线程,一个线程对此变量的修改对其他线程不可见。

在C++中,线程局部存储变量使用__thread修饰。线程局部存储可以让我们使用一个独属于线程的全局变量。也就是说,虽然该变量可以被所有线程访问,但该变量在每个线程中都有一个副本,一个线程对改变量的修改不会影响到其它线程。

线程共享资源

在进程的地址空间中,线程共享除了线程上下文外的所有内容。

代码区

代码区保存的是程序编译后的可执行机器指令,它是从可执行文件中加载到内存的。线程之间共享代码区,这就意味着任何一个函数都可以放到线程中去执行不存在某个函数只能被特定线程执行的情况。

code-region

数据区

数据区存放的全局变量,即定义在函数之外的变量。在程序运行期间(run time),数据区中的全局变量有且仅有一个实例,所有的线程都可以访问到该全局变量。

data-region

有一类特殊的变量——使用static关键词修饰的变量,它们虽然有可能定义在函数内部,但它们依然具备全局变量的特性,即使函数执行完后改变量依然存在。因此静态变量也是放在进程地址空间的数据区的。

堆区

堆区存放的是程序中通过动态申请内存得到的变量的存放位置。在堆区的变量,只要知道变量的地址,也就是指针,任何一个线程都可以访问指针指向的数据,数据进程的资源。

heap-region

栈区

从抽象的概念上来说,栈区是线程私有的,但从实际的实现上来看,栈区属于线程私有这一规则并没有被严格遵守。

通常情况下,栈区是线程私有的,存在不通常情况的原因是,与进程地址空间之间的严格隔离不同,线程的栈区之间没有严格的隔离机制,也就是说,线程之间是存在相互修改数据的可能性的。

cross-thread

例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
void thread(void* var) {
int* p = (int*)var;
*p = 2;
}

int main() {
int a = 1;
pthread_t tid;

pthread_create(&tid, NULL, thread, (void*)&a);
return 0;
}

首先我们在主线程的栈区定义了一个局部变量a,该变量属于主线程的私有数据。但接下来我们创建了一个新心线程,并把变量a的地址以参数的形式传递给新线程,在线程thread中,程序通过指针修改了变量a的值。也就是说,我们在新创建的线程中修改了本属于主线程的私有数据。

尽管栈区是线程的私有数据,但由于栈区没有添加任何保护机制,一个线程的栈区对其它线程是可以见的,也就是说我们可以修改属于任何一个线程的栈区。上面的这种做法在代码的角度是行得通的,但这种编码方式会给问题定位带来很大的困难。

动态链接库

可执行文件是由编译器生成的,这句话只对了一半。编译器在将可执行程序翻译成机器指令后,需要链接器链接目标文件后,才能生成可执行程序。

executable-file

链接器有两种链接方式,静态链接和动态链接。

静态链接的意思是说把所有的机器指令一股脑全部打包到可执行程序中,动态链接的意思是我们不把动态丽娜姐的部分打包到可执行程序,而是在可执行程序运行起来后去内存中找动态链接的那部分代码。动态链接一个显而易见的好处就是可执行文件会比较小,而动态链接的部分生成的库就是我们熟悉的动态链接库,在Windows下是以dll结尾的文件,而在Linux下是以so结尾的文件。

dynamic-link-library

如果一个应用程序是通过动态链接方式生成的,那么在其地址空间中有一部分包含的就是动态链接库,否则程序无法运行,这一部分空间也是被所有线程共享的。

打开的文件

如果程序在运行过程中打开了一些文件,那么进程地址空间中还保存有打开的文件信息,它们也可以被所有线程共享使用。

opend-file

参考来源:

https://www.cnblogs.com/bakari/p/10443484.html#406017600

Linux network namespace:ip命令

和Linux network namespace相关的操作子命令为ip netnsip link

创建net namespace

1
2
3
[root@control-plane ~]# ip netns add net1
[root@control-plane ~]# ip netns ls
net1

进入net namespace

1
2
3
4
5
6
7
8
9
[root@control-plane ~]# ip netns exec net1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@control-plane ~]# ip netns exec net1 bash
[root@control-plane ~]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@control-plane ~]# exit
exit

两个net namespace间通信

两个network namespace之间通信,可以借助虚拟网络设备veth pair。拓扑图如下:

cross-netns-two

创建veth pair

1
2
3
4
5
6
[root@control-plane ~]# ip link add type veth
[root@control-plane ~]# ip link
14: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 5a:b3:38:ff:19:68 brd ff:ff:ff:ff:ff:ff
15: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 76:fe:86:96:87:e4 brd ff:ff:ff:ff:ff:ff

可以看到,veth pair总是成对出现的,使用命令 ip link add xxx type veth peer name yyy 指定 veth pair 的两个网络接口名。

将veth pair加入net namespace

1
2
3
4
5
6
7
[root@control-plane ~]# ip link set veth0 netns net0
[root@control-plane ~]# ip link set veth1 netns net1
[root@control-plane ~]# ip netns exec net0 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
14: veth0@if15: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 5a:b3:38:ff:19:68 brd ff:ff:ff:ff:ff:ff link-netnsid 1

可以看到,net interface接入namespace后,初始状态为DOWN。

给veth pair配上IP地址

1
2
3
4
5
6
7
8
[root@control-plane ~]# ip netns exec net0 ip link set veth0 up 
[root@control-plane ~]# ip netns exec net0 ip addr add 10.1.1.1/24 dev veth0
[root@control-plane ~]# ip netns exec net0 ip route
10.1.1.0/24 dev veth0 proto kernel scope link src 10.1.1.1
[root@control-plane ~]# ip netns exec net1 ip link set veth1 up
[root@control-plane ~]# ip netns exec net1 ip addr add 10.1.1.2/24 dev veth1
[root@control-plane ~]# ip netns exec net1 ip route
10.1.1.0/24 dev veth1 proto kernel scope link src 10.1.1.2

可以看到,配置完IP地址后,自动生成了路由信息。

测试连通性

1
2
3
4
[root@control-plane ~]# ip netns exec net0 ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.055 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.042 ms

多个net namespace之间通信

两个namespace之间通信可以借助veth pair,多个net namespace之间的通信则更适合用bridge来转接。拓扑图如下:

cross-netns-multi

使用ip link和brctl创建bridge

1
2
3
4
5
[root@control-plane ~]# ip link add br0 type bridge 
[root@control-plane ~]# ip link set dev br0 up
[root@control-plane ~]# ip link
16: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 2e:ea:a9:1e:70:3b brd ff:ff:ff:ff:ff:ff

创建veth pair

1
2
3
[root@control-plane ~]# ip link add type veth
[root@control-plane ~]# ip link add type veth
[root@control-plane ~]# ip link add type veth

将veth pair的一端挂到namespace,另一端挂到bridge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@control-plane ~]# ip link set dev veth1 netns net0
[root@control-plane ~]# ip netns exec net0 ip link set dev veth1 name eth0
[root@control-plane ~]# ip netns exec net0 ip addr add 10.0.1.1/24 dev eth0
[root@control-plane ~]# ip netns exec net0 ip link set dev eth0 up

[root@control-plane ~]# ip link set dev veth0 master br0
[root@control-plane ~]# ip link set dev veth0 up


[root@control-plane ~]# ip link set dev veth3 netns net1
[root@control-plane ~]# ip netns exec net1 ip link set dev veth3 name eth0
[root@control-plane ~]# ip netns exec net1 ip addr add 10.0.1.2/24 dev eth0
[root@control-plane ~]# ip netns exec net1 ip link set dev eth0 up

[root@control-plane ~]# ip link set dev veth2 master br0
[root@control-plane ~]# ip link set dev veth2 up


[root@control-plane ~]# ip link set dev veth5 netns net2
[root@control-plane ~]# ip netns exec net2 ip link set dev veth5 name eth0
[root@control-plane ~]# ip netns exec net2 ip addr add 10.0.1.3/24 dev eth0
[root@control-plane ~]# ip netns exec net2 ip link set dev eth0 up

[root@control-plane ~]# ip link set dev veth4 master br0
[root@control-plane ~]# ip link set dev veth4 up

测试连通性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@control-plane ~]# ip netns exec net0 ping 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_seq=1 ttl=64 time=0.075 ms
64 bytes from 10.0.1.2: icmp_seq=2 ttl=64 time=0.059 ms
^C
--- 10.0.1.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.059/0.067/0.075/0.008 ms

[root@control-plane ~]# ip netns exec net0 ping 10.0.1.3
PING 10.0.1.3 (10.0.1.3) 56(84) bytes of data.
64 bytes from 10.0.1.3: icmp_seq=1 ttl=64 time=0.105 ms
64 bytes from 10.0.1.3: icmp_seq=2 ttl=64 time=0.049 ms
^C
--- 10.0.1.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.049/0.077/0.105/0.028 ms

上述数据表明,三个network namespace之间可以互相访问。

参考来源:

https://cizixs.com/2017/08/29/linux-namespace/

https://coolshell.cn/articles/17010.html

https://mp.weixin.qq.com/s/10HgkUE14wVI_RNmFdqkzA

简介

Linux Namespace是Linux提供的一种内核级别的环境隔离方法。在Unix时代,有一个叫做chroot的系统调用,它提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。目前Linux 内核提供了以下几种Namespace:

Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。

名称 宏定义 隔离资源 内核版本
mnt CLONE_NEWNS Mount point 2.4.19
ipc CLONE_NEWIPC System V IPC, POSIX message queue 2.6.19
net CLONE_NEWNET network device interface, IPv4 and IPv6 protocol stack, IP routing table, firewall rule, the /proc/net and /sys/class/net directory tree, socket, etc 2.6.24
pid CLONE_NEWPID Process ID 2.6.24
user CLONE_NEWUSER User and group ID 3.8
UTS CLONE_NEWUTS Hostname and NIS domain name 2.6.19
cgroup CLONE_NEWCGROUP Control group root directory 4.6

这些namespace基本上覆盖了一个程序运行所需的环境,包括主机名、用户权限、文件系统、网络、进程号、进程间通信。所有的进程都会有namespace,可以理解为namespace是进程的一个属性。

每个进程都对应一个/proc/[pid]/ns目录,里面保存了该进程所在namespace的链接文件:

1
2
3
4
5
6
7
8
9
10
11
12
[root@control-plane ~]# ps
PID TTY TIME CMD
2718 pts/0 00:00:00 bash
18121 pts/0 00:00:00 ps
[root@control-plane ~]# ll /proc/2718/ns/
total 0
lrwxrwxrwx. 1 root root 0 Jan 7 11:43 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 Jan 7 11:43 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Jan 7 11:43 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 Jan 7 11:43 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 Jan 7 11:43 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 Jan 7 11:43 uts -> uts:[4026531838]

上述每个文件都是对应namespace的文件描述符,方括号中的值是文件的inode,如果两个进程所在的namespace是相同的,那么inode值也是相同的。如果某个namespace中没有进程了,它会被自动删除,但当某个应用程序打开了namespace文件时它不会被删除,通过这个特性,完美可以持有一个空的namspace,以便向其中添加进程。

三个系统调用

Linux内核提供的功能都会提供系统调用接口供应用程序使用,namespace自然也不例外。和namespace相关的系统调用主要有三个:clonesetnsunshare

clone:创建新进程并设置它的namespace

clone类似于fork系统调用,可以创建一个新进程,不同的是我们可以指定紫禁城要执行的函数,以及通过参数控制子进程的运行环境。下面是clone的定义:

1
2
# include <sched.h>
int clone(int (*fn) (void *), void *child_stack, int flags, void *arg, ...);

它有四个重要的参数:

  • fn参数是一个函数指针,子进程启动的时候会调用这个函数来执行
  • child_stack参数指定了子进程stack开始的内存地址,因为stack都会从高位到地委增长,所以这个指针需要指向分配stack的最高位地址
  • flags用来控制子进程的特性,比如新创建的进程是否与父进程共享虚拟内存等。比如可以传入CLONE_NEWNS标志使得新创建的新城拥有独立的Mount Namespace,也可以传入多个flags如CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC
  • arg作为fn函数的参数

setns:让进程加入已经存在的namespace

setns能够把某个进程加入到给定的namespace,它的定义是这样的:

1
int setns(int fd, int nstype);
  • fd参数是一个文件描述符,指向/proc/[pid]/ns/目录下的某个namespace,调用这个函数的进程就会被加入到fd指向文件所代表的namespace,fd可以通过打开namespace对应的文件获取
  • nstype限定进程可以加入的namespace,可能的取值有:(如果不知道 fd 指向的 namespace 类型,比如fd是其他进程打开的,然后在应用中希望明确指定特种类型的 namespacenstype 就非常有用)
    • 0:可以加入任意的namespace
    • CLONE_NEWIPC:fd 必须指向IPC namespace
    • CLONE_NEWNET:fd 必须指向network namespace
    • CLONE_NEWNS:fd 必须指向mount namespace
    • CLONE_NEWPID:fd 必须指向PID namespace
    • CLONE_NEWUSER: fd 必须指向user namespace
    • CLONE_NEWUTS: fd 必须指向UTS namespace

unshare:让进程脱离到新的namespace

unshare()系统调用用于将当前进程和所在的namespace分离,并加入到一个新的namespace中,只需要指定想要分离的namespace即可,它的定义如下:

1
int unshare(int flags);
  • flags,它的含义和 cloneflags 相同。
    • CLONE_FILES: 子进程一般会共享父进程的文件描述符,如果子进程不想共享父进程的文件描述符了,可以通过这个flag来取消共享。
    • CLONE_FS: 使当前进程不再与其他进程共享文件系统信息。
    • CLONE_SYSVSEM: 取消与其他进程共享SYS V信号量。
    • CLONE_NEWIPC: 创建新的ipc namespace,并将该进程加入进来。

注意unshare()setns()系统调用对PID Namespace的处理不太相同。

发生系统调用时,调用进程会为它的子进程分配一个新的pid namespace,且调用进程第一个创建的子进程会成为新namespace中的pid=1的进程,但是调用进程本身不会被移到新的pid namespace中。

为什么创建其他的namespace时unshare()setns()会直接进入新的namespace,而唯独PID Namespace不是如此呢?

因为调用getpid()函数得到的pid是根据调用者所在的PID Namespace而决定返回哪个pid,进入新的pid namespace会导致pid变化。而对用户态的程序和库函数来说,他们都认为进程的pid是一个常量,pid的变化会引起这些进程奔溃。

换句话说,一旦程序进程创建以后,那么它的pid namespace的关系就确定下来了。

参考来源:

视频:苦了小镇做题家,学渣个个企业家!https://www.bilibili.com/video/BV12y4y1k7g8

up主:真_寒冰射手曹草草

什么叫做题家思维

做题本身没有任何问题,做题只是一种手段,一种社会元素,如同吃饭、驾驶汽车、与人交谈一样,是一种无关对错的技术和行为,而且也是这个社会生活必不可少的组成部分。不做题,科技没有办法进步,社会没有办法发展。真正荼毒我国社会,尤其是我国只是分子整体精神状态的是一种习惯以做题来解决以切问题的做题家思维。

这种做题家思维包括三点。

第一,试图使用单一标准和价值体系,给人分成三六九等。年薪高的人一定比年薪低的人优秀,某高校的人一定优于另一高校的人,专业,城市,国家。

第二,试图使用单一方式解决生活中遇到的一切问题。

第三,试图使用单一要素,来塑造自己的人生价值。一个做题家的人生价值往往非常单一,就是赚钱,赚钱买房就是成功,如果已经赚到钱买了房之后呢,就赚更多的钱买更多的房。生活中没有其他的快乐,没有其他的爱好,不喜欢旅游,不喜欢乐器,也不喜欢运动,不喜欢和朋友们一起活动,因为所有这些活动,都具有潜在的对赚钱行为以及能力的破坏,他们认为花钱是在浪费时间和精力。

如何破除做题家思维

第一,社会的价值体系是极为多样化的,有的人能赚钱,有的人会做官,有的人技术强,有的人人脉广,有的人名气大,他们都能在各自的领域获得尊重。

第二,解决问题的方案往往是综合性的。

第三,人生价值是极为丰富多彩的。

思考

自出生以来,你觉得自己最大的成就是什么?

本文参考、整理自:

《云原生服务网格Istio原理、实践、架构与源码解析》https://book.douban.com/subject/34438220/

朱双印个人日志:iptables详解系列 http://www.zsythink.net/archives/tag/iptables/

iptables的基本原理

概念

我们通常所说的iptables严格来讲应该叫做NetfilterNetfilter是一种内核防火墙框架,可以实现很多网络安全策略,包括数据包过滤、数据包处理、地址伪装、透明代理、网络地址转换等。iptables则是一个应用层的二进制工具,可以基于Netfilter接口设置内核中的Netfilter配置表。

iptables由表及构成表的链组成,每条链又由具体的规则组成。iptables内置4张表和5条链,4张表分别是RAW、Mangle、NAT、Filter表,5条链又叫作数据包的5个挂载点(Hook Point,可以理解为回调函数点,在数据包到达这些位置时,内核回主动调用回调函数,使得数据包在路由时可以改变方向或内容),分别是PREROUTING、INPUT、OUTPUT、FORWORD和POSTROUTING。对于不同表中相同类型的规则执行顺序,iptabels定义了优先级,该优先级由高到低排序为raw、managle、nat、filter。例如,对于PREROUTING链来说,首先执行raw表的规则,然后执行mangle表的规则,最后执行nat表的规则。

iptables的表和链

Filter表表示iptables的默认表,如果在创建规则时未指定表,那么默认使用Filter表,主要用于数据包过滤,根据具体规则决定是否放行该数据包(DROP、ACCEPT、REJECT、LOG等)。

Filter表包含如下三种内建链:

  • INPUT链:过滤目的地址是本机的所有数据包
  • OUTPUT链:过滤本机产生的所有数据包
  • FORWARD链:过滤经过本机的所有数据包(源地址和目的地址都不是本机)

NAT表主要用于修改数据包的IP地址、端口号等信息,包含以下三种内建链

  • PREROUTING链:DNAT,处理刚到达本机并在路由转发前转换数据包的目的地址
  • POSTROUTING链:SNAT,处理即将离开本机的数据包,转换数据包的源地址
  • OUTPUT链:MASQUERADE,改变本机产生的数据包的源地址

主要用于修改数据包的TOS、TTL,以及为数据包设置Mark标记,以实现QoS调整及策略路由等。它包含所有5条内置规则链:PREROUTING、POSTROUTING、INPUT、OUTPUT、FORWORD。

RAW表是iptables在1.2.9版本之后新增的表,主要用于决定数据包是否被状态跟踪机制处理。RAW表的优先级要优于其他表,包含两条规则链:OUTPUT和PREROUTING。

iptables原理图

iptables详解(1):iptables概念

上图展示了iptables规则链处理数据包的顺序,网卡接收的数据包会进入内核协议栈被PREROUTING规则链处理(可能发生数据包的目的地址转换),之后由内核协议栈进行路由选择,如果数据包的目的地址是本机,则内核协议栈会将其传给INPUT链处理,INPUT链在允许通过后,数据包由内核空间进入用户空间,被主机进程处理;如果PREROUTING链处理后的数据报的目的地址不是本机地址,则将其传给FORWARD链进行处理,最后交给POSTROUTING链。本机进程发出的数据包首先进行路由选择,经过OUTPUT链后,到达POSTROUTING链(可能发生数据包的源地址转换)。

iptables的基本使用

规则查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@localhost ~]# iptables --line-number -t filter -nvxL INPUT
Chain INPUT (policy ACCEPT 750 packets, 50472 bytes)
num pkts bytes target prot opt in out source destination

# 选项
# --line-number 显示行号
# -t 指定表,默认为filter表
# -n 直接显示IP地址
# -v 显示详细信息
# -x 显示精确的收发包大小
# -L 指定链,不指定链名默认所有链

# 结果
# policy 当前链的默认策略
# pkts 当前链默认策略匹配到的包的数量
# bytes 当前链默认策略匹配到的包的大小总和
# num 行号
# pkts 当前规则匹配到的数据包数量
# bytes 当前规则匹配到的数据包大小总和
# target 当前规则的效果
# prot 当前规则的匹配的数据包网络协议
# opt
# in
# out
# source 源地址
# destination 目的地址

规则管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost ~]# iptables -t filter -I INPUT -s 192.168.1.146 -j REJECT
[root@localhost ~]# iptables -nvxL INPUT
Chain INPUT (policy ACCEPT 49 packets, 2900 bytes)
pkts bytes target prot opt in out source destination
0 0 REJECT all -- * * 192.168.1.146 0.0.0.0/0 reject-with icmp-port-unreachable

# -t 指定表名,默认为filter表
# 规则管理动作:-I 首部插入 -A 尾部追加 -D 删除 -F 刷新链(删除链中所有规则) -R 修改规则(必须指定原匹配条件和匹配效果)
# 规则匹配条件: -s 源IP -d 目的IP
# -j 规则效果: ACCEPT通过 DROP丢弃 REJECT拒绝

# 修改链的默认规则效果
[root@localhost ~]# iptables -P FORWARD DROP
[root@localhost ~]# iptables -nvxL FORWARD
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination

# 保存规则
[root@localhost ~]# iptables-save > /etc/sysconfig/iptables

规则匹配条件

基本匹配条件

源IP/目的IP地址

1
2
3
4
5
6
7
8
# 指定单个地址
iptables -t filter -I INPUT -s[d] 192.168.4.129 -j DROP
# 指定多个地址
iptables -t filter -I INPUT -s[d] 192.168.4.129,192.168.4.130 -j DROP
# 指定网段
iptables -t filter -I INPUT -s[d] 192.168.4.0.0/16 -j DROP
# 取反
iptables -t filter -I INPUT ! -s[d] 192.168.4.129 -j DROP

假设在空的INPUT链中添加如下规则:

iptables -t filter -I INPUT ! -s 192.168.4.129 -j DROP

那么当IP为192.168.4.129的主机ping本机时,首先对比源IP和规则中的IP,发现不符合规则的匹配条件,会进入INPUT链的默认处理策略,即ACCEPT,因此IP为192.168.4.129的主机可以收到ICMP响应包。

协议类型

1
2
3
iptables -t filter -I INPUT -s 192.168.4.129 -p tcp -j REJECT
# iptables支持的协议类型
# tcp, udp, udplite, icmp, icmpv6,esp, ah, sctp, mh 不指定协议时默认匹配所有协议

网卡接口

1
2
3
iptables -t filter -I INPUT -i eth0 -j REJECT
# -i 匹配数据包流入网卡,只能用于PREROUTING、INPUT、FORWARD链
# -o 匹配数据包流出网卡,只能用于OUTPUT、FORWARD、POSTROUTING链

扩展匹配条件

上面讲的都是iptabls的基本匹配条件,端口号属于扩展匹配条件。对于基本匹配条件,如果需要使用扩展匹配条件,则需要一些扩展模块的支持。

tcp扩展

1
2
3
4
5
6
7
8
9
10
11
# 端口号
iptables -t filter -I INPUT -s 192.168.4.129 -p tcp [-m tcp] --dport 22 -j REJECT
iptables -t filter -I INPUT -s 192.168.4.129 -p udp [-m udp] --sport 30000:36655 -j REJECT
# -m 指定扩展模块名,如不指定,会默认使用-m 协议名
# --dport/--sport 指定端口范围
iptables -t filter -I INPUT -s 192.168.4.129 -p tcp -m mutiport --dport 22,23:25 -j REJECT
# -m mutiport 扩展模块可以同时指定多个离散的端口或端口范围

# TCP标志位
iptables -t filter -I INPUT -p tcp -m tcp --dport 22 --tcp-flags SYN,ACK,FIN,RST,URG,PSH[ALL] SYN -j REJCET
# --tcp-flags 指定了TCP报文中的标志位。第一个参数表示要匹配报文TCP头中的哪些标志位,第二个参数表示第一个参数列表中标志位必须为1的标志(其他标志必须为0)

udp扩展

1
iptables -t filter -I INPUT -p udp -m udp --dport 137 -j REJECT

icmp扩展

1
2
3
4
iptables -t filter -I INPUT -p icmp -m icmp --icmp-type 8/0 -j REJECT
# --icmp-type 指定icmp报文类型,8表示icmp type,0表示icmp code
# https://tools.ietf.org/html/rfc792
# https://zh.wikipedia.org/wiki/%E4%BA%92%E8%81%94%E7%BD%91%E6%8E%A7%E5%88%B6%E6%B6%88%E6%81%AF%E5%8D%8F%E8%AE%AE

iprange扩展

1
2
iptables -t filter -I INPUT -m iprange --src-range 192.168.4.127-192.168.4.131 -j REJECT
# iprange模块匹配数据包的源IP或目的IP地址范围(--dst-range)

string扩展

1
2
3
4
iptables -t filter -I INPUT -m string --algo bm --string "OOXX" -j REJCET
# string模块用于匹配字符串
# --algo 指定算法,支持bm算法或kmp算法
# --string 指定待匹配的字符串

time扩展

1
2
3
4
5
iptables -t filter -I INPUT -m time --timestart 09:00:00 --timestop 11:00:00 --weekdays 1,2,3,4,5 --monthdays 22,23,24 --datestart 2020-07-01 --datestop 2020-09-01 -j REJCET
# time 模块用于匹配时间
# --timestart/--timestop 指定一天内的时间段
# --weekdays/--monthdays 指定一周/月内的哪几天
# --datestart/--datestop 指定起始和结束日期

connlimit扩展

1
2
3
4
iptables -t filter -I INPUT -p tcp --dport 22 -m connlimit --connlimit-above 2 --connlimit-mask 24 -j REJECT
# connlimit 模块用于设置连接数限制
# --connlimit-above 指定每个客户IP的并发连接数上限
# --connlimit-mask 指定客户IP的网段(掩码长度)

limit扩展

1
2
3
4
5
6
7
8
9
10
11
12
# 假设现在要设置这样一条规则:每6s响应1次(1min响应10次)icmp报文

# 方式一
iptables -t filter -I INPUT -p icmp -m limit --limit 10/minute -j ACCEPT
# limit 模块提供了速率限制功能
# --limit 指定匹配时间间隔
# 效果一:第6秒的报文匹配到了该规则,执行了放行操作;而第6秒前的报文没有匹配到该规则,会执行默认策略即ACCEPT,也就是说该规则实际上没有起到任何效果

# 方式二
iptables -t filter -I INPUT -p icmp -m limit --limit 10/minute -j ACCEPT
iptables -t filter -I INPIT -p icmp -j REJECT
# 效果二:只有被第一条规则匹配的到数据包会放行,其余的被REJECT掉,能够实现限流的功能。但实际测试会发现前5个报文没有被限流(都被放行了),原因在于limit模块默认参数--limit-burst=5。该参数与令牌桶算法有关。

令牌桶算法

有一个木桶,木桶里面放了5块令牌(–limit-burst),所有报文如果想要出关入关,都必须要持有木桶中的令牌才行,这个木桶每隔6秒钟会生成一块新的令牌。如果木桶中的令牌不足5块,新生成的令牌会被放入木桶中,否则令牌被丢弃。如果此时有5个报文想要入关,恰好这5个报文能在木桶里找到一人一个令牌,于是报文被放行;此时木桶中已经没有令牌可以使用了,因此剩余的报文被拒绝。但每6秒钟,会生成新的令牌,新的报文获取到该令牌后,再此被放行。令牌可积累,但木桶最多能存放的令牌数只有5个。

规则动作

规则动作也包括基础动作与扩展动作,与扩展匹配条件不同,扩展动作可以直接使用,而不需要指定特定模块。

基础动作

ACCEPT

DROP

扩展动作

REJECT

1
2
3
4
iptables -t filter -I INPUT -p tcp --dport 22 -j REJECT --reject-with icmp-host-unreachable
# --reject-with 指定拒绝提示信息,默认为icmp-host-unreachable
# 可用值如下:icmp-net-unreachable/icmp-host-unreachable/icmp-port-unreachable
# icmp-proto-unreachable/icmp-net-prohibited/icmp-host-pro-hibited/icmp-admin-prohibited

LOG

1
2
3
4
5
iptables -t filter - I INPUT -p tcp --dport 22 -j LOG --log-level info --log-prefix "log in from port 22"
# LOG动作会把符合条件的请求记录到日志中,默认的日志地址为/var/log/messages
# 修改日志地址:/etc/rsylog.conf文件中添加kern.warning /var/log/iptables.log,重起systemctl restart rsyslog
# --log-level 指定日志级别,可选值:emerg/alert/crit/error/warning/notice/info/debug
# --log-prefix 指定日志前缀(标签)

NAT

网络地址转换

假设网络内部有10台主机,它们有各自的IP地址,当网络内部的主机与其他网络中的主机通讯时,则会暴露自己的IP地址,如果我们想要隐藏这些主机的IP地址,该怎么办呢?

当网络内部的主机向网络外部主机发送报文时,报文会经过防火墙或路由器,当报文经过防火墙或路由器时,将报文的源IP修改为防火墙或者路由器的IP地址,当其他网络中的主机收到这些报文时,显示的源IP地址则是路由器或者防火墙的,而不是那10台主机的IP地址,这样,就起到隐藏网络内部主机IP的作用,当网络内部主机的报文经过路由器时,路由器会维护一张NAT表,表中记录了报文来自于哪个内部主机的哪个进程(内部主机IP+端口),当报文经过路由器时,路由器会将报文的内部主机源IP替换为路由器的IP地址,把源端口也映射为某个端口,NAT表会把这种对应关系记录下来。

示意关系如下:

iptables详解(13):iptables动作总结之二

于是,外部主机收到报文时,源IP与源端口显示的都是路由的IP与端口,当外部网络中的主机进行回应时,外部主机将响应报文发送给路由器,路由器根据刚才NAT表中的映射记录,将响应报文中的目标IP与目标端口再改为内部主机的IP与端口号,然后再将响应报文发送给内部网络中的主机。整个过程中,外部主机都不知道内部主机的IP地址,内部主机还能与外部主机通讯,于是起到了隐藏网络内主机IP的作用。

内部网络的报文发送出去时,报文的源IP会被修改,也就是源地址转换:Source Network Address Translation,缩写为SNAT。

外部网络的报文响应时,响应报文的目标IP会再次被修改,也就是目标地址转换:Destinationnetwork address translation,缩写为DNAT。

1
2
3
4
5
6
7
8
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -j SNAT --to-source public-IP
# SNAT动作只能配置在POSTROUTING、OUTPUT、FORWARD链上
# --to-source 指定源IP(公网IP)

iptables -t nat -I PREROUTING -d public-IP -p tcp --dport public-PORT -j DNAT --to-destination private-IP:private-PORT
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -j SNAT --to-source public-IP
# DNAT动作只能配置在PREROUTING、INPUT、FORWARD链上
# --to-destination 指定目的IP(私网IP)

MASQUERADE

1
2
3
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -o eth0 -j MASQUERADE
# MASQUERADE 动态获取有效IP
# -o 指定从那个网卡获取IP

REDIRECT

1
2
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
# REDIRECT 端口映射,只能定义在PREROUTING链或者OUTPUT链中

自定义链

当iptables链中的规则很多时,非常难以管理,比如在大量的规则中有些是针对httpd服务的,有些是针对sshd服务的,有针对私网IP的,有针对公网IP的。如果修改一条规则需要查看其他所有规则的话,将会大大降低效率,因此iptables提供了自定义链的能力。但自定义链并不能直接使用,而是需要被默认链引用才能够使用。

1
2
3
4
5
6
7
8
# 在filter表中创建IN_WEB自定义链
iptables -t filter -N IN_WEB
# 在INPUT链中引用自定义链
iptables -t filter -I INPUT -p tcp --dport 80 -j IN_WEB
# 重命名自定义链
iptables -E IN_WEB WEB
# 删除引用计数为0并且不包含任何规则的自定义链
iptables -X WEB

iptables实战分析