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

相关推荐
zzzll11112 小时前
SpringBoot 入门与实践指南
java·spring boot·后端
newerp2 小时前
Go :结构体嵌入、sort.Interface 与 io.Reader/Writer
后端
SamDeepThinking2 小时前
如何维持缓存的一致性?
后端·面试·程序员
泡海椒2 小时前
还在手动把 Postman 和浏览器的 curl 转成 Java 代码?这个框架让你一键粘贴直接用
后端
用户8181870627462 小时前
第13章 死锁诊断实战
后端
晨曦中的暮雨2 小时前
Redis 有哪些常见数据结构?它们的底层实现和使用场景是什么?
java·后端·八股
枫叶V2 小时前
Prompt Injection 防不住怎么办?从 Source-Sink 模型设计 Agent 安全边界
后端·agent
用户8181870627462 小时前
第14章 线程池 RejectedExecutionException 与拒绝策略源码
后端
Leo2822 小时前
LangGraph:把 Agent 从“会回答”推进到“可控执行”的工程架构
后端
铁皮饭盒3 小时前
网页端OCR, 加载6mb大小模型, 又快又准, 百度这次真香
前端·后端