浅析trapframe与context的原理与区别

TRAPFRAME与CONTEXT的区别

在ucore操作系统中,trapframecontext是很容易混淆的两个结构,他们都会出现在线程的调度中。实际上,结构体trapframe用于切换优先级、页表目录等,而context则是用于轻量级的上下文切换。从技术上来看,两者的区别在于context仅仅能够切换普通寄存器,而trapframe可以切换包括普通寄存器、段寄存器以及少量的控制寄存器。

*在看后续内容之前,你需要提前了解汇编语言、栈、C函数调用的实现。

TRAPFRAME结构体

trapframe定义如下——

这个结构体中,依次储存了——

  • 目标寄存器
  • gs, fs, es, ds 段寄存器
  • trap_no, err 用于储存中断信息
  • eip, cs, eflags 用于储存陷阱(trap)返回后的目的地址
  • esp, ss 在权限发生变化时,用于指示新的栈的位置

有两个地方使用了trapframe,一个是中断调用,另一个是进程切换。两者对于trapframe的使用有相似之处,但也并不完全相同。

中断调用中使用TRAPFRAME

trapframe在中断中,在前期负责中断信息的储存,后期负责中断的恢复。同时,trapframe结构体是位于栈中的,其生成和使用都是通过栈的pushpop命令实现的,这将在后面详细解释。

中断发生时,下面代码,将一系列信息压到栈中。这些信息在后续的,trap(struct trapframe *tf)函数中,被对齐到了tf结构体中。

中断处理完成后,需要恢复原来的运行状态,此时,按顺序将之前push的所有信息pop出来即可。

当然,倘若读者认为trapframe仅仅像这样中规中矩的实现信息的保存,那就太小看他了。我们发现,在调用call trap之后,有一句popl %esp,而后续恢复的信息完全是基于该%esp进行定位的,那么在中断处理内存中,如果我们强行修改%esp成为我们希望接下来运行的代码段的trap描述,那么经过__trapret代码恢复trapframe后,你就可以让程序跳转到任何你希望的地方。

比如下面代码就实现了内核态到用户态的切换。

其中*((uint32_t *)tf - 1)这个位置的值就是之后popl %esp恢复的%esp的值。

进程切换中CONTEXT的作用

context的结构体定义如下,可以看到,其中储存了所有的用户寄存器的值。

context结构体干的事情也很简单,可以用switch_to函数囊括,即保存一系列寄存器,并恢复一系列寄存器。在C++中,switch_to是拥有两个context结构体为参数的函数switch_to(&(prev->context), &(next->context)); ,其实现如下——

进程切换中TRAPFRAME的作用

那是不是进程的切换就可以直接用switch_to函数呢?答案是否定的,因为switch_to仅仅保存、恢复了普通寄存器,无法实现优先级跳转、段寄存器修改等等。这时,就要借助trapframe了。

由于switch_to函数跳转后,将调到context.eip位置。而这个跳转我们没法完全实现进程切换,所以我们可以将其设置为一个触发二级跳转的函数,forkret

其中,forkret定义如下(current是当前进程,也就是进程切换的目标进程),forkret不同于switch_to,它尝试使用trapframe作为进程切换的手段,而相比于contexttrapframe的功能就强大多了。

而forkrets定义如下——

现在,又回到了中断恢复的那一段代码,而其中的逻辑也完全相同。最终,进程跳转目标进程的入口,而该入口的地址,被存放在proc->tf中。下面是kernel_threadtrapframe初始化代码,也能佐证最终调用函数入口fn被储存在了eip中。

浅析文本摘要算法(Document Summarization)

概述

文本摘要算法,指的是在一篇文章中,摘要出核心观点的算法。主流的文本摘要算法生成一篇文章的摘要,摘要长度在3~4句话左右。

历史

在深度学习算法出现之前,人们使用了贪心算法、图算法来研究文本摘要。同时,为了衡量一段生成的摘要是否和标准摘要(gold summary)相似,学术界提出了一系列标准,这些标准现在广泛用于文本摘要算法的模型评价,其中最为常用的就是ROUGE – A Package for Automatic Evaluation of Summarieszz中提到的ROUGE-x系列算法。

深度学习提出后,LSTM/GRU作为摘要生成的强有力工具,一举打破传统算法的效果。最为主流的摘要生成模型是基于抽取式和生成式的,这两种模型将在后面详述。

随着深度学习的发展,很多其他算法也被引入。如一些文章使用强化学习,直接尝试训练一个能够获得最高ROUGE得分的模型。一些文章借助机器视觉方向的研究,优化模型结构。也有文章训练模型,判断句子相对于文章的重要性,进而间接实现摘要提取。

抽取式和生成式文本摘要

不过既然是摘要,那么显然,摘要中很大一部分都应该是原文中出现过的词语。同时,基于语法的考虑,也需要引入一些没有在原文中出现的词。

这时,就引出了文本摘要算法的两大学派—— 抽取式(extractive)和生成式(abstractive)。

  • extractive算法希望直接从文本中,抽出若干词语,连成一句话。词汇的抽取通常使用循环神经网络,配合注意力机制。得到“下一个词”可能是谁的概率分布。
  • abstractive算法希望从词汇表中,直接选出“下一个词”。

不论是abstractive的文本摘要,还是extractive的文本摘要,模型都由两个部分构成的,即一个编码器(encoder)和一个解码器(decoder)。

  • 对于编码器来说,通常都是将输入文本导入一个循环神经网络,生成出一个上下文特征向量c
  • 对于解码器来说,通常也是使用一个循环神经网络。以编码器生成的表征原文的上下文特征向量c,和之前生成的词汇{y_1, y_2, \dots, y_t},生成出摘要的下一个词y_t

数据集

当前研究人员多使用CNN/DailyMail作为数据集,在该数据集中,每一组数据是一个新闻稿,平均每篇文章39句话。同时还有一个“多句”的摘要。

著名论文

Get To The Point: Summarization with Pointer-Generator Networks

这篇文章提出了一个Pointer-Generator模型,既可以通过Pointer拷贝原文的文字,也可以通过Generator生成不存在原文中的文本。这两个模型通过一个开关p_{gen}进行选择——

P(w) = p_{gen} P_{vocab}(w) + (1-p_{gen})\sum_{i:w_i=w} a_i^t

其中P_{vocab}表示从词汇表中选择一个单词的概率分布,而a_i则是从原文选择第i个词汇的概率分布。

在此基础上,本文还提出了一种称之为覆盖率机制(coverage mechanism)的方式,用以解决抽取式摘要中最容易出现的内容重复的问题。

SummaRuNNer: A Recurrent Neural Network based Sequence Model for Extractive Summarization of Documents

这篇文章核心目标是仅仅是在“句子级别”进行摘要,即从原文中选择若干适合作为摘要的句子。该文章使用两层GRU,依次在“词语”级别和“句子”级别总结信息,最终获得一个可以表征全局性质的向量d。接着,再参考句子级别的特征向量s_i、全局特征向量d、以及RNN的隐状态h_i,生成对于句子的评分。

Ranking Sentences for Extractive Summarization with Reinforcement Learning

这篇文章的核心目标是给一篇文章的句子按照与主题的相关度进行排序,文章指出,在之前的方法中,通常是训练模型,使得对于每一个句子的估价尽量靠近一个指定的ground-true。但提升对于ground-true的近似并不一定能够提高模型的ROUGE评分。因此,文章提出使用强化学习的方式,直接学习句子的label,进一步最大化ROUGE评分(注意,这里ROUGE评价指标是不可导的,因此才需要用到强化学习)。

Neural Document Summarization by Jointly Learning to Score and Select Sentences

这篇文章仍然是句子级别摘要的算法,其思想借鉴了MMR算法,在MMR算法中,借助一个建立在语句集合中的评分函数r(S),迭代选择能够最大化加入后r(S')值的句子。即

g(S_i) = r(S_{t-1} \cap {S_i}) - r(S_{t-1})

g(S_i)可以看做在选择了S_{t-1}后,第i篇文章S_i的评分。使用神经网络结构去学习g(S_i)即可实现——能够参考之前选取句子集合信息的语句选择算法。

BanditSum: Extractive Summarization as a Contextual Bandit

这篇文章也是借助强化学习尝试拟合文本的ROUGE得分,并且提出了新的结构用于防止摘要倾向于前面的句子。

Bottom-Up Abstractive Summarization

这篇文章与Pointer-Generator相似,不过为了解决前文中文本拷贝倾向于拷贝整句话的bug。在输入给Pointer-Generator模型之前,先给输入的文本加了一个mask,过滤掉其中一部分,这样就导致文本不再倾向于拷贝连续的内容。

DeepChannel: Salience Estimation by Contrastive Learning for Extractive Document Summarization

这篇文章训练模型,拟合P(D|S),即给定摘要S,能够恢复原文D的概率。而函数P(D|S)的训练,可以借助文章D,摘要S,一句不重要的话S_2这三部分进行训练。生成P(D|S)后,再使用贪心算法构造摘要集合。

一句话解析最大边缘相关性算法(MMR, Maximal Marginal Relevance)

随着网络资源的丰富,找到和询问(query)最相关的一系列文本成为了学者们研究的问题。

之前的做法通常是直接计算逐个候选文本与询问的相关度。将前K大值对应的文档作为“最相关文档”输出出来。但是这种做法适用于那些候选文本集合较小,且只有少量文本与询问存在关联的情况。

但实际上,我们还有另一种场景,以搜索引擎为首的这类环境中,潜在的相关文档非常多,我们需要非常高的召回率(recall),并且尽量降低结果的冗余性(redundance)。在这种情况下,MMR就有用武之地了。

实际上MMR的算法核心思想可以用一句话解释——

A document has high mariginal relevance if it is both relevant to the query and contains minimal similarity to previously selected documents.

一个文章拥有高边缘相关性(MMR)当且仅当它与询问相关性高,而且与之前已经选出来的文章集合相关性低。

而使用公式化的表达如下——

MMR \overset{def}{=}  \underset{D_i \in R \backslash S}{Arg max} \left[ \lambda (Sim_1(D_i, Q) - (1-\lambda) \underset{D_j \in S}{max} Sim(D_i, D_j) \right]

其中,Q表示询问,D_i表示文档,S表示选择出来的相关文档集合,Sim表示任意一种文档相关性计算函数。

以上就是MMR的解释,MMR在文档摘要方面有很多应用。想要更细了解,可以参考文献 The Use of MMR, Diversity-Based Reranking for Reordering Documents and Producing Summaries

云计算平台故障事件与相关研究

今年来,云计算平台发生了多起故障事件,而其原因的分析以及损失估计被很多学者所研究,本文将分别列举规模较大的云计算故障事件,并加以分析,同时对于学界对云计算研究成果做综述性摘要。

经典事件

阿里云停机事件

2019年3月3日,阿里巴巴旗下阿里云发布通报称,华北2地域可用区C部分ECS服务器(云服务器)等实例出现IO HANG(IO不响应),经紧急排查处理后已全部恢复。在这一次事件中,华北相当多的互联网公司的App、网站全部瘫痪,大量程序员、运维晚上从被窝里面被拉回公司修复错误。

Amazon AWS宕机事件

2017年2月28号,一名亚马逊程序员在调试系统的时候,试图运行了一条脚本,以删除少量用于支持支付操作的服务器。但是他输错了命令,导致大量服务器被删。而被误删除的服务器集群中中,有两个非常重要的系统,一个是存放了该区域所有托管服务器的配置信息,用于处理所有GET, LIST, PUT 和 DELETE 请求的。另一个系统用于负责新服务器的分配。

为了修复这个错误,亚马逊不得不重启整个系统。对于数年没有重启过得系统,而重启系统后的完整性和安全检查耗费了非常多的时间。最终导致了震惊全球的Amazon S3宕机4个小时事件。

事后,亚马逊官方网站通告总结事件时,称整个事件的触发程序,是一个支持快速移除大量负载的命令,而我们已经将该命令进行修改,使得负载的移除更加缓慢,并且增加了足够的安全保障,当某个节点移除后,其对应的子系统剩余节点没有能力负载所有的访问,则该移除指令不会被执行。另一方面,亚马逊也修改了恢复系统,保障在将来错误发生时,恢复不再会花费那么多时间。

Azure服务器中断事件

在2012年2月28日,微软旗下的Azure云服务器发生中断,事件长达7小时,据微软官方网站通告,该中断是由于服务中一处关于闰年的计算失误导致的。问题发生后,工程师快速部署了修复代码,并在7小时候恢复了服务器的访问。

其他安全事件

对于云服务商来说,设备故障并不是唯一的灾难,安全性漏洞更为致命,在7 Most Infamous Cloud Security Breaches文章中写道,微软、Dropbox、LinkedIn、Apple iCloud、Yahoo都曾经经历过安全漏洞攻击。并产生了很大的损失。

相关研究

硬件故障是云计算平台故障中,一个非常古老的错误,有非常非常多的研究者对他进行过研究。随着云计算的发展和商业化,硬件故障所扮演的角色也在随之改变,在Wang et al. (2017)的研究中指出,这些变化有积极和消极的方面。
从积极的方面来讲,云服务商的硬件设备的质量越来越可靠,更完备的故障检测系统也被部署到了云计算平台上,从而使得故障的恢复更加高效。而且由于云计算已经是一门非常成熟的技术,面对故障,运维也有足够的经验。这一系列变化都会倾向于降低对故障事件的损失。
但是从消极的方面来讲,由于市场扩大,更多的服务商进驻云计算行业,使得运营商们更加注重成本。自然而然的,他们就会倾向于使用那些不那么可靠,但是便宜的设备。

在Lloyds ́ and AIR (2018)的研究中,对于云服务商的故障损失进行了估计,发现美国的云服务商停机3-6天对应损失可达69亿美元。其中,制造业和零售批发业首当其冲,而其中损失最严重的是财富1000强的企业。

为了控制由于云服务商故障导致的损失,催生出一个全新的保险行业,叫做网络保险(cyber-insurance),这种保险以投保者的网站的可用性作为保险项目。不同于传统的保险,网络保险作为一个新兴的品种,仍没有被大众以及保险公司所接受,一方面是人们并没有正确的认识到服务商故障的损失,另一方面是网络保险有极大的系统性风险。一个云服务商的故障可能同时美国导致124万企业的经营遭受影响,此时大规模的赔偿可能会对保险公司带来巨大的流动性风险。

对于云服务的故障原因,文章Calvesbert (2018列举了四类原因:
1)环境原因,包括自然灾害在内的,无法认为控制的故障。
2)敌对攻击,主要包括竞争对手或黑客,尝试利用网络的漏洞进行攻击。
3)意外原因,指的是日常操作维护的误操作。
4)结构性故障,由于结构缺陷、资源耗尽而产生无法预知的错误。

当然,云平台的故障并不局限于大规模停机,频次更高的是局部性错误。在Chen et al. (2014)的研究中,将这类错误分类为应用错误、配置错误与云错误。配置错误是开发者认为错误配置、重复提交任务导致的,由于开发者在发现任务失败后倾向于重复提交,因此配置错误是所有故障中最多的。其次就是应用错误,其原因为云平台本身的资源限制、以及硬件错误。最后是云错误,其来源于运节点的添加、删除与维护。

总结

经过大规模的资源不可访问的问题的四种成因,意外原因和结构性故障居多,因为这两种成因比较难以估计,与之相反,敌对攻击和环境原因都会有非常完善的防范措施。因而在近现代的宕机事件中,并不常发生。同时可以发现,不同公司在事件后的翻译也不尽相同,这可以通过修复时间,以及事后事件成因分析看出。其中,亚马逊事件后,官方公布了详细的故障原因,以及改进措施,不仅给用户一个交代,也对我们的研究有很大帮助。反之另一些宕机事件,则官方没有公布调查结果,或是寥寥几句,给用户和研究者都带来 极大的困惑。

现有的研究从各个方面为降低云计算错误做了非常重要的贡献,包括损失估计,频率分析,原因总结等等。为云计算厂商优化平台算法,增加可靠性有非常积极的作用。

引用

  • AWSCloud. 2017. Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region. (2017). https://aws.amazon.com/message/41926/.
  • Contel Bradford. 2018.7 Most Infamous Cloud Security Breaches.(2018).https://blog.storagecraft.com/7-infamous-cloud-security-breaches/.
  • Gian Calvesbert. 2018. Cloud Service Failure: 3 Things to Know. (2018). https://www.air-worldwide.com/Blog/Cloud-Service-Failure–3-Things-to-Know/.
  • Xin Chen, Charng-Da Lu, and Karthik Pattabiraman. 2014. Failure analysis of jobs in computeclouds: A google cluster case study. In2014 IEEE 25th International Symposium on SoftwareReliability Engineering. IEEE, 167–177.
  • Bill Laing. 2012. Windows Azure Service Disruption Update. (2012). https://azure.microsoft.com/en-us/blog/windows-azure-service-disruption-update/.
  • Lloyd ́s and AIR. 2018.Cloud Down: Impacts on the US economy. Technical Report.
  • G. Wang, L. Zhang, and W. Xu. 2017. What Can We Learn from Four Years of Data Center Hardware Failures?. In 2017 47th Annual IEEE/IFIP International Conference on Dependable Systems and Networks (DSN). 25–36. https://doi.org/10.1109/DSN.2017.26

从零开始编写并托管WordPress插件:NoBaidu抵制百度插件

最近被一篇称为《搜索引擎百度已死》的文章被刷屏了,笔者本身就非常不喜欢百度,趁放寒假没有事情干,琢磨这在自己的网站上写一个插件。当访问者从百度进入网站,则在页面顶部显示一段用户自定义的阻止标语。顺便搞清楚Wordpress插件的原理。

上图就是NoBaidu的一个截图,本文按照我的编写顺序,分为——

  • 如何编写一个Wordpress插件
  • 如何将编好的插件托管到云端

另外,插件的源代码在我的github中开源,读者可以对照理解。

如何编写一个wordpress插件

编写Wordpress插件的教程网上很多,我就不重新造轮子了,我自己是在 如何开发一个WordPress如何开发一个WordPress插件 这篇文章中学到的,其核心要义就是钩子函数——

在Wordpress渲染一个页面的时候,在很多关键位置设置钩子(比如页面渲染开头,渲染器启动后等位置),而插件可以自定义的在任何钩子上挂载自己的函数,这样当页面渲染到了指定位置时,插件可以做一些自己的事情。

比如下面代码就可以在robots.txt渲染时,运行 $this->robots_txt_edit 指定的插件处理模块。

而以NoBaidu插件为例,我们需要支持如下逻辑——

  • 当普通用户访问时,检查用户的请求头的REFERER域是不是baidu域名下的URL
  • 当用户来自百度时,注册一个钩子函数,在页面渲染开始时,加入一个固定在页面顶部的,包含了自定义文字的HTML元素。
  • 当请求robots.txt时,返回禁止Baidu爬虫的robots文字

除此之外,还需要一些额外的代码,来保证我们的插件可以正常使用

  • 在用户是管理员的时候,生成插件设置页面,在其中可以设置显示的自定义抵制文字,用户选择是否组织百度爬虫,以及抵制文字是显示在顶端还是页面中部等等…
  • 编写插件安装、卸载时的逻辑,保证安装时初始化配置信息,卸载时删除配置信息,做到无残留

上述的逻辑,都集中到了下面的代码中,该代码使用一个类包裹了所有的函数(其作用类似于C++中的Namespace,用于最小化命名冲突)

看懂了上面的代码,是不是觉得Wordpress编写插件非常简单? 上述代码中引用的其他php代码,大家可以在我的github中自行查阅。

将自己写好的插件托管到wordpress云端

WordPress官网的Plugin目录最低端,有一个Add Your Plugin分区,这个分区详细介绍了该如何将你的Wordpress插件托管至wordpress.org,一般来说,读完上述的文章,你就已经能够自主把自己的插件丢到云端了。不过我准备从自己的经历出发,来大致讲一讲流程。

WordPress.org自己维护了一个svn仓库,最终会获得自己插件的专属仓库,你可以使用svn提供的版本控制,自行升级、修订等等。而想要获得自己插件的svn仓库,你就需要把自己的插件的最初版提交给官方审核。提交入口在这里。这里写几个提交的注意事项吧——

  • 最重要的是要知道php没有命名空间之说,所以一切全局变量都应该防止冲突。一个比较取巧的做法就是像上文一样,将你自己的程序包裹在一个class里面,而class以自己的插件名称命名。(我就因为配置的class命名为MySettingClass被官方打回来了)
  • 不要有额外的文件,之前写插件的时候,把网上别人插件的代码拷贝到了自己的目录,本来只是想照着抄一抄,结果忘删了,这个也被审核人发现了。
  • README.txt按照官方的标准格式写。

网站上面说的是7个工作日,结果我第一天晚上提交了初版的zip包,第二天早上就收到了审核者的邮件。按照要求修改了一些类名,并删除了无关文件后,我直接把zip包附在了回复中,第三天早上就拿到了通过的消息,并且获得了svn仓库的访问权限。开源社区真的非常强大👍

第一次要求修改的邮件
成功通过审核

接下来就是上传仓库了,如果读者会使用git的话,就别自己去学svn了,照着官方指南走一遍,就完全学会了。和git的pull, add, commit, push, tag如出一辙。

当你的first commit上传到远程仓库,并且readme.txt没有什么问题的话,你应该是可以立即在插件商店中搜索到自己写的插件的。

到这里我们在wordpress上面第一个插件就做好啦!

如果你觉得本文有用的话,欢迎到我的github仓库Star或Contribute~

让我们一起抵制Baidu吧~

参考文章

  • 【一个前人的代码】:http://ju.outofmemory.cn/entry/222836
  • 【如何开发Wordpress插件】:https://www.wpdaxue.com/writing_a_plugin.html
  • 【官方文档】:https://wordpress.org/plugins/developers/

Visdom教程(3):绘制折线图

这是Visdom教程的第三篇文章,主要讲了绘制折线图的方法。


Visdom中,使用函数vis.line绘制折线图,该函数参数表如下——

折线的Y坐标是必要参数。它可以是一个一维数组表示一条直线,也可以是二维数组表示多条折线。该函数的调用可以看示例代码的demo1函数。

line函数还可以设置update参数为’append’。在这种情况下,可以每次只传入新增的数据。示例代码demo2即使一个使用append优化了的绘制深度学习loss函数的例子。

Visdom教程(2):实时展示图片

这是Visdom教程的第二篇文章。主要讲了如何使用Visdom在本地查看服务器上的一个图片。


我们常常使用Visdom实时展示模型训练时刻的图像输出。Visdom支持展示numpy数组形式的若干张图片。展示图片用到了如下两个函数

  • visdom.image(img, win)
  • visdom.images(imgs, win)

第一个函数在win指定的窗口,展示单张图片,而第二个函数则用来展示一系列图片。

  • img是一个表示图片的numpy数组,形状(3,width,height)或者(width,height)形状
  • imgs是表示一系列图片的numpy数组,形状为(batch, 3, width, height)。
  • win表示窗口id,应该可以为任意类型。如果不指定win,服务端将自动新建一个窗口放图片,然后你的浏览器中将堆满密密麻麻的图片窗口。

下面是一个例子——

Visdom教程(1):开启Visdom服务

这是Visdom教程系列文章的第一篇,主要介绍了Visdom的安装。


Visdom是一个Python的远程可视化工具,经常用于配合深度学习训练进行可视化。

Visdom的工作原理是先在远端服务器上面运行一个Server,该服务器将绑定localhost:8097,同时,任意远端运行的Python程序可以通过引入visdom包,实现与该Server的交互。

Visdom服务端假设在服务器上的8097端口,我们只需要执行

安装visdom,并且执行

打开服务端,此时服务端将自动监听8097端口。

下面是一个最简单的visdom程序,该程序在example环境中,新建了一个text_window号窗口,里面写上了Here is the text这句话。

值得注意的是,Visdom中的环境可以理解为工作区,比如一个训练神经网络的程序,训练loss折线图可以输出到train环境中,而模型的内部可视化信息则输出到model环境中。这样可以使我们的工程井井有条。

我们既可以直接通过 server_of_hostname:8097 访问可视化界面,也可以通过ssh命令,将服务器上面的端口映射到本地,并进一步通过 localhost:8097 访问。

ssh name@host.com -p 3522 -L 8097:*:8097 .

 

ICMP攻击及防范

ICMP协议概述

IP协议本身无法处理诸如“得到一个包发送失败的事实”,“对网络进行诊断”这些操作。ICMP协议就是为了上述操作而包含在IP协议中的一个子协议。

ICMP协议(Internet Control Message Protocol)全称Internet控制报文协议,是TCP/IP协议族的一个子协议,用于在主机,路由器之间传递控制信息。最常见的是我们使用的ping命令就是通过发送ICMP包实现的。

ICMP包位于IP包内部,ICMP的包头一般来说紧跟着IP包头(通常IP包头为20字节,则ICMP包头从第21字节开始)。

ICMP包包含Type, Code, Checksum和Data域,其中Type指定了ICMP的类别,可大致分为两类——一种是请求及其回应,另一种是错误信息反馈。

以Echo请求为例,一个Type=8的包发向目标IP,如果目标服务器成功收到,回复一个Type=0请求。倘若中间出现路由错误,则中间路由器返回一个Type=3的包。

ICMP攻击及其防范

ICMP攻击有非常多不同的种类,接下来将列举一些常见的攻击。

ICMP隧道

首先讲一讲ping指令的原理,ping指令首先发送一个ICMP的ECHO REQUEST包给目标IP,并在data域包含一个唯一的16位标识符(这个标识符在Linux中是顺序生成的,而在Windows中是随机生成的),通常操作系统还会在data域后面添加一些无用的信息使得ICMP包长到达一个指定的长度。目标主机收到了ping指令后,将data域原封不动包裹在一个ECHO REPLY包中发回来。

对于一个ICMP ECHO REQUEST/REPLY包,由于其做的是类似于网络控制方面的事情,故通常不会被防火墙在意(除非防火墙一一分析每个包的内在data域有没有异常),而其data域可以携带任意的数据,因此可以借助ICMP包实现一个从服务器发送消息的程序。我在github上面也找到了一个实现ICMP隧道的程序,大家可以参考一下。

对于ICMP tunneling的攻击,有一些非常原始的防范方式,包括限制包的大小以及直接丢弃所有的ICMP包。但是在实际上这些方式都不可行。也有一些比较复杂的防范方式,包括在本机监听包,并在发现异常是,通过网络中的控制节点拦截攻击的流量。

DDOS攻击

PING FLOOD攻击

两个对称的主机,攻击者在一个主机上使用多线程发送大量包向目标主机。只要攻击者的主机的带宽大于被攻击者,被攻击者的带宽就会被完全占满,而真正的包无法到达。

对于这类攻击,可以在子网入口的路由器上设置过滤表,拦截攻击者IP。

Smurf攻击

Smurf-attack是一种原始的DOS攻击,攻击者伪造目标主机的IP为source IP,并发送一个广播的ICMP ECHO REQUEST指令。当指令被网络的其他主机回应后,回应的ECHO REPLY将涌入目标主机,导致目标主机瘫痪。

一般Smurf-attack攻击的解决方式有——对于主机,设置相应的网段,不在该网段的ICMP ECHO REQUEST都不会被相应。或者是对于一个子网路由器,丢弃掉发入子网的广播ECHO REQUEST。由于现在新的路由器都默认disable广播流量,因此smurf攻击基本已经成为历史了。

伪造IP的方式不一定在攻击者主机上面实现,比如在 DNS Amplification Attack 攻击中,攻击者可以通过UDP包,将目标主机的真实地址关联一个无关的IP,注入DNS服务器,将大量无关请求全部导向目标主机。

网络信息收集

通过ICMP攻击可以用来收集网络拓扑结构,首先,通过ECHO REQUEST可以探知一个主机的存在性,而通过一个TTL依次递减的一系列ECHO REQUEST包,观察其ICMP Time Exceeded 包的发送源即可获得一条网络的边。

甚至通过ECHO REPLY包,我们还可以探知目标机器的OS类型。返回TTL=128的机器是Windows系的,而TTL=64的机器是Linux系的。同时,对于一个任意Code域非空的ICMP包,Windows系主机将返回一个Code域为0的包,而Linux则不会。进一步,不同的系统版本对于TIMESTAMP REQ包的回应也不尽相同,我们可以借此更进一步确认。

这类行为因为不会造成严重的后果,故一般没有进行防范。

Ping of Death攻击

通过发送若干段IP超大包,并让目标主机接受后,拼接出一个大小超过65536的包,导致栈溢出。这种攻击仅发生在早起服务器上,新的服务器都通过修改其收包算法的边界条件判定解决了这个攻击。

总结

对于ICMP攻击的防范一般都需要用户服务器精细配置防火墙,或是在边界路由器中设置过滤规则。普通的丢弃ICMP包也是可行的,但是会使得很多网络诊断工具不可用。在参考了有限资料后,我认为ICMPv4和ICMPv6在上述攻击的防御上几乎没有区别,ICMPv6协议本身没有增加额外的防范机制的支持。

参考文献

ICMPv4 and ICMPv6: https://notes.shichao.io/tcpv1/ch8/

ICMP attacks:  https://resources.infosecinstitute.com/icmp-attacks/#gref

ICMP tunnels: https://www.notsosecure.com/icmp-tunnels-a-case-study/

ICMP floods: https://www.cloudflare.com/learning/ddos/ping-icmp-flood-ddos-attack/

Express的Session不同步问题

近期使用express做项目,发现遇到了一个神奇的bug,从现象上来看是用户的session并不同步。具体来说有如下现象——

  • 页面A在session中加入了某个字段。并输出session,发现字段已经成功加入。
  • 页面A通过setTimeout设置5秒后再次输出session,发现字段仍然存在。
  • 页面B在页面A加载后的5秒内访问同用户的session,发现字段不见了。

经过研究,解决方式为在session创建时,指明resave选项为false——

resave字段为false的时候,倘若一个请求未修改session本身,则session不会重新写回内存。通常倘若两个同时发生的请求,只有一个修改了session。当resave为true时,未修改session的请求的session写回操作很容易覆盖另一个请求对于session的修改。