You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

30 KiB

06 | 定位防火墙(二):网络层的精确打击

你好,我是胜辉。今天我们接着上节课的学习和思考,继续来探讨如何定位防火墙问题。

在上节课里,我们用两侧抓包并对比分析的方法,首先定位到了引发长耗时的数据包,然后对比两侧抓包文件,定位到了包乱序的现象及其原因。最后,我们综合这些有利证据,跟安全部门沟通后,拿到了真正的根因,并彻底解决了问题。

在那个案例中,大量的分析技术是位于传输层,而且要结合应用层(超时的问题)做综合分析。总的来说,难度还是不小的。而且还有一个不可回避的问题:包乱序难道只有防火墙才会引发吗?

其实不是的。包乱序是一种相对来说比较普遍的现象,除了防火墙,还有网络设备、主机本身都可能引起乱序。所以,单纯根据包乱序就断定是防火墙在中间捣鬼,就有点以偏概全了。

那么有没有一种方法,不需要借助那么多的传输层的复杂知识,就可以让我们更加明确地判断出,问题是在防火墙呢?

这节课,我就给你介绍这种方法,即聚焦在网络层的精确打击。这是一种更加直接、更加高效的办法。

你可能又会疑惑了:难道说我们上节课学的东西,其实是多余的吗?那倒不是。这两节讲防火墙的课程,各自有不同的侧重点和不同的适用场景。这次我们介绍的方法,在上节课的案例里就不会起到作用;反过来也是如此。技术上没有“一招鲜”,只是这次课讲的内容相对上节课来说,确实更加直接,这也是它的一大特点。

好了,废话不多说,咱们来看案例吧。

案例1Web站点访问被reset

几年前我在公有云公司就职当时公司乔迁入住了新的办公大楼没过半天不少同事陆续报告他们无法访问某个内部Web工具。而且报告的人都有个共同点他们使用的是二楼的有线网络。

我们这个内部工具是以Web网站形式运行的报错页面就是类似下面这种不同浏览器会有不同的错误页面

补充不要误会这可不是我们极客时间App的图片加载的报错这本身就是当时的报错截图。

我们让二楼同事连接到有线网络并做了抓包,然后传给我们做分析。因为抓包的同事没有做过滤,所以抓上来的包比较大,包含了各种其他无关的数据包。不过没关系,我们可以按下面的顺序来。

  • 第一步过滤IP

在这个案例里面我们就可以把Web站点的IP作为过滤条件。Web站点的IP是253.61.239.103,所以我们的过滤器就是:

ip.addr == 253.61.239.103

补充因为信息安全的原因这里的报文信息我做过脱敏了也就是修改或者删除了敏感信息所以IP和载荷等都已经不是原始信息了。

我们得到下面的过滤结果:

  • 第二步选中可能有问题的数据包然后过滤出整个TCP流

从上图中已经能看到RST报文了。当然第4讲我们也学习了如何在Wireshark里面用过滤器来找到TCP RST报文。那么这个案例里面我们同样可以这么做。在过滤输入框里输入

ip.addr eq 253.61.239.103 and tcp.flags.reset eq 1

我们觉得某个RST包比较可疑那么还是用Follow -> TCP Stream的方法找到对应的TCP流

  • 第三步在过滤出来的TCP流中进一步分析

在我们点击TCP Stream的时候Wireshark会弹出一个窗口显示解读好的应用层信息。如果是HTTP明文非HTTPS加密那就可以直接看到里面的内容了。

回到主窗口此时过滤生效所以只有属于这个TCP流的包才被显示其他无关的数据包都已经被隐藏了。

可见TCP三次握手后客户端发起了一个HTTP请求即GET /overview HTTP/1.1但服务端回复的是TCP RST怪不得访问失败了。我画了下面这张示意图供你参考

握手能成功看起来网络连通性没有问题然后发送HTTP请求后收到RST感觉服务端的嫌疑很大。

我们找到了负责服务端的同事不过他们说完全没有做RST这种设置而且反问“不是说Wi-Fi就都正常吗二楼以外的其他楼层也都可以不是可以排除服务端原因了吗

客户端没问题,服务端也没问题,是不是就要怀疑是防火墙了?但还是那个困境:我们并没有防护墙的权限,无法证实这件事。

那我们还是回到网络本身来查看,静下心来思考一下。

其实,虽然抓包分析是一个很好的排错方法,但是一般在中间设备(交换机、防火墙等)上不方便抓包,所以主要途径还是在客户端或者服务端的抓包文件里寻找端倪。当然,我们也可以争取条件到服务器上抓个包,然后再结合两侧抓包文件,进行对比分析,会更容易得出结论,这也是上节课我介绍过的方法。

不过,**有没有办法只根据客户端的抓包,就能确定这次问题的根因呢?**这个比较悬。但是,对于当前这种场景,如果能掌握到一个关键信息,我们就可以直接得出结论。

它就是TTLTime To Live。你可能会觉得这个知识点简直太简单了真的能解决我们的问题吗且听我慢慢说。

TTL详解

TTL是IP包网络层的一个属性字面上就差不多是生命长度的意思每一个三层设备都会把路过的IP包的TTL值减去1。而IP包的归宿无非以下几种

  • 网络包最终达到目的地;
  • 进入路由黑洞并被丢弃;
  • 因为网络设备问题被中途丢弃;
  • 持续被路由转发并TTL减1直到TTL为0而被丢弃。

RFC791中规定了TTL值为8位所以取值范围是0~255。

因为TTL是从出发后就开始递减的那么必然网络上我们能抓到的包它的当前TTL一定比出发时的值要小。而且我们可能也早就知道TTL从初始值到当前值的差值就是经过的三层设备的数量。

不同的操作系统其初始TTL值不同一般来说Windows是128Linux是64。由此我们就可以做一些快速的判断了。比如我自己测试ping www.baidu.com收到的TTL是52如下图

这个百度服务端大概率是Linux类系统因为用Linux类系统的TTL 64减去52得到12。这意味着这个回包在公网上经过了12跳的路由设备三层设备这个数量是符合常识的。

假如百度服务端是Windows那么Windows类系统一般TTL为128减去52得到76。那就意味着这个回包居然要途经76个三层设备这显然就违背常识了所以反证这个百度服务端不会是Windows。这就是我想说的第一点TTL的值是反映了网络路径跳数的也可以通过它间接推导出对端的OS类型。

补充:现今绝大部分网站的接入环节都是*nix类系统所以这里只是单纯关于TTL这个知识点的技术讨论不涉及“哪种OS是服务端主流”这种有争议的话题。

接下来第二点更加关键了。同样两个通信方之间的数据交互其数据包在公网上容易出现路径不同的情况。就好比你每天开车上班一般也有不止一条线路无论走哪一条只要能到公司就可以了。那么你和百度之间的路径上午是12跳下午变成13跳或者11跳也都属于正常。

但是内网不同因为内网路径相对稳定一般不会变化。如果出现TTL值的波动特别是当这个波动值比较大比如超过2的时候那几乎就说明这些包的不同寻常了。这就是我想说的第二点内网同一个连接中的报文其TTL值一般不会变化。

回到我们这次的案例。我们就按这个思路来排查下这仅有的5个数据包

因为TTL并不是默认的展示列所以我们需要选中报文然后到IP详情部分去查看像下面这样

不过这样看几个报文的TTL还行再多点就十分困难了。怎么做才能更有效率呢答案是借助Wireshark的自定义列。我们可以这样做

  • 选中任意一个报文点开IP详情找到Time to live然后右单击后选中Apply as column。

  • 然后在主窗口中就多出了一列Time to live对比起来非常方便。

你也能很清楚地看到同样的服务端在三次握手中SYN+ACK报文的TTL是59在导致连接中断的RST包里却变成了64显然**这个RST包并不是跟我们握手的那个服务端发出的**否则TTL值就不会变化。

发出这个包的会是谁呢其实一般就是防火墙设备。由于防火墙也遵循IP协议而这里的TTL值是64这就说明这个防火墙跟客户端之间没有别的三层环节或者说是三层直连的。

我们可以用一张简单的图来概括这个案子:

这样我们底气大增根据我们提供的信息负责防火墙的同事就去复查了下果然有发现防火墙上对二楼有线网络有一条可疑的策略跟其他线路不同。这条策略的出发点是每个网络协议规定了协议数据格式以及标准端口号所以协议数据跟端口号不匹配的话就可以认为是“有害”流量。因为HTTP协议标准端口是TCP 80但是我们这个Web站点是3001端口的被防火墙认为不一致所以就拒掉了。

我们来看一下当时的防火墙的配置:

这里的application-default就是说端口需要跟协议匹配。要不然就会被禁止也就是回复RST给客户端终止这条连接。这个防火墙策略被修正后问题也立刻被解决了。

案例2访问LDAPS服务报connection reset by peer

案例1中我们在有权限的客户端抓了包。那么如果两端都做了抓包那么是否可以看到更多的风景呢

这是我们最近遇到的一个例子。eBay内部有一个用户报告他的服务到LDAPS服务失败了这个LDAPS在我们维护的Windows AD域控上遇到了connection reset by peer的报错。LDAPS就是LDAP的TLS版本它监听的端口是636。正好两端的操作权限我们都有就可以做两侧抓包了。

我们先看一下客户端这边的抓包文件确实有看到服务端返回了TCP RST包

图中可见TCP三次握手正常完成了随后的TLS握手Client Hello包也发送出来了但是服务端回复的是RST包这也就是引起客户端显示connection reset by peer报错的原因。

接着,我们再来看下服务端抓包文件的情况:

显然这里也有一个从客户端回复的TCP RST。

不过再仔细对比服务端抓包和客户端抓包是不是有些异样呢为什么客户端抓包里有TLS Client Hello报文但在服务端抓包里却没有呢是因为Client Hello报文延迟了吗

如果是延迟那么这个Hello报文还是会出现在服务端抓包文件里比如出现在RST报文的后面。但事实上服务端抓包里并没有这个Hello报文的存在。所以这就反证了这个Hello不是延迟而是确实就没到服务端

那么问题来了:

  • 客户端发送了TCP握手 + 发送了Client Hello。
  • 服务端收到了TCP握手 + 没有收到Client Hello。

这个现象足以让我们怀疑两者之间存在着一个“看不见的东西”这个东西把Client Hello报文给“吃了”。

我们通过案例1的学习已经了解到TTL是判断“是否有防火墙”的非常直接方便的方法。那么对于当前这个案例我们也一样检查一下TTL。

从服务端视角来看它收到的报文只有三个SYN包、ACK包、最后的RST包。我们选择SYN和RST来对比看下它们的TTL是多少。下图是SYN包

下图是RST包

显然跟之前的案例类似这里的TTL也发生了明显的变化。你应该也明白了这两个包并不是同一个设备发出的

因为这个案例里,客户端我们也抓包了,所以可以来看看客户端抓包是否也有TTL不同的现象呢

客户端收到的SYN+ACK包的TTL是110

客户端收到的RST包的TTL是54

终于结合两侧的抓包我们就可以把这个拼图给拼完整了而Client Hello报文丢失之谜也将揭晓。更新一下前面的示意图会变成下面这样。显然Client Hello报文就是被防火墙丢弃了。

可见防火墙的拦截行为可能出现在多个方向上面对客户端时代表服务端面对服务端时代表客户端毕竟报文都要经过它它如果想乱来通信两端还真的无法控制它。从上图来看防火墙两边“截胡”两边拒绝两边还都只好乖乖地听话结束了连接。你都不能说它“没有武德”因为整个过程都是完全遵照了TCP规范的防火墙做得不可谓不周到。

不过百密一疏,它偏偏在TTL上露出了马脚,被我们抓了个现行。

你看小小一个TTL在这里能起到如此关键的作用真是不可小视。connection reset by peer的问题我们在第4讲专门讲TCP挥手的时候就仔细分析过了。不过你还记得为什么那次的情况跟这次不同吗那次的场景里RST真的是对端发出来的并不是防火墙从中作梗所以TTL也都正常。

那么今天的课程给你介绍的就是connection reset by peer的另外一种可能。你可以仿照我这里介绍的排查过程然后拿证据去跟网络安全团队沟通就行了。相信此时的你说服力远远超过单纯抱怨而没有实质证据的你。

如何应对?

防火墙简单插入一个RST就可以终止连接确实令人无奈。当然这对网络安全来说也许就是一种特性Feature了。正所谓“他人的美味可能是你的毒药”。

我们通过上面介绍的排查过程,确认了防火墙的存在。那么,你可能会问了:接下来我们有什么办法可以规避这个问题呢?

这个问题的核心就是既然我们可以准确地定义什么是防火墙插入的RST报文那是否意味着我们就可以避开它了比如你有没有想到

干脆直接丢弃这个报文!

这个想法很大胆,好像也挺合理。但稍一细想觉得有几个现实问题得先解决:

  • 我们用什么手段来达到“报文丢弃”这个目的呢?
  • 我们做丢弃的时候内核的TCP协议栈是否已经被“毒害”了呢如果真是那样的话即使我们丢弃了RST报文实际上还是没有帮助的吧

对于第一个问题我想如果你熟悉Linux网络的话可能第一时间就会想到 iptables 了。是的我们就是可以利用它来达到“丢弃RST报文”的目的。当然你做这个事情的时候必须十分小心毕竟RST也是TCP协议里定义的标准无论你是否喜欢它RST在很多场景里是必需的一股脑地丢弃一定会引来难以预料的麻烦。

所以我们可以对这条iptables规则设定精确的限定条件使得它既能帮助我们丢弃“有害”的RST报文同时也不影响到其他正常连接的交互。

因为我们很难有条件拥有一台真实的防火墙设备来帮助我们做这个实验所以只能找一个办法来模拟防火墙。怎么做呢我们还是借助iptables。

这里真是要感谢创造了iptables的内核子模块——Netfilter的内核开发团队了有了Netfilter基于它的iptables等工具在我们日常工作中起到了很大的作用。

对于第二个问题,其实不用担心,在报文进来的方向,报文会经过这样的处理流程:

PREROUTING -> INPUT -> 本地处理 -> OUTPUT -> POSTROUTING

所以当我们在INPUT链上创建了丢弃RST报文的规则那么当这个RST报文进入到机器时会被这条规则丢弃进不到本地处理也就是不会有被内核协议栈处理的机会那么我们的TCP连接就不会被“毒害”了。还好我们还有这张王牌可以打只要对系统和网络足够熟悉总还是有一线生机。

动手实践

现在“理都懂”了,让我们来动手实操一下。我们需要搭建这么一个测试环境:

  • 虚拟机1下面简称为1配置为客户端实验时会在这台上执行telnet模拟访问行为。
  • 虚拟机2下面简称为2配置为客户端的网关这样它就可以劫持流量模拟防火墙行为。

实验1telnet第三方站点

在1上直接telnet www.baidu.com 443可以成功。

然后我们需要配置一下让1访问www.baidu.com的流量强制经过2这样后续我们就可以让2来操控1和baidu之间的连接了。接下来步骤稍多感谢你的耐心。

在虚拟机1上我们需要完成这么几件事。

创建隧道隧道另一头就是虚拟机2我们将在那里模拟一个“防火墙”。在上节课里我们了解了ipip隧道这里的GRE隧道也是类似的工作原理。

ip tun add tun0 mode gre remote 172.17.158.46 local 172.17.158.48 ttl 64
ip link set tun0 up
ip addr add 100.64.0.1 peer 100.64.0.2 dev tun0

添加路由项使得本地去往第三方站点的流量都走这条路由也就是通过隧道到达虚拟机2然后2来转发报文。

ip route add 110.242.68.0/24 via 100.64.0.2 dev tun0

当然了虚拟机2上面也需要做对等的隧道配置

ip tun add tun0 mode gre remote 172.17.158.48 local 172.17.158.46 ttl 64
ip link set tun0 up
ip addr add 100.64.0.2 peer 100.64.0.1 dev tun0

虚拟机1把报文发到虚拟机2但是如果后者不做配置默认是会丢弃这些报文的所以还需要在2上开启ip_forward

sysctl net.ipv4.ip_forward=1

我们在2上运行tcpdump port 443然后1上运行telnet www.baidu.com 443。在2的tcpdump窗口里已经可以看到从1过来的流量了

root@server$tcpdump port 443
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:53:36.054124 IP 100.64.0.1.34396 > 110.242.68.3.https: Flags [S], seq 3364415625, win 64620, options [mss 1436,sackOK,TS val 2049305210 ecr 0,nop,wscale 7], length 0
16:53:37.084514 IP 100.64.0.1.34396 > 110.242.68.3.https: Flags [S], seq 3364415625, win 64620, options [mss 1436,sackOK,TS val 2049306241 ecr 0,nop,wscale 7], length 0
16:53:39.100482 IP 100.64.0.1.34396 > 110.242.68.3.https: Flags [S], seq 3364415625, win 64620, options [mss 1436,sackOK,TS val 2049308257 ecr 0,nop,wscale 7], length 0

抓包里只有SYN包而没有SYN+ACK1这头的telnet也挂起没响应了这是怎么回事

原来我们还需要设置一下NAT要不然出去的报文源IP是100.64.0.1回包也会回这个地址显然回不到2了。

所以还需要在2上启用SNAT

iptables -t nat -A POSTROUTING -d 110.242.68.0/24 -j MASQUERADE

我们再试试在1上发起telnet www.baidu.com 443果然成功了。

2上的tcpdump也抓取到了正常连接的报文这里就不贴了

实验2插入RST报文连接失败

现在我们需要在2上配置一个“插入RST报文”的动作这样就可以模拟“防火墙阻隔TCP连接”的效果了。

我们可以在2上运行这条iptables命令

iptables -I FORWARD -p tcp -m tcp --tcp-flags SYN SYN -j REJECT --reject-with tcp-reset

有了这条命令2就用TCP RST拒绝了转发链也就是命令中的FORWARD链上的SYN报文。1上的telnet立刻收到了拒绝

2上的tcpdump抓包窗口里也看到了握手和拒绝的报文

root@server$tcpdump -i any port 443
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
17:02:16.623480 IP 100.64.0.1.34428 > 110.242.68.3.https: Flags [S], seq 3314573698, win 64620, options [mss 1436,sackOK,TS val 2049825780 ecr 0,nop,wscale 7], length 0
17:02:16.623518 IP 110.242.68.3.https > 100.64.0.1.34428: Flags [R.], seq 0, ack 3314573699, win 0, length 0

可见这个RST实实在在地起到了类似防火墙的作用让你的连接无法建立。你看其实防火墙也没那么神秘我们也可以实现。可以小小地鼓励一下你自己

实验3丢弃RST报文连接成功

这套实验的核心目标是实现对RST干扰报文的规避也就是丢弃这类报文。让我们继续实验在1上添加这么一条iptables规则

iptables -I INPUT -s 110.242.68.0/24 -p tcp --sport 443 -m tcp --tcp-flags RST RST -m ttl --ttl-eq 64 -j DROP

有了这条规则我们就对符合条件的TCP报文进行了丢弃这个条件就是“来自110.242.68.0/24网段的TCP源端口为443的带RST标志位的TTL等于64的报文”。

这里的TTL条件就是关键了。在实际场景下你就可以根据防火墙插入的RST报文的TTL的实际特征写一条精确匹配的规则把它跟正常报文区分开进行精准的丢弃。

我们还是在1上telnet然后发现这次不再被reset而是挂起了。在1的tcpdump中也看到SYN发出了对方也回复了RST但是我们并没有被真的被reset。这里正是这条丢弃RST报文的iptables规则起到了效果。

root@client2:~# tcpdump -i any host 110.242.68.4 and port 443
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
17:27:50.748194 IP client2.53438 > 110.242.68.4.https: Flags [S], seq 1164905016, win 64620, options [mss 1436,sackOK,TS val 2201853747 ecr 0,nop,wscale 7], length 0
17:27:50.748433 IP 110.242.68.4.https > client2.53438: Flags [R.], seq 0, ack 1164905017, win 0, length 0

你可能这里稍有疑惑1持续发SYN2持续回复RST那我们这个连接不是依然没建立起来吗

其实一般防火墙工作模式跟实验里的稍有不同。这次两个案例中的防火墙都运行在应用层模式也就是会先让TCP连接建立然后检查后续的报文发现不符合安全策略的时候就插入一个RST报文终止这条连接。这种动作一般是一次性的。

而在这个实验中我们只要让iptables的REJECT只生效一两次比如用下面的复合命令TCP连接就只是在最初几秒被短暂干扰之后就依然能成功建立。

iptables -I FORWARD -p tcp -m tcp --tcp-flags SYN SYN -j REJECT --reject-with tcp-reset;sleep 5;iptables -D FORWARD -p tcp -m tcp --tcp-flags SYN SYN -j REJECT --reject-with tcp-reset

在实际场景中只要设置前面提到的iptables丢弃特定RST报文的规则就还有很大的几率能让这条连接继续保持下去应用也运行下去。防火墙居然对你无效了你除了长舒一口气会不会心里也冒出“终于翻身当主人”的感觉

拓展思考

那么除了这种丢弃有害RST的办法还有没有别的办法呢

就上面探讨的丢弃RST的方法来说这是一个“应对式”的策略,也就是有人要“害我”,那我把“毒药”给扔了。但仍然是“被动”的方法。如果思考得更进一步,我们有没有办法,使得别人都没有机会来害我呢?就是你连“下毒”的机会也没有?这就是“主动”的策略了。

我个人看法是,可以到网络层IP层去寻找机会。利用IPSec比如IPv6默认启用了IPsec我们就获得了在第三层加密的能力。因为就连IP报文本身都是加密的那么即使防火墙要插入报文因为它不具备密钥所以这个报文会被接收端认为非法而被丢弃。这样就有希望真正摆脱防火墙对传输层TCP/UDP的这种控制。

小结

上节课我们采用的方法,是通过两端抓包后进行网络包的对比分析,排查定位到防火墙的存在,这种方法对于丢包、乱序等场景特别有用。而这节课的方法呢是通过分析TTL值的变化快速定位到防火墙的存在。这种方法对于连接被重置RST的场景,十分有效。

那么在学完这节课之后,你需要记住以下几个关键要点:

  • **需要在受影响的客户端或者服务端进行抓包。**这样你才能获取到你需要的关键信息,而这种信息,单纯通过应用层日志等途径,是很难获取的,这也是应用层排查的天然的不足。对此,你需要有清醒的认识,并深刻理解网络层排查技术的重要性和不可替代性。
  • **分析抓包文件识别TTL的变化。**这里你需要了解网络层和IP协议的相关知识点。同时也要明白即使一个知识点看似简单其背后的设计原理都大有文章。对每个技术细节的推敲能帮助我们打造出更为强大的技术底蕴。
  • 灵活运用Wireshark自定义列。我们通过添加自定义列让每个报文的TTL值都在主视图中展现极大地方便了对这些TTL的比较。所以我们除了掌握协议知识以外也要挖掘各类工具的使用技巧。所谓“工欲善其事必先利其器”也。

另外在这节课的最后我们也通过一系列实验再一次深入理解了RST报文的作用以及可能的规避方法。在这个过程中我们学习了

  • GRE隧道的搭建和用途你可以用ip tunnel add命令创建GRE隧道并用ip route add命令配置路由项让某些网络的流量转而走这个隧道网关。注意即使是一个二层不可达的IP通过隧道也可以“包装成”二层可达进而可以配置为网关。这一点如果不借助隧道是无法实现的。
  • 用iptables实现对报文的操控在你需要模拟一些问题场景的时候不妨多发掘一下iptables的“潜能”比如可以丢弃符合某种条件的报文
iptables -I INPUT -s 110.242.68.0/24 -p tcp --sport 443 -m tcp --tcp-flags RST RST -m ttl --ttl-eq 64 -j DROP

  • 我们也学习了如何用iptables结合内核配置实现一个简单的NAT网关
iptables -t nat -A POSTROUTING -d 110.242.68.0/24 -j MASQUERADE
sysctl net.ipv4.ip_forward=1

思考题

这节课我给你介绍了利用防火墙的TTL跟原先的正常报文的TTL不一致的特点从而识别出防火墙的方法。

那么假设有一天防火墙公司把这个特性也完善了我们再也不能仅仅凭TTL的突变而发现防火墙了你觉得**还有什么办法可以识别出防火墙吗?**这是一个开放式的话题,欢迎你把答案写在留言区,与同学们一同分享。

附录

抓包文件:https://gitee.com/steelvictor/network-analysis/tree/master/06