混沌工程 ChaosBlade - 深度解析网络故障模拟技术实现

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

引言

在上一篇的文章中,我们介绍如何使用混沌工程的开源项目 ChaosBlade 进行网络故障模拟。本文将深入解析网络故障模拟的底层核心技术。

网络故障模拟如何实现,如果没有了解过内核与网络的同学可能会无从下手。如果耐心看完本文你会发现,实现网络故障的模拟并不"复杂"。

ChaosBlade 网络故障模拟 - 场景

目前 ChaosBlade 支持的网络故障场景,包括数据包类故障,DNS故障,端口占用故障,网络屏蔽故障

数据包 故障 - 原理解析

Linux TC

介绍

首先我们需要先简单了解下Linux TC ,它是模拟网络数据包故障中最核心,最基础的工具。

tc(Traffic Control)是 Linux 中用于管理和控制网络流量的工具。它是 iproute2 套件的一部分,通过它可以对网络接口进行带宽限制、流量整形、流量分类等操作,从而优化网络性能,确保关键应用的网络资源需求。在 ChaosBlade 中就是利用 Linux tc 实现的网络故障模拟,

主要功能

Linux tc(Traffic Control)使用队列(Queueing Disciplines, qdisc)、类(Classes)和过滤器(Filters)来管理和控制网络流量。下面是对这些概念以及它们如何协同工作的详细介绍。

队列规则(qdisc)

队列规则决定了如何处理网络接口上的数据包。每个网络接口都至少有一个根队列规则(root qdisc)。qdisc 可以是简单的,也可以是复杂的层次结构。

常见的 qdisc 类型

  • pfifo_fast:默认的无策略 FIFO 队列。
  • htb(Hierarchical Token Bucket):用于分层带宽限制。
  • tbf(Token Bucket Filter):用于精确的带宽控制。
  • fq_codel(Fair Queue Controlled Delay):用于减少延迟和缓解 bufferbloat。
  • netem:用于网络延迟、丢包等网络条件的模拟。

类(Classes)

类是队列规则的一部分,用于进一步细分和管理流量。它们在一些高级 qdisc(如 htbhfsc)中使用,可以为不同类型的流量分配不同的带宽和优先级。

过滤器(Filters)

过滤器用于匹配数据包,并将其分配到特定的类。过滤器可以基于各种数据包属性(如 IP 地址、端口号等)进行匹配。

具体示例

限制带宽

下面是一个具体示例,展示如何使用 tc 配置队列规则、类和过滤器来限制和管理网络流量。

场景:限制网络接口 eth0 的上传带宽,并对不同类型的流量进行分类和优先级设置。

添加根队列规则:

使用 prio 优先级队列规则作为根 qdisc:

csharp 复制代码
tc qdisc add dev eth0 root handle 1: htb default 30

创建主类:

创建一个主类,设置带宽限制为 100Mbps:

kotlin 复制代码
tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit 

创建子类:

  1. 为不同的流量类型创建子类:

为高优先级流量(如 VoIP)分配 20Mbps:

kotlin 复制代码
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 20mbit ceil 100mbit 

为中等优先级流量(如 HTTP)分配 30Mbps:

kotlin 复制代码
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 30mbit ceil 100mbit 

为低优先级流量(如批量传输)分配 10Mbps:

kotlin 复制代码
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 10mbit ceil 100mbit 

添加过滤器:

  1. 使用 u32 过滤器将特定流量分配到相应的类:

将 VoIP 流量(假设端口为 5060) 分配到高优先级类:

sql 复制代码
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 5060 0xffff flowid 1:10 

将 HTTP 流量(端口 80) 分配到中等优先级类:

sql 复制代码
tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dport 80 0xffff flowid 1:20 

将其他流量分配到低优先级类:

sql 复制代码
tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip sport 0 0x0000 flowid 1:30 

下图是:tc htb(层次令牌桶)队列规则的工作原理

丢包

在 Linux 中,可以使用 tc(Traffic Control)和 netem(Network Emulator)来模拟丢包。netemtc 的一个扩展,用于模拟网络特性,如延迟、抖动、丢包和重排序。

下面是如何使用 tcnetem 来实现丢包的详细步骤。

1. 检查和加载 netem 模块

首先,确保 netem 模块已加载:

sudo modprobe sch_netem 

2. 添加根队列规则

为网络接口(如 eth0)添加一个根队列规则:

csharp 复制代码
sudo tc qdisc add dev eth0 root handle 1: netem loss 10% 

这个命令的意思是,在 eth0 接口上添加一个 netem 根队列规则,并使 10% 的数据包丢失。

3. 检查配置

你可以使用以下命令来检查配置是否生效:

sql 复制代码
tc qdisc show dev eth0 

4. 移除配置

如果你想移除这个配置,可以使用以下命令:

css 复制代码
sudo tc qdisc del dev eth0 root

ChaosBlade - 源码分析

网络数据包 - 重排序、丢包、损坏、延迟、重复

入口

以ChaosBlade v1.7.1版本为例,在exec#network#tc文件夹中(github.com/chaosblade-...),可以看到和数据包相关的故障模拟代码实现。

我们以网络丢包(network_loss.go)为例,进行代码分析,其他场景的实现原理几乎一样。在丢包场景中首先是解析用户传递的参数,然后调用start func,在start func中,可以看到只是创建了classRule变量,在classRule中设置了 netem loss (其他数据包模拟场景也是只有这里不同)

go 复制代码
func (nle *NetworkLossExecutor) start(netInterface, localPort, remotePort, excludePort, destIp, excludeIp, percent string,
  ignorePeerPort, force bool, protocol string, ctx context.Context) *spec.Response {
  classRule := fmt.Sprintf("netem loss %s%%", percent)
  return startNet(ctx, netInterface, classRule, localPort, remotePort, excludePort, destIp, excludeIp, force, ignorePeerPort, protocol, nle.channel)
}

核心函数 - 故障注入

由于startNet代码比较长,这里只讲解最关键的部分。 startNet就是模拟网络数据包故障最关键的函数,本质上在startNet中会根据用户传入的参数,转换成不同的tc netem指令并执行。

go 复制代码
func startNet(ctx context.Context, netInterface, classRule, localPort, remotePort, excludePort, destIp, excludeIp string, force, ignorePeerPorts bool, protocol string, cl spec.Channel) *spec.Response {
	var localPortRanges, remotePortRanges, excludePortRanges [][]int
	var err error
	// Only interface flag
	if localPort == "" && remotePort == "" && excludePort == "" && destIp == "" && excludeIp == "" && protocol == "" {
		return cl.Run(ctx, "tc", fmt.Sprintf(`qdisc add dev %s root %s`, netInterface, classRule))
	}
	response = addQdiscForDL(cl, ctx, netInterface)


	// only contains excludePort or excludeIP
	if localPort == "" && remotePort == "" && destIp == "" && protocol == "" {
		// Add class rule to 1,2,3 band, exclude port and exclude ip are added to 4 band
		args := buildNetemToDefaultBandsArgs(netInterface, classRule)
		excludeFilters := buildExcludeFilterToNewBand(netInterface, excludePortRanges, excludeIp)
		response := cl.Run(ctx, "tc", args+excludeFilters)
		if !response.Success {
			stopNet(ctx, netInterface, cl)
		}
		return response
	}


	destIpRules := getIpRules(destIp)
	excludeIpRules := getIpRules(excludeIp)


	// local port or remote port
	return executeTargetPortAndIpWithExclude(ctx, cl, netInterface, classRule, localPortRanges, remotePortRanges, destIpRules,
		excludePortRanges, excludeIpRules, protocol)
}
  1. 如果用户只设置了网卡,端口、ip等参数都没有填,则默认对整个网卡进行丢包,对网卡添加tc根队列并且丢包即可
    1. 如: tc qdisc add dev eth0 root netem loss 100%
  2. 如果用户设置了其他参数,则先为当前网卡 eth0 上添加一个根(root)优先级(priority)队列规则(qdisc),它具有4个带宽控制通道(band)
    1. 如addQdiscForDL中的 tc qdisc add dev eth0 root handle 1: prio bands 4
  3. 如果只设置了加白ip或者加白端口
    1. 先设置三个模拟网络丢包(netem)队列,分别对不同的根队列的前三个带宽控制通道,每个队列都模拟 100% 的数据包丢失。然后设置一个优先级队列(prio)规则对应根队列的第四个带宽控制通道。
csharp 复制代码
tc qdisc add dev eth0 parent 1:1 netem loss 100% && \
tc qdisc add dev eth0 parent 1:2 netem loss 100% && \
tc qdisc add dev eth0 parent 1:3 netem loss 100% && \
tc qdisc add dev eth0 parent 1:4 handle 40: prio
    1. 然后设置加白规则,让加白的ip或端口的流量进入到第四个带宽控制通道中,这样加白的ip和端口流量就不会受到丢包影响
sql 复制代码
tc filter add dev eth0 parent 1: prio 4 protocol ip u32 match ip dst 192.168.1.5 flowid 1:4
  1. 如果设置了加黑ip/端口
    1. 先设置一个模拟网络丢包(netem)队列,对应根队列的第四个带宽控制通道,并且设置自己的标识符号为40
      1. 如tc qdisc add dev eth0 parent 1:4 handle 40: netem loss 100%
    2. 如果设置了本地的加黑端口,则添加过滤规则,将访问加黑的端口流量路由到第四个带宽控制通道
      1. 如tc filter add dev eth0 parent 1: prio 4 protocol ip u32 match ip sport 8090 0xffff flowid 1:4
    3. 如果设置了本地的加黑ip,则添加过滤规则,将访问加黑的ip流量路由到第四个带宽控制通道
      1. 如tc filter add dev eth0 parent 1: prio 4 protocol ip u32 match ip dst 192.168.1.5 0xffff flowid 1:4
    4. 如果设置加黑端口/ip的同时也设置了加白的端口/ip,则添加过滤规则,将加白的流量路由到1,2,3其中一条带宽控制通道即可
      1. 如tc filter add dev eth0 parent 1: prio 3 protocol ip u32 match ip dst 192.168.1.5 flowid 1:3

核心函数 - 故障恢复

故障恢复的代码逻辑相对简单,只是执行tc命令,清除对应的tc filter规则和根队列即可,

go 复制代码
func stopNet(ctx context.Context, netInterface string, cl spec.Channel) *spec.Response {
	if os.Getuid() != 0 {
		return spec.ReturnFail(spec.Forbidden, fmt.Sprintf("tc no permission"))
	}
	response := cl.Run(ctx, "tc", fmt.Sprintf(`filter show dev %s parent 1: prio 4`, netInterface))
	if response.Success && response.Result != "" {
		response = cl.Run(ctx, "tc", fmt.Sprintf(`filter del dev %s parent 1: prio 4`, netInterface))
		if !response.Success {
			log.Errorf(ctx, "tc del filter err, %s", response.Err)
		}
	}
	return cl.Run(ctx, "tc", fmt.Sprintf(`qdisc del dev %s root`, netInterface))
}

小结

本质上Chaosblade 实现网络数据包故障模拟的手段就是接收用户传递的参数,然后通过参数组合成不同的 tc + netem 指令,实际上只用到了tc的优先级队列,并不复杂。

DNS 篡改 - 原理解析

ChaosBlade - 源码分析

入口

在DNS 篡改场景中实现相对较为简单,以ChaosBlade v1.7.1版本为例,在exec#network#network_dns.go文件中(github.com/chaosblade-...)

核心函数 - 故障注入

go 复制代码
const hosts = "/etc/hosts"


func (ns *NetworkDnsExecutor) start(ctx context.Context, domain, ip string) *spec.Response {
  domain = strings.ReplaceAll(domain, sep, " ")
  dnsPair := createDnsPair(domain, ip)
  response := ns.channel.Run(ctx, "grep", fmt.Sprintf(`-q "%s" %s`, dnsPair, hosts))
  if response.Success {
   return spec.ReturnFail(spec.OsCmdExecFailed, fmt.Sprintf("%s has been exist", dnsPair))
  }
  return ns.channel.Run(ctx, "echo", fmt.Sprintf(`"%s" >> %s`, dnsPair, hosts))
}
  1. 将用户输入的多个domain 域名进行拼接组合,组合后的形式如(www.baidu.com 127.0.0.1 #chaosblade)
  2. 利用grep -q 查询组合后的字符串是否在/etc/hosts文件中。
  3. 如果在的话,说明不需要进行故障注入,直接返回成功
  4. 如果不在,使用echo 将组合后的字符写入到/etc/hosts文件,这样就完成了域名的篡改

核心函数 - 故障恢复

go 复制代码
const tmpHosts = "/tmp/chaos-hosts.tmp"


func (ns *NetworkDnsExecutor) stop(ctx context.Context, domain, ip string) *spec.Response {
  domain = strings.ReplaceAll(domain, sep, " ")
  dnsPair := createDnsPair(domain, ip)
  response := ns.channel.Run(ctx, "grep", fmt.Sprintf(`-q "%s" %s`, dnsPair, hosts))
  if !response.Success {
   // nothing to do
   return spec.Success()
  }


  response = ns.channel.Run(ctx, "cat", fmt.Sprintf(`%s | grep -v "%s" > %s && cat %s > %s`,
   hosts, dnsPair, tmpHosts, tmpHosts, hosts))
  if !response.Success {
   return response
  }
  return ns.channel.Run(ctx, "rm", fmt.Sprintf(`-rf %s`, tmpHosts))
}
  1. 将用户输入的多个domain 域名进行拼接组合,组合后的形式如(www.baidu.com 127.0.0.1 #chaosblade)
  2. 同样利用grep -q 查询组合后的字符串是否在/etc/hosts文件中
  3. 如果不在的话,说明不需要进行故障恢复,直接返回成功
  4. 如果在的话要执行如下指令
shell 复制代码
 cat /etc/hosts | grep -V www.baidu.com 127.0.0.1 #chaosblade > /tmp/chaos-hosts.tmp && cat /tmp/chaos-hosts.tmp > /etc/hosts
    1. cat /etc/hosts
    2. 输出 /etc/hosts 文件的内容到标准输出。
    3. grep -v www.baidu.com 127.0.0.1 #chaosblaade
    4. 读取标准输入,输出不包含当前字符串的行。
    5. > /tmp/chaos-hosts.tmp
    6. 将 不包含当前字符串的行输出重定向到临时文件 /tmp/chaos-hosts.tmp
    7. cat /tmp/chaos-hosts.tmp > /etc/hosts
    8. 将临时文件 /tmp/chaos-hosts.tmp 的内容覆盖写回到 /etc/hosts 文件。

小结

本质上dns 篡改故障就是根据用户的输入来修改/etc/hosts文件中的内容,额外需要注意的是 故障恢复是利用了临时文件,将注入故障前的/etc/hosts内容写入到临时文件后,在刷回到/etc/hosts文件中的。

端口 占用 - 原理解析

ChaosBlade - 源码分析

入口

以ChaosBlade v1.7.1版本为例,端口占用的实现在exec#network#network_occupy.go文件中(github.com/chaosblade-...)

核心函数 - 故障注入

golang 复制代码
func (oae *OccupyActionExecutor) Exec(uid string, ctx context.Context, model *spec.ExpModel) *spec.Response {
  if force == "true" {
   // search the process which is using the port and kill it
   // netstat -tuanp | awk '{print $4,$7}'| grep ":8182"|head -n 1
   response := oae.channel.Run(ctx, "netstat",
    fmt.Sprintf(`-tuanp | awk '{print $4,$7}' | grep ":%s" | head -n 1 | awk '{print $NF}'`, port))
   // 127.0.0.1:8182 2814/hblog
   if !response.Success {
    return response
   }
   processMsg := strings.TrimSpace(response.Result.(string))
   if processMsg != "" {
    idx := strings.Index(processMsg, "/")
    if idx > 0 {
     pid := processMsg[:idx]
     if pid != "" {
      response := oae.channel.Run(ctx, "kill", fmt.Sprintf("-9 %s", pid))
      if !response.Success {
       return response
      }
     }
    }
   }
  }
  // start occupy process
  return oae.start(port, ctx)
}


func (oae *OccupyActionExecutor) start(port string, ctx context.Context) *spec.Response {
  err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
  if err != nil {
   return spec.ReturnFail(spec.OsCmdExecFailed, fmt.Sprintf("listen and serve fail %v", err))
  }
  return spec.Success()
}
  1. 如果用户在注入故障时,如果设置了force参数,则强制停止当前正在使用待占用端口的进程
  2. 执行 netstat -tuanp | awk '{print <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 , 4, </math>4,7}' | grep ":port" | head -n 1 | awk '{print NF},找到使用当前端口的进程pid 1. 1. 这条命令链用于从 netstat 的输出中提取与端口 8080 相关的进程的 PID。让我们一步步分析这条命令的作用:
    1. netstat -tuanp
    2. 列出所有正在监听的 TCP 和 UDP 端口以及相关的进程。选项解释如下: 1. 1. -t: 显示 TCP 连接。
      1. -u: 显示 UDP 连接。
      2. -a: 显示所有连接和监听端口。
      3. -n: 以数字形式显示地址和端口号。
      4. -p: 显示每个连接所属的进程和 PID(需要超级用户权限)。
    3. awk '{print $4,$7}'
    4. netstat 的输出中提取第四列(本地地址和端口)和第七列(PID/程序名)。
    5. grep ":8080"
    6. 过滤出本地地址包含端口 8080 的行。
    7. head -n 1
    8. 从过滤后的结果中取出第一行。这是为了避免多个匹配结果时,只取第一个。
    9. awk '{print $NF}'
    10. 从第一行中提取最后一列($NF 代表最后一列),即 PID/程序名。 1. 根据pid执行kill指令,将占用当前端口的进程停止
  3. 启动http server 使用用户指定的端口,这样就完成了端口占用的故障模拟

核心函数 - 故障恢复

故障恢复就是找到启动chaos启动的http server然后kill掉,实现比较简单,这里就不贴源码了。

小结

端口占用场景本质上是通过用户指定的端口来启动一个http server,达到占用端口的场景模拟

网络 屏蔽 - 原理解析

iptables 介绍

了解网络屏蔽的故障实现,需要先了解iptables 。iptables是 Linux 系统中的一个用户空间实用程序,允许系统管理员配置、维护和检查 IP 数据包过滤规则。它是 Linux 内核中 Netfilter 框架的一部分,用于实现防火墙、网络地址转换 (NAT) 和数据包过滤等功能。

主要功能

  1. 数据包过滤:可以根据源地址、目的地址、协议、端口等各种条件来过滤网络数据包。
  2. 网络地址转换 (NAT):实现 IP 地址的转换,如源地址转换 (SNAT) 和目的地址转换 (DNAT)。
  3. 流量控制:控制数据包的流向,如接受、拒绝、丢弃或转发。
  4. 记录和监控:可以记录数据包信息以供分析和监控。

基本概念

  1. 表(tables):iptables 使用不同的表来处理不同类型的操作,每个表包含多个链。常见的表有:
  • filter:默认表,用于数据包过滤。
  • nat:用于网络地址转换。
  • mangle:用于修改数据包。
  • raw:用于配置数据包的处理方式,绕过连接追踪。
  • security:用于安全相关的过滤。
  1. 链(chains):每个表包含一个或多个链,每个链是一系列有序的规则。常见的内置链有:
  • INPUT:处理进入本机的数据包。
  • OUTPUT:处理从本机发出的数据包。
  • FORWARD:处理通过本机转发的数据包。
  • PREROUTING:处理进入本机在路由前的数据包(主要用于 NAT)。
  • POSTROUTING:处理离开本机在路由后的数据包(主要用于 NAT)。
  1. 规则(rules):每个链由一系列规则组成,规则定义了数据包处理的条件和动作。常见的动作有:
  • ACCEPT:接受数据包。
  • DROP:丢弃数据包,不发送任何响应。
  • REJECT:拒绝数据包,并发送响应。
  • LOG:记录数据包信息。
  • SNAT:源地址转换。
  • DNAT:目的地址转换。
  • MASQUERADE:一种特殊的源地址转换,常用于动态 IP。

ChaosBlade - 源码分析

入口

以ChaosBlade v1.7.1版本为例,端口占用的实现在exec#network#network_drop.go文件中(github.com/chaosblade-...)

核心函数 - 故障注入

golang 复制代码
func (ne *NetworkDropExecutor) start(sourceIp, destinationIp, sourcePort, destinationPort, stringPattern, networkTraffic string, ctx context.Context) *spec.Response {
	if destinationIp == "" && sourceIp == "" && destinationPort == "" && sourcePort == "" && stringPattern == "" {
		return spec.ReturnFail(spec.OsCmdExecFailed, "must specify ip or port or string flag")
	}


	var response *spec.Response
	netFlows := []string{"INPUT", "OUTPUT"}
	if networkTraffic == "in" {
		netFlows = []string{"INPUT"}
	}
	if networkTraffic == "out" {
		netFlows = []string{"OUTPUT"}
	}
	for _, netFlow := range netFlows {
		tcpArgs := fmt.Sprintf("-A %s -p tcp", netFlow)
		udpArgs := fmt.Sprintf("-A %s -p udp", netFlow)
		if sourceIp != "" {
			tcpArgs = fmt.Sprintf("%s -s %s", tcpArgs, sourceIp)
			udpArgs = fmt.Sprintf("%s -s %s", udpArgs, sourceIp)
		}
		if destinationIp != "" {
			tcpArgs = fmt.Sprintf("%s -d %s", tcpArgs, destinationIp)
			udpArgs = fmt.Sprintf("%s -d %s", udpArgs, destinationIp)
		}
		if sourcePort != "" {
			if strings.Contains(sourcePort, ",") {
				tcpArgs = fmt.Sprintf("%s -m multiport --sports %s", tcpArgs, sourcePort)
				udpArgs = fmt.Sprintf("%s -m multiport --sports %s", udpArgs, sourcePort)
			} else {
				tcpArgs = fmt.Sprintf("%s --sport %s", tcpArgs, sourcePort)
				udpArgs = fmt.Sprintf("%s --sport %s", udpArgs, sourcePort)
			}
		}
		if destinationPort != "" {
			if strings.Contains(destinationPort, ",") {
				tcpArgs = fmt.Sprintf("%s -m multiport --dports %s", tcpArgs, destinationPort)
				udpArgs = fmt.Sprintf("%s -m multiport --dports %s", udpArgs, destinationPort)
			} else {
				tcpArgs = fmt.Sprintf("%s --dport %s", tcpArgs, destinationPort)
				udpArgs = fmt.Sprintf("%s --dport %s", udpArgs, destinationPort)
			}
		}
		if stringPattern != "" {
			tcpArgs = fmt.Sprintf("%s -m string --string %s --algo bm", tcpArgs, stringPattern)
			udpArgs = fmt.Sprintf("%s -m string --string %s --algo bm", udpArgs, stringPattern)
		}
		tcpArgs = fmt.Sprintf("%s -j DROP", tcpArgs)
		udpArgs = fmt.Sprintf("%s -j DROP", udpArgs)
		response = ne.channel.Run(ctx, "iptables", fmt.Sprintf(`%s`, tcpArgs))
		if !response.Success {
			ne.stop(sourceIp, destinationIp, sourcePort, destinationPort, stringPattern, networkTraffic, ctx)
			return response
		}
		response = ne.channel.Run(ctx, "iptables", fmt.Sprintf(`%s`, udpArgs))
		if !response.Success {
			ne.stop(sourceIp, destinationIp, sourcePort, destinationPort, stringPattern, networkTraffic, ctx)
		}
	}
	return response
}

该方法根据传入的参数配置 iptables 规则来阻止特定的网络流量。可以指定源 IP、目的 IP、源端口、目的端口以及匹配的字符串模式。

  1. 参数验证:
  2. 如果 destinationIpsourceIpdestinationPortsourcePortstringPattern 全部为空,则返回失败响应,因为至少需要指定一个过滤条件。
  3. 配置网络流量方向:
  4. 如果 networkTraffic 是 "in",则只处理 INPUT 链。 如果 networkTraffic 是 "out",则只处理 OUTPUT 链。 否则同时处理 INPUTOUTPUT 链。
  5. 构建 iptables 命令:
  6. 遍历 netFlows(可能是 INPUT 和/或 OUTPUT)。
    1. 根据传入参数构建 tcpArgsudpArgs,用于匹配 TCP 和 UDP 数据包。
    2. 添加 IP 地址和端口条件。
    3. 如果指定了 stringPattern,则添加相应的字符串匹配条件。
    4. 最后添加 -j DROP 动作,表示丢弃匹配的数据包。
  7. 执行构建好的 iptables 命令来添加 TCP 规则。

核心函数 - 故障恢复

故障恢复的源码和故障注入很像,同样是构建iptables 这些匹配规则,只不过最后执行iptables -D 将这些规则进行删除,从而恢复故障

小结

网络屏蔽场景本质上是通过动态构建和执行 iptables 命令,来过滤和丢弃特定的网络流量。

总结

本文深入探讨了网络故障模拟的核心技术,主要包括数据包故障、DNS篡改、端口占用和网络屏蔽等方面。针对每种故障场景,我们介绍了其原理和实现方式,并通过ChaosBlade项目进行了源码分析,从而可以了解到网络故障模拟并不是一件"复杂"的事情,主要是基于Linux工具如TC和iptables等实现的。通过这些技术手段,可以有效地模拟和测试网络环境下的各种故障情况,从而提高系统的稳定性和可靠性。

相关推荐
大梦百万秋11 分钟前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
广而不精zhu小白14 分钟前
CentOS Stream 9 挂载Windows共享FTP文件夹
linux·windows·centos
一休哥助手20 分钟前
全面解析 Linux 系统监控与性能优化
linux·运维·性能优化
二进制杯莫停22 分钟前
掌控网络流量的利器:tcconfig
linux
watl038 分钟前
【Android】unzip aar删除冲突classes再zip
android·linux·运维
斌斌_____39 分钟前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@1 小时前
Spring如何处理循环依赖
java·后端·spring
赵大仁1 小时前
在 CentOS 7 上安装 Node.js 20 并升级 GCC、make 和 glibc
linux·运维·服务器·ide·ubuntu·centos·计算机基础
vvw&1 小时前
Docker Build 命令详解:在 Ubuntu 上构建 Docker 镜像教程
linux·运维·服务器·ubuntu·docker·容器·开源
海绵波波1072 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask