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 ---> 对应规则匹配到的报文的个数/字节数:
- 新增+删除
新增一条拒绝的报文,我们直接把docker0网关ip给禁了,这样就无法通过主机ping通docker容器了(如果有疑问,下面有解答,会涉及docker的一些小姿势):
iptables -I INPUT -s 172.17.0.1 -j DROP (-I不指定序号的话就是头插)
iptables -t filter -D INPUT 1
可见已经生效了,拦截了ping包,随后我删除了这条规则,又能够ping通了
-
修改
通过-R可以进行规则修改,但能修改的部分比较少,只能改action,所以我的建议是先通过编号删除规则,再在原编号位置添加一条规则。
-
持久化
当我们对规则进行了修改以后,如果想要修改永久生效,必须使用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规则
- 自定义链
我们可以创建自己的规则集,这样统一管理会非常方便,比如说,我现在要创建一系列的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常见的配置场景
-
允许回环连接
例如,如果您运行ping localhost或ping 127.0.0.1,您的服务器将使用回环接口对自身进行ping测试。如果 您配置应用程序服务器以使用本地主机地址连接到数据库服务器,也将使用回环接口。
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
-
允许已建立和相关的传入连接
由于网络流量通常需要双向(传入和传出)才能正常工作,通常会创建一个防火墙规则来允许已建立和相关的传入流量,以便服务器允许由服务器自身发起的传出连接的返回流量。
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
-
允许内部网络访问外部网络
假设eth0是您的外部网络接口,而eth1是您的内部网络接口,以下命令将允许内部网络访问外部网络:
sudo iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
-
丢弃无效数据包
一些网络流量数据包会被标记为无效。有时记录此类数据包可能很有用,但通常直接丢弃它们也是可以的。
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
-
阻止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
-
阻止对网络接口的连接
要阻止来自特定IP地址(例如203.0.113.51)到特定网络接口(例如eth0)的连接,请使用以下命令:
iptables -A INPUT -i eth0 -s 203.0.113.51 -j DROP
-
允许所有传入的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
-
允许特定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
-
允许传出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
-
允许所有传入的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
-
允许所有传入的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
-
允许所有传入的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
-
允许特定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
-
阻止传出的SMTP邮件
如果您的服务器不应发送传出邮件,您可能希望阻止此类流量。要阻止传出的SMTP邮件(使用端口25)
sudo iptables -A OUTPUT -p tcp --dport 25 -j REJECT
这将配置iptables拒绝在端口25上的所有传出流量。如果您需要拒绝其他端口号对应的服务,请将上述的25端口替换为相应的端口号。
-
允许所有传入的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时才需要。
-
允许所有传入的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时才需要。
-
允许所有传入的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时才需要。
-
允许所有传入的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时才需要。
-
隐藏网络内部主机的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
-
透明代理,将公网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
-
端口映射
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))
几个重要函数的实现:
-
func (ipt *IPTables) ListChains(table string) ([]string, error)
通过执行命令行 iptables -t table名 -S
返回的字符串通过词法解析即可:
Format is the following:
-P OUTPUT ACCEPT
-N Custom
-
func (ipt *IPTables) ClearChain(table, chain string) error
命令行执行:先创建再flush ipt.run("-t", table, "-N", chain)+ ipt.run("-t", table, "-F", chain)
-
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
- dev.to/isabelcmdco...
- colobu.com/2023/10/30/...
- zhuanlan.zhihu.com/p/618848653
- blog.csdn.net/weixin_4214...
- www.cnblogs.com/marility/p/...
- www.zsythink.net/archives/17...
- blog.csdn.net/u013538795/...
- www.cnblogs.com/bakari/p/10...
- www.zhihu.com/question/36...
- netfilter.org/ & netfilter.org/projects/ip...
- zhuanlan.zhihu.com/p/93630586
- www.cnblogs.com/semwu/p/157...
- zhuanlan.zhihu.com/p/395752839
- www.digitalocean.com/community/t...
- blog.csdn.net/sinat_38816...
- zhuanlan.zhihu.com/p/25134841
- blog.csdn.net/asmartkil