go-iptables功能与源码详解

1. 什么是iptables

介绍iptables之前我们先搬出他的父亲netfilter,netfilter是基于Linux 2.4.x或更新的内核,提供了一系列报文处理的能力(过滤+改包+连接跟踪),具体来讲可以包含以下几个功能:

  • 构建基于无状态和有状态的互联网防火墙,且支持部署高可用集群
  • 如果你没有很多公网ip,那么你的局域网内部可以使用nat技术共享公网访问
  • 使用nat技术进行透明代理,你的公网暴露ip转换为你的真实的内网服务ip
  • 构建复杂的qos和策略路由器
  • 进一步的数据包操作(修改),如更改ip头部的tos/dscp/ecn位。

其实说白了,netfilter就是操作系统实现了网络防火墙的能力(连接跟踪+过滤+改包),而iptables就是用户态操作内核中防火墙能力的命令行工具,位于用户空间。快问快答,为啥计算机系统需要内核态和用户态(狗头)。

既然netfilter是对报文进行处理,那么我们就应该先了解一下内核是如何进行收发包的,发生报文大致流程如下:

  • 用户态(User Space)程序 Client 向另一台主机上的 Server 发送数据,需要通过调用内核态(Kernel Space)提供给用户态的 Socket 抽象层接口发送数据;
  • Socket 抽象层接口收到用户态数据后,向下交给传输层接口(TCP 或 UDP);
  • 传输层负责创建sk_buff,并将用户数据(应用层数据)填充到缓冲区,做合法性检查后,添加传输层头部,并通过网络层注册的接口将数据包交给网络层处理;
  • 网络层收到传输层数据包后,会查询路由表,决定数据包去向,如果是需要发出的数据包,会填充网络层头部,并交到内核虚拟网络接口设备的发送队列中;
  • 虚拟网络接口从发送队列获取数据,调用对应网卡驱动发送数据

netfilter框架就是作用于网络层中,在一些关键的报文收发处理路径上,加一些hook点,可以认为是一个个检查点,有的在主机外报文进入的位置(PREROUTING​),有的在经过路由发觉要进入本机用户态处理之前(INPUT​),有的在用户态处理完成后发出的地方(OUTPUT​),有的在报文经过路由并且发觉不是本机决定转发走的位置(FOWARD​),有的在路由转发之后出口的位置(POSTROUTING​),每个检查点有不同的规则集合,这些规则会有一定的优先级顺序,如果报文达到匹配条件(五元组之类的)且优先级最高的规则(序号越小优先级越高),内核会执行规则对应的动作,比如说拒绝,放行,记录日志,丢弃。

lua 复制代码
                                  userspace process
                                  ^                |
                                  |                |
                             _____|____       ____/___
                            /          \     /         \
                            |   input   |    |  output  |
                            __________/     _________/
                                 ^                 |
                                 |                 |
       __________            ---------       _____/_____
      /          \           |Routing |     /            \
  ---> prerouting ---------> |decision|     | postrouting |
      __________/           ----------     ____________/  
                                 |                 ^        
                            ____/___              |        
                           /         \             |        
                           | forward |--------------     
                           _________/         

最后总结如下图所示,里面包含了netfilter框架中,报文在网络层先后经过的一些hook点:

报文转发视角:

iptables命令行工具管理视角:

规则种类:

makefile 复制代码
filter表: 负责过滤功能

nat表: 网络地址转换功能

mangle表: 报文解封装,改包

raw表: 连接追踪,记录每一条连接的状态,在提升设备转发效率上起到了很大的作用

流入本机路径:

rust 复制代码
数据进入(通过网线) --> 链接网卡设备 --> 网络接口层 --> Netfilter 
--> 在网络层时会经过PREROUTING链 --> TCP UDP协议 --> 进入用户层之前(INPUT链) --> 到达用户层

经过本机路径:

rust 复制代码
数据进入 --> PREROUTING --> FORWARD --> POSTROUTING --> 出去

流出本机路径:

rust 复制代码
用户操作命令工具(iptables) --> OUTPUT链 --> ip_tables内核模块 
--> Netfilter(防火墙) --> 网络层 --> 网络接口层 --> POSTROUTING链 --> 设备驱动 --> 网络传输出

2. iptables命令实战

由上一章节我们已经知道了iptables是用户态的命令行工具,目的就是为了方便我们在各个检查点增删改查不同种类的规则,命令的格式大致如下,简单理解就是针对具体的哪些流(五元组+某些特定协议还会有更细分的匹配条件,比如说只针对tcp syn报文)进行怎样的动作(端口ip转换或者阻拦放行):

css 复制代码
iptables -t nat -I PREROUTING -d 公网IP -p tcp --dport 8080 -j DNAT --to-destination 10.1.0.1:80

iptables -A INPUT -p tcp --dport 22 -j ACCEPT

iptables -A FORWARD -p TCP ! --syn -m state --state NEW -j DROP

2.1 最基本的增删改查

增删改查的命令,我们以最常用的filter规则为例,就是最基本的防火墙过滤功能,实验环境我先准备了一个centos7的docker跑起来(docker好啊,实验完了直接删掉,不伤害本机),并通过iptables配置一些命令,然后通过主机向该docker发生ping包,测试增删改查的filter规则是否生效。

1.查询

如果有规则会把他的序号显示出来,后面插入或者删除可以用

iptables -nvL -t filter --line​

可以看出filter规则可以挂载在INPUT,FORWARD,OUTPUT检查点上,并且兜底的规则都是ACCEPT,也就是没有匹配到其他规则就全部放行,这个兜底规则是可以修改的。

我们通过ifconfig查看出docker的ip,然后主机去ping一波:​

然后再去查一下,会发现17 packets, 11414 bytes ---> 对应规则匹配到的报文的个数/字节数:

  1. 新增+删除
    新增一条拒绝的报文,我们直接把docker0网关ip给禁了,这样就无法通过主机ping通docker容器了(如果有疑问,下面有解答,会涉及docker的一些小姿势):
    iptables -I INPUT -s 172.17.0.1 -j DROP (-I不指定序号的话就是头插)
    iptables -t filter -D INPUT 1

可见已经生效了,拦截了ping包,随后我删除了这条规则,又能够ping通了

  1. 修改

    通过-R可以进行规则修改,但能修改的部分比较少,只能改action,所以我的建议是先通过编号删除规则,再在原编号位置添加一条规则。

  2. 持久化

    当我们对规则进行了修改以后,如果想要修改永久生效,必须使用service iptables save保存规则,当然,如果你误操作了规则,但是并没有保存,那么使用service iptables restart命令重启iptables以后,规则会再次回到上次保存/etc/sysconfig/iptables文件时的模样。

bash 复制代码
# 配置好yum源以后安装iptables-service 
# yum install -y iptables-services 
# 停止firewalld 
# systemctl stop firewalld 
# 禁止firewalld自动启动 
# systemctl disable firewalld 
# 启动iptables 
# systemctl start iptables 
# 将iptables设置为开机自动启动,以后即可通过iptables-service控制iptables服务 
# systemctl enable iptables

再使用service iptables save命令保存iptables规则

  1. 自定义链
    我们可以创建自己的规则集,这样统一管理会非常方便,比如说,我现在要创建一系列的web服务相关的规则集,但我查询一波INPUT链一看,妈哎,200条规则,这200条规则有针对mail服务的,有针对sshd服务的,有针对私网IP的,有针对公网IP的,我这看一遍下来头都大了,所以就产生了一个非常合理的需求,就是我能不能创建自己的规则集,然后让这些检查点引用,答案是可以的:
    iptables -t filter -N MY_WEB

iptables -t filter -I INPUT -p tcp --dport 80 -j MY_WEB

这就相当于tcp目的端口80的报文会被送入到MY_WEB规则集中进行匹配了,后面有陆续新规则进行增删时,完全可以只针对MY_WEB进行维护。

还有不少命令,详见这位大佬的总结:

Linux iptables常用命令​www.cnblogs.com/ilinuxer/p/6364064.html

回过头来,讲一个关于docker的小知识点,就是容器和如何通过主机通讯的?

这就是veth-pair技术,一端连接彼此,一端连接协议栈,evth---pair 充当一个桥梁,连接各种虚拟网络设备的。

我们在容器内和主机敲一下ifconfig:

看到了吧,容器内的eth0和主机的veth41589a9就是成对出现的,然后各个主机的虚拟网卡通过docker0互联,也实现了容器间的通信,大致如下:

我们抓个包看一哈:

[root@ec58b2c87238 ens33]# tcpdump -i eth0 -vvvvvv

tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

06:03:28.342113 IP (tos 0x0, ttl 64, id 51104, offset 0, flags [DF], proto ICMP (1), length 84)

可以看出都是通过docker0网关转发的:

2.2 一些iptables常见的配置场景

  1. 允许回环连接

    例如,如果您运行ping localhost或ping 127.0.0.1,您的服务器将使用回环接口对自身进行ping测试。如果 您配置应用程序服务器以使用本地主机地址连接到数据库服务器,也将使用回环接口。

    sudo iptables -A INPUT -i lo -j ACCEPT

    sudo iptables -A OUTPUT -o lo -j ACCEPT

  2. 允许已建立和相关的传入连接

    由于网络流量通常需要双向(传入和传出)才能正常工作,通常会创建一个防火墙规则来允许已建立和相关的传入流量,以便服务器允许由服务器自身发起的传出连接的返回流量。

    sudo iptables -A INPUT -i lo -j ACCEPT

    sudo iptables -A OUTPUT -o lo -j ACCEPT

  3. 允许内部网络访问外部网络

    假设eth0是您的外部网络接口,而eth1是您的内部网络接口,以下命令将允许内部网络访问外部网络:

    sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT

  4. 丢弃无效数据包

    一些网络流量数据包会被标记为无效。有时记录此类数据包可能很有用,但通常直接丢弃它们也是可以的。

    sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

  5. 阻止IP地址

    要阻止源自特定IP地址(例如203.0.113.51)的网络连接

    sudo iptables -A INPUT -s 203.0.113.51 -j DROP

    sudo iptables -A INPUT -s 203.0.113.51 -j REJECT

  6. 阻止对网络接口的连接

    要阻止来自特定IP地址(例如203.0.113.51)到特定网络接口(例如eth0)的连接,请使用以下命令:

    iptables -A INPUT -i eth0 -s 203.0.113.51 -j DROP

  7. 允许所有传入的SSH连接

    sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 22 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  8. 允许特定IP地址或子网的传入SSH连接

    要允许来自特定IP地址或子网的传入SSH连接,请指定源地址。例如,如果您想允许整个203.0.113.0/24子网

    sudo iptables -A INPUT -p tcp -s 203.0.113.0/24 --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 22 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  9. 允许传出SSH连接

    如果您的防火墙OUTPUT策略没有设置为ACCEPT,并且您希望允许传出的SSH连接(即您的服务器主动连接到另一台服务器的SSH连接)

    sudo iptables -A OUTPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A INPUT -p tcp --sport 22 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  10. 允许所有传入的HTTP连接

    sudo iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 80 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  11. 允许所有传入的HTTPS连接

    sudo iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 443 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  12. 允许所有传入的HTTP/HTTPS连接

    sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  13. 允许特定IP地址或子网的传入MySQL连接

    要允许来自特定IP地址或子网的传入MySQL连接,请指定源地址。例如,如果您想允许整个203.0.113.0/24子网进行MySQL连接

    sudo iptables -A INPUT -p tcp -s 203.0.113.0/24 --dport 3306 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 3306 -m conntrack --ctstate ESTABLISHED -j ACCEPT

  14. 阻止传出的SMTP邮件

    如果您的服务器不应发送传出邮件,您可能希望阻止此类流量。要阻止传出的SMTP邮件(使用端口25)

    sudo iptables -A OUTPUT -p tcp --dport 25 -j REJECT

    这将配置iptables拒绝在端口25上的所有传出流量。如果您需要拒绝其他端口号对应的服务,请将上述的25端口替换为相应的端口号。

  15. 允许所有传入的SMTP连接

    要允许服务器响应端口25上的SMTP连接,请运行以下命令:

    sudo iptables -A INPUT -p tcp --dport 25 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 25 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    第二个命令允许已建立的SMTP连接的传出流量,只有在OUTPUT策略未设置为ACCEPT时才需要。

  16. 允许所有传入的IMAP连接

    要允许服务器响应IMAP连接(端口143),请运行以下命令:

    sudo iptables -A INPUT -p tcp --dport 143 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 143 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    第二个命令允许已建立的IMAP连接的传出流量,只有在OUTPUT策略未设置为ACCEPT时才需要。

  17. 允许所有传入的POP3连接

    要允许服务器响应POP3连接(端口110),请运行以下命令:

    sudo iptables -A INPUT -p tcp --dport 110 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 110 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    第二个命令允许已建立的POP3连接的传出流量,只有在OUTPUT策略未设置为ACCEPT时才需要。

  18. 允许所有传入的POP3S连接

    要允许服务器响应POP3S连接(端口995),请运行以下命令:

    sudo iptables -A INPUT -p tcp --dport 995 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

    sudo iptables -A OUTPUT -p tcp --sport 995 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    第二个命令允许已建立的POP3S连接的传出流量,只有在OUTPUT策略未设置为ACCEPT时才需要。

  19. 隐藏网络内部主机的IP地址,还能够让局域网内的主机共享公网IP,让使用私网IP的主机能够访问互联网,snat

    iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -j SNAT --to-source 公网IP

    如果公网IP是动态获取的,不是固定的,则可以使用MASQUERADE进行动态的SNAT操作,如下命令表示将10.1网段的报文的源IP修改为eth0网卡中可用的地址。

    iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -o eth0 -j MASQUERADE

  20. 透明代理,将公网ip转为实际服务的内网ip,dnat

    iptables -t nat -I PREROUTING -d 公网IP -p tcp --dport 公网端口 -j DNAT --to-destination 私网IP:端口号

    iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -j SNAT --to-source 公网IP

  21. 端口映射

    iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080

    其他机器访问本机的80端口时,会被映射到8080端口,这也是docker端口映射的原理。

最后引用一波朱老板总结的常用套路,作为本章结尾:

1、规则的顺序非常重要。

如果报文已经被前面的规则匹配到,IPTABLES则会对报文执行对应的动作,通常是ACCEPT或者REJECT,报文被放行或拒绝以后,即使后面的规则也能匹配到刚才放行或拒绝的报文,也没有机会再对报文执行相应的动作了(前面规则的动作为LOG时除外),所以,针对相同服务的规则,更严格的规则应该放在前面。

2、当规则中有多个匹配条件时,条件之间默认存在"与"的关系。

如果一条规则中包含了多个匹配条件,那么报文必须同时满足这个规则中的所有匹配条件,报文才能被这条规则匹配到。

3、在不考虑1的情况下,应该将更容易被匹配到的规则放置在前面。

4、当IPTABLES所在主机作为网络防火墙时,在配置规则时,应着重考虑方向性,双向都要考虑,从外到内,从内到外。

5、在配置IPTABLES白名单时,往往会将链的默认策略设置为ACCEPT,通过在链的最后设置REJECT规则实现白名单机制,而不是将链的默认策略设置为DROP,如果将链的默认策略设置为DROP,当链中的规则被清空时,管理员的请求也将会被DROP掉。

3. go-iptables安装

go-iptables是组件库,直接一波import "github.com/coreos/go-i..."​,然后go mod tidy一番,就准备兴致冲冲的跑一波自带的测试用例集,没想到上来就是4个error:

这还了得,我直接去go-iptables的仓库issue上瞅瞅有没有同道中人,果然发现一个类似问题:

虽然都是test failures,但是错的原因是不一样的,但是看他的版本是1.8的,所以我怀疑是我的iptables的版本太老了,一个iptables -v看一眼:

直接用yum update好像不能升级,yum search也没看到最新版本,看来只能下载iptables源码自己编译了,一套连招先打出来:

bash 复制代码
git clone git://git.netfilter.org/iptables
cd libnftnl
sh autogen.sh
./configure
make
make install

不出意外的话,那就得出点意外了:

那就继续下载源码安装吧,然后发现libmnl​又依赖libnftnl​,所以直接一波大招,netfilter全家桶全安装:

bash 复制代码
yum install autoconf autogen nftables.x86_64 -y

git clone git://git.netfilter.org/libmnl
cd libmnl
sh autogen.sh
./configure
make
make install

git clone git://git.netfilter.org/libnftnl
cd libnftnl
sh autogen.sh
./configure
make
make install

PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH

# 重新进到iptables目录
./configure
make
make install

Finally,再跑一次测试用例就成功了,下面就可以愉快的阅读源码了:

4. 如何使用go-iptables

go 复制代码
// 新建ipv4 iptables
// 默认是ipv4,等待iptables lock的超时时间为永远,无限等待
ipt, err := New()
if err != nil {
	panic(fmt.Sprintf("New failed: %v", err))
}

// 新建ipv6 iptables
ip6t, err := NewWithProtocol(ProtocolIPv6)
if err != nil {
	panic(fmt.Sprintf("NewWithProtocol(ProtocolIPv6) failed: %v", err))
}

// 列出filter表的所有链
// output: [INPUT FORWARD OUTPUT DOCKER DOCKER-ISOLATION FORWARD FORWARD_IN_ZONES_SOURCE OUTPUT_direct]
originalListChain, err := ipt.ListChains("filter")

// 清除filter表内的某个链的规则,如果没有这个链的话会创建一个出来
chain := "TEST-91382"
err = ipt.ClearChain("filter", chain)

// filter表是否存在"TEST-91382"这个链
// output: true
exists, err := ipt.ChainExists("filter", chain)

// 删除filter表内的某个链,但不能删除非空的链,否则会报err ---> e.IsNotExist()
err = ipt.DeleteChain("filter", chain)

// filter表,"TEST-91382"链尾部追加一条规则
err = ipt.Append("filter", chain, "-s", "0/0", "-j", "ACCEPT")

// 自定义链重命名
newChain := "TEST-91383"
err = ipt.RenameChain("filter", chain, newChain)

// 清空链的规则,并删除这个链
err = ipt.ClearAndDeleteChain("filter", chain)

address1 = "203.0.113.1/32"
address2 = "203.0.113.2/32"
subnet1 = "192.0.2.0/24"
subnet2 = "198.51.100.0/24"
// address1 = "2001:db8::1/128"
// address2 = "2001:db8::2/128"
// subnet1 = "2001:db8:a::/48"
// subnet2 = "2001:db8:b::/48"

// 尾部追加,但如果链中已经存在一样的规则,那就不会重复新增了
err = ipt.AppendUnique("filter", chain, "-s", subnet1, "-d", address1, "-j", "ACCEPT")

// 指定位置新增规则
err = ipt.Insert("filter", chain, 2, "-s", subnet2, "-d", address2, "-j", "ACCEPT")

// 指定位置新增规则, 但如果链中已经存在一样的规则,那就不会重复新增了
err = ipt.InsertUnique("filter", chain, 2, "-s", subnet2, "-d", address2, "-j", "ACCEPT")

// 删除规则
err = ipt.Delete("filter", chain, "-s", subnet1, "-d", address2, "-j", "ACCEPT")

// 修改规则
err = ipt.Replace("filter", chain, 1, "-s", subnet2, "-d", address2, "-j", "ACCEPT")

// 展示链中的所有具体规则
rules, err := ipt.List("filter", chain)

// 展示链中某一位置的规则
rule, err := ipt.ListById("nat", "PREROUTING", 1)

// 删除规则,如果存在的话
err = ipt.DeleteIfExists("filter", chain, "-s", address1, "-d", subnet2, "-j", "ACCEPT")

// 一些error信息
var isNotExistPatterns = []string{
	"Bad rule (does a matching rule exist in that chain?).\n",
	"No chain/target/match by that name.\n",
	"No such file or directory",
	"does not exist",
}

5. go-iptables源码分析

关键结构体IPTables

go 复制代码
type IPTables struct {
	path              string   // iptables可执行路径
	proto             Protocol // ipv4还是v6
	hasCheck          bool     // 是否支持--check命令
	hasWait           bool     // 是否支持--wait命令
	waitSupportSecond bool     // 是否支持--wait second 命令
	hasRandomFully    bool
	v1                int      // 版本号1.8.1 -> v1=1, v2=8, v3=1
	v2                int
	v3                int
	mode              string   // the underlying iptables operating mode, e.g. nf_tables
	timeout           int      // time to wait for the iptables lock, default waits forever
}

初始化函数func New(opts ...option) (*IPTables, error)​,流程如下:

go 复制代码
可以认为是一个建造者模式,灰常优雅
type option func(*IPTables)

func IPFamily(proto Protocol) option {
	return func(ipt *IPTables) {
		ipt.proto = proto
	}
}

func Timeout(timeout int) option {
	return func(ipt *IPTables) {
		ipt.timeout = timeout
	}
}

func Path(path string) option {
	return func(ipt *IPTables) {
		ipt.path = path
	}
}

func New(opts ...option) (*IPTables, error)
->
	for _, opt := range opts {
		opt(ipt)
	}

// example
ipt2, err := New(Timeout(5))
ipt2, err := New(IPFamily(proto), Timeout(0))

几个重要函数的实现:

  1. ​func (ipt *IPTables) ListChains(table string) ([]string, error)​

    通过执行命令行 iptables -t table名 -S​

    返回的字符串通过词法解析即可:

    Format is the following:

    -P OUTPUT ACCEPT

    -N Custom

  2. ​func (ipt *IPTables) ClearChain(table, chain string) error​

    命令行执行:先创建再flush ipt.run("-t", table, "-N", chain)​+ ipt.run("-t", table, "-F", chain)​

  3. ​Stats​统计rules including the byte and packet counts​

    执行命令行:"-t", table, "-L", chain, "-n", "-v", "-x"​

    分词解析:0=pkts 1=bytes 2=target 3=prot 4=opt 5=in 6=out 7=source 8=destination 9=options​

其他好像也米有什么,这里面就主要介绍一下,他的命令行执行是怎么实现的:

go 复制代码
func (ipt *IPTables) runWithOutput(args []string, stdout io.Writer) error {
	args = append([]string{ipt.path}, args...)
	if ipt.hasWait {
        // 1.6.0以后的版本支持--wait命令,可以获取iptables锁
		args = append(args, "--wait")
		if ipt.timeout != 0 && ipt.waitSupportSecond {
			args = append(args, strconv.Itoa(ipt.timeout))
		}
	} else {
        // 不支持我就自己创建一个xtables文件锁
		fmu, err := newXtablesFileLock()
		if err != nil {
			return err
		}
        // tryLock尝试在不阻塞的情况下对xtables锁文件进行独占锁定,拿不到会报err -> resource temporarily unavailable
        // 
        // best-effort机制,flock只是用于检测文件是否被加锁,针对文件已经被加锁,
        // 另一个进程写入数据的情况,内核不会阻止这个进程的写入操作
        //
        // flock锁的释放非常具有特色,即可调用LOCK_UN参数来释放文件锁,
        // 也可以通过关闭fd的方式来释放文件锁(flock的第一个参数是fd),意味着flock会随着进程的关闭而被自动释放掉。
        // ps: flock其中的一个使用场景为:检测进程是否已经存在。
		ul, err := fmu.tryLock()
		if err != nil {
			syscall.Close(fmu.fd)
			return err
		}
		defer func() {
			_ = ul.Unlock()
		}()
	}

	var stderr bytes.Buffer
	cmd := exec.Cmd{
		Path:   ipt.path,
		Args:   args,
		Stdout: stdout,
		Stderr: &stderr,
	}
	// 关键调用cmd.Run()
	if err := cmd.Run(); err != nil {
		switch e := err.(type) {
		case *exec.ExitError:
			return &Error{*e, cmd, stderr.String(), nil}
		default:
			return err
		}
	}

	return nil
}

6. Reference

相关推荐
小蜗牛慢慢爬行42 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
wm10431 小时前
java web springboot
java·spring boot·后端
龙少95433 小时前
【深入理解@EnableCaching】
java·后端·spring
溟洵5 小时前
Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)
linux·运维·数据库·后端·sql·mysql
SomeB1oody7 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody8 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
啦啦右一9 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien9 小时前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手11 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生11 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl