你知道吗?截屏可能会泄密 解密:阿里巴巴根据截图查泄露信息员工的技术

本文通过一个的实验,简要介绍频域手段添加数字盲水印的方法,并进一步验证其抗攻击性。在上述实验的基础上,总结躲避数字盲水印的方法。(多图预警)

本文分为四个部分,第一部分综述;第二部分频域数字盲水印制作原理介绍;第三部分盲水印攻击性实验;第四部分总结;

一、综述

本文提供的一种实现“阿里通过肉眼无法识别的标识码追踪员工”的技术手段。通过看其他答主的分析,阿里可能还没用到频域加水印的技术。

相对于空域方法,频域加盲水印的方法隐匿性更强,抵抗攻击能力更强。这类算法解水印困难,你不知道水印加在那个频段,而且受到攻击往往会破坏图像原本内容。本文简要科普通过频域手段添加数字盲水印。对于web,可以添加一个背景图片,来追踪截图者。

所谓盲水印,是指人感知不到的水印,包括看不到或听不见(没错,数字盲水印也能够用于音频)。其主要应用于音像作品、数字图书等,目的是,在不破坏原始作品的情况下,实现版权的防护与追踪。

添加数字盲水印的方法简单可分为空域方法和频域方法,这两种方法添加了冗余信息,但在编码和压缩情况不变的情况下,不会使原始图像大小产生变化(原来是10MB添加盲水印之后还是10MB)。

空域是指空间域,我们日常所见的图像就是空域。空域添加数字水印的方法是在空间域直接对图像操作(之所以说的这么绕,是因为不仅仅原图是空域,原图的差分等等也是空域),比如将水印直接叠加在图像上。

我们常说一个音有多高,这个音高是指频率;同样,图像灰度变化强烈的情况,也可以视为图像的频率。频域添加数字水印的方法,是指通过某种变换手段(傅里叶变换,离散余弦变换,小波变换等)将图像变换到频域(小波域),在频域对图像添加水印,再通过逆变换,将图像转换为空间域。相对于空域手段,频域手段隐匿性更强,抗攻击性更高。

所谓对水印的攻击,是指破坏水印,包括涂抹,剪切,放缩,旋转,压缩,加噪,滤波等。数字盲水印不仅仅要敏捷性高(不被人抓到),也要防御性强(抗打)。就像Dota的敏捷英雄往往是脆皮,数字盲水印的隐匿性和鲁棒性是互斥的。(鲁棒性是抗攻击性的学术名字)

二、频域制作数字盲水印的方法

信号是有频率的,一个信号可以看做是无数个不同阶的正弦信号的的叠加。

解密:阿里巴巴根据截图查泄露信息员工的技术

上式为傅里叶变换公式,是指时域信号(对于信号我们说时域,因为是与时间有关的,而图像我们往往说空域,与空间有关),是指频率。想要对傅里叶变换有深入了解的同学,建议看一下《信号与系统》或者《数字信号处理》的教材,里面系统介绍了傅里叶变换、快速傅里叶变换、拉普拉斯变换、z变换等。

简而言之,我们有方法将时域信号转换成为频域,同样,我们也能将二维信号(图像)转换为频域。在上文中提到,图像的频率是指图像灰度变换的强烈情况。关于此方面更系统的知识,参见冈萨雷斯的《图像处理》。

下面以傅里叶变换为例,介绍通过频域给图像添加数字盲水印的方法。注意,因为图像是离散信号,我们实际用的是离散时间傅里叶变换,在本文采用的都是二维快速傅里叶变换,快速傅里叶变换与离散时间傅里叶变换等价,通过蝶型归并的手段,速度更快。下文中傅里叶变换均为二维快速傅里叶变换。

解密:阿里巴巴根据截图查泄露信息员工的技术

上图为叠加数字盲水印的基本流程。编码的目的有二,一是对水印加密,二控制水印能量的分布。以下是叠加数字盲水印的实验。

这是原图像,尺寸300*240

解密:阿里巴巴根据截图查泄露信息员工的技术

之后进行傅里叶变换,下图变换后的频域图像,

解密:阿里巴巴根据截图查泄露信息员工的技术

这是我想加的水印,尺寸200*100,

解密:阿里巴巴根据截图查泄露信息员工的技术

这是我编码后的水印,编码方式采用随机序列编码,通过编码,水印分布到随机分布到各个频率,并且对水印进行了加密,

解密:阿里巴巴根据截图查泄露信息员工的技术

将上图与原图的频谱叠加,可见图像的频谱已经发生了巨大的变化,

解密:阿里巴巴根据截图查泄露信息员工的技术

之后,将叠加水印的频谱进行傅里叶逆变换,得到叠加数字水印后的图像,

解密:阿里巴巴根据截图查泄露信息员工的技术

肉眼几乎看不出叠加水印后的图像与原图的差异,这样,数字盲水印已经叠加到图像中去。

实际上,我们是把水印以噪声的形式添加到原图像中。

下图是在空域上的加水印图与原图的残差(调整了对比度,不然残差调小看不见),

解密:阿里巴巴根据截图查泄露信息员工的技术

可以看出,实际上上述方法是通过频域添加冗余信息(像噪声一样)。这些噪声遍布全图,在空域上并不容易破坏。

最终,均方误差(MSE)为0.0244

信噪比(PSNR)为64.2dB

那么,为什么频谱发生了巨大的变化,而在空域却变化如此小呢?这是因为我们避开了图像的主要频率。下图是原图频谱竖过来的样子,其能量主要集中在低频。

解密:阿里巴巴根据截图查泄露信息员工的技术

水印提取是水印叠加的逆过程,

解密:阿里巴巴根据截图查泄露信息员工的技术

经提取后,我们得到如下水印,问:为什么水印要对称呢?嘿嘿,大家想想看。

解密:阿里巴巴根据截图查泄露信息员工的技术

解密:阿里巴巴根据截图查泄露信息员工的技术

三、攻击性实验

本部分进行攻击性实验,来验证通过频域手段叠加数字盲水印的鲁棒性。

1.进行涂抹攻击,这是攻击后的图片:

解密:阿里巴巴根据截图查泄露信息员工的技术

再进行水印提取:

解密:阿里巴巴根据截图查泄露信息员工的技术

2.进行剪切攻击,就是网上经常用的截图截取一部分的情况:

解密:阿里巴巴根据截图查泄露信息员工的技术

进行循环补全:

解密:阿里巴巴根据截图查泄露信息员工的技术

提取水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

3.伸缩攻击(这个实验明码做的,水印能量较高,隐匿性不强):

解密:阿里巴巴根据截图查泄露信息员工的技术

提取水印(水印加的不好,混频挺严重的):

解密:阿里巴巴根据截图查泄露信息员工的技术

4.旋转攻击(明码):

解密:阿里巴巴根据截图查泄露信息员工的技术

提取水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

5.JPEG压缩后(这个实验我好像是拿明码做的,能量主要加在了高频):

解密:阿里巴巴根据截图查泄露信息员工的技术

提取结果:

解密:阿里巴巴根据截图查泄露信息员工的技术

6. PS 4像素马赛克/均值滤波等,攻击后图像

解密:阿里巴巴根据截图查泄露信息员工的技术

提取水印后图像:

解密:阿里巴巴根据截图查泄露信息员工的技术

7.截屏,

截屏后我手动抠出要测试的图像区域,并且抽样或者插值到原图尺寸:

解密:阿里巴巴根据截图查泄露信息员工的技术

测试结果:

解密:阿里巴巴根据截图查泄露信息员工的技术

8.亮度调节(明码):

解密:阿里巴巴根据截图查泄露信息员工的技术

水印提取:

解密:阿里巴巴根据截图查泄露信息员工的技术

9.色相调节(明码):

解密:阿里巴巴根据截图查泄露信息员工的技术

水印提取:

解密:阿里巴巴根据截图查泄露信息员工的技术

10.饱和度调节(明码):

解密:阿里巴巴根据截图查泄露信息员工的技术

水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

11.对比度(明码):

解密:阿里巴巴根据截图查泄露信息员工的技术

水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

12.评论区用waifu2x去噪后图片:

解密:阿里巴巴根据截图查泄露信息员工的技术

解水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

13.美图秀秀,我对我女票一键美颜,美白,磨皮,加腮红,加唇彩(有一种很羞耻的感觉,捂脸):

解密:阿里巴巴根据截图查泄露信息员工的技术

提取水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

14.对于背景纯色的图其实也是无所谓的

解密:阿里巴巴根据截图查泄露信息员工的技术

能量系数为10时加水印图片:觉得太显噪就把能量系数调低,不过水印的隐秘性和鲁棒性是互斥的

解密:阿里巴巴根据截图查泄露信息员工的技术

最终提取出的水印:

解密:阿里巴巴根据截图查泄露信息员工的技术

15.我用将RGB>600的像素设置成为(0,255,0)来模拟PS魔术手,

解密:阿里巴巴根据截图查泄露信息员工的技术

提取水印为:

解密:阿里巴巴根据截图查泄露信息员工的技术

16.屏摄,好吧,这个实验我做哭了

屏摄图:

解密:阿里巴巴根据截图查泄露信息员工的技术

实验结果:

解密:阿里巴巴根据截图查泄露信息员工的技术

我把水印能量系数调整到2000都没有用。

屏摄之后与原图信噪比为4dB左右,我用多抽样滤波的方式试过,滤不掉屏摄引入的噪声。屏摄不仅引入了椒盐噪声,乘性噪声,还有有规律的雪花纹理(摩尔纹)。

四、总结

基于频域的盲水印方法隐藏性强,鲁棒性高,能够抵御大部分攻击。但是,对于盲水印算法,鲁棒性和隐匿性是互斥的。

本文方法针对屏摄不行,我多次实验没有成功,哪位大神可以做一下或者讨论讨论。还有二值化不行,这是我想当然的,觉得肯定不行所以没做实验。其他的我试了试,用给出的方法调整一下能量系数都可以。

我想大家最关心的是什么最安全,不会被追踪。

不涉及图像的都安全,比如拿笔记下来。

涉及图像的屏摄最安全,

截屏十分不安全。

38个攻略!教你给文章起个好标题

网编最大的功力是什么?就是在24个字符之内把文字拼凑的有创意,让人有点击的欲望。一篇文章内容一般,但是点击量是100万+,很快、很迅速,所有人都愿意分享,可能就命中了一个痛点,使得流量激增。

如果没有一个好的标题,没有好的关键词吸引人来点击,这个文案和广告就不会成功,因为人家连点击的欲望都没有。

起标题应该有一个清晰的思路,比如要结合什么样的事件,我的定位,这里是展示企业的信息和内容。

我建议小编们做资料库,分为图片的功能库和经典范例的资料库。我们会按照类别分,如果碰到紧急事件,突发新闻,再找这样的专题的时候,迅速就可以拿出来放到文章里面,第一时间抢发。

标题范例

1.在标题里提出疑问

微商到底还能走多远?为什么说微商大部分将要被淘汰?我们经常可以看到行业类的文章,如果你的标题是提出疑问式,用户群体一定会关心。在介绍技巧的同时,软性地把自己的品牌植入是很好的方式。疑问式和相应的内容:《微商会被淘汰?》这样的标题切中了微商群体,切中了他们的命运和疑问。

2.结合时事

郭美美的“肉体买卖“经济学:为何能夜入几十万?如何塑造个人品牌?跟苪成钢学装X不完全指南;“阅兵仪式背后默默疑问的英雄们”,这也是结合时事,并且有长尾效应。一个事件结束之后还有一个讨论的空间,这时候进行完美的结合就很好了,也算是结合时事。

3.创造新名词

揭开”新科技美容“神秘面纱,为您详解什么叫“科技美容生活化”。大家可以结合产品,结合要销售的东西,结合最近要突出的服务,把它包装成一个新名词。

4.传递新消息,并运用“新推出”“引进”“宣布”这类词汇

微商洗牌在即!各大传统化妆品巨头宣布进军微商圈!纷纷新推出微商子品牌!

5.给读者建议,告诉读者应该采取哪些行动

立刻把这篇文章收藏并转发到朋友圈,你会发现与你思维观点相同的那群人。

6.利用数字与数据

让公众号在3天内吸收3200位精准用户的技巧!这种数据大家会比较感兴趣。结合时事从选题到标题,建议大家只要事件很火的时候,要定期看百度、风云榜、新闻的排行榜,文章的标题要刚好击中用户的痛点,再结合自己的品牌。

7.承诺会提供对读者有用的信息

如何避免在朋友圈购买产品时犯下大错。

8.强调你能提供的服务

即日起,我们的新款袜子提供微信预购,就如同订杂志一样简单。提出推荐性的意见,做比较,说明好处,让读者脑海中浮现画面。

9.讲故事,描述一段过程

我坐在电脑前时,他们还在群里讨论今晚的培训课程,然而当我开始回忆……

10.提出推荐性的意见

现在就必须关注的五大全新移动电商化妆品品牌。

11.说明好处

删除我们朋友圈的“僵尸好友”,已经从困难变容易。

12.做比较

只需要支付雅诗兰黛一半的价格,就能够解决您的皮肤粗糙干燥暗黄等问题。

13.使用能够让读者脑中浮现画面的词汇

为什么有些伪劣化妆品会在你的皮肤里“投下剧毒”?

14.引述见证

超过1万8千份体验礼盒的试用反馈报告证明,我们的产品至今没有产生过敏刺激等不良反应!或者这样一个标题,月均8%,这么选就对了。这样的标题,大家也会点进来,因为股票基金在赔钱,这个大环境结合了时事,又跟理财相关,大家也会点击。

15.提供免费的特别报告、目录或宣传册

我们的报告免费揭露那些大咖鲜为人知的秘密,告诉你所谓自媒体明星如何赚钱、并且利用粉丝快速致富。

16.直接点出服务内容

O2O吸粉WIFI路由神器,所有经销商都免费赠送,帮您线下拓客!

17.勾起读者的好奇心

你必须买进的理财通理财基金,不是你想的那一支!

18.承诺要公开秘密

揭露朋友圈所谓自媒体大咖的阴暗勾当!走近“传说中”的“XX联盟”!

19.具体说明

在时速60英里的驰骋下,新劳斯莱斯的最大噪音来自电子钟。

20.锁定特定类型读者

征求专业线美容院从业者。

21.加入时间元素

不必麻烦,在家里也能教你半小时做美容。

22.请强调省钱、折扣或价值

价值66元的全新美容科技化妆品体验礼盒,现在只需要0.1元就可以得到!提供读者无法再其它地方得到的度假好处。

23.给读者好消息

在家里带孩子也能拥有创业的好机会!

24.提供能够取代竞争对手产品及服务的其他选择

没地方存货?没时间发货给自己的客户?没事,我们帮你工厂直接代发!

25.提出一项挑战

你的头皮健康经得起指甲测试吗?

26.强调有保证

我们代运营承诺:保证您的品牌在朋友圈的成交额提高3倍,否则退费。

27.名列价格

一整箱味道工坊牛肉干,只需要99元!

28.做出看似矛盾的说法

靠“内线交易”致富,而且100%合法!

29.提供读者无法在其他地方得到的独家好处

鲜为人知的交易秘密武器,让您获利翻5倍以上。

30.提出读者关心的事

为什么大部分的微商卖货只会刷屏?最终以失败收场?我们提供突破之道。

31.不妨用“听起来难以置信。。。”句型

听起来难以置信,但今天一款刚刚上市的新产品,不久的未来可能将改变整个行业的格局。

32.画大饼

让您年轻20岁!

33.强调商品的投资报酬率

34.运用“为什么”“原因”“理由”来写

制作公司在拍摄重要的电视广告时,偏好采用Unilus Strobe牌灯光设备的7大理由。

35.回答关于商品或服务的重要问题

面对微信零售客户时要回答的7个问题……我们对每个问题都有好答案。

36.强调买就送

免费送给您。现在订购就送价值888元的免费好礼。

37.协助读者达成目标

帮您在未来30天内推出微营销突破性的营销计划,而且完全免费!

38.做出看似矛盾的说法或承诺

不需要加盟费,只要您想打造自己的微商团队,就能帮您实现梦想!

作者介绍:苏超,网络事业群\移动社区产品中心高级讲师,专长产品运营。曾就职于CCTV2,担任财经记者和编导。2010年进入腾讯公司,从事过腾讯博客\新闻客户端\微博\微视\微社区等多部门的运营工作,带领腾讯博客1000多名写手,通过4年多的时间经历和见证了微信的诞生与发展。

携程被攻击事件的深入解析和深刻反思

携程网今日出现宕机事件。关于事故根源,网上众说纷纭。作为互联网运维老兵,尝试分析原因,谈谈我的看法。

宕机原因分析

网上有各种说法,有说是数据库数据和备份数据被物理删除的。也有说是各个节点的业务代码被删除,现在重新在部署。也有说是误操作,导致业务不可用,还有说是黑客攻击甚至是内部员工恶意破坏的。

先说一下最早传出来的“数据库物理删除”,其实这个提法就很不专业,应该是第一个传播者,试图强调问题之严重和恢复之困难,所以用了一个普通电脑用户比较熟悉的“物理删除”的概念。实际上,任何一个网站的数据库,都分为本地高可用备份、异地热备、磁带冷备三道防线,相应的数据库管理员、操作系统管理员、存储管理员三者的权限是分离的,磁带备份的数据甚至是保存在银行的地下金库中的。从理论上而言,很难有一个人能把所有的备份数据都删除,更不用说这个绘声绘色的物理删除了。

第二个则是内部员工破坏的说法,这个说法能满足一些围观者猎奇的心理,因此也传播的比较快。但理性分析,可能性也不大。我还是相信携程的运维人员的操守和职业素养,在刑法的威慑下,除非像“法航飞行员撞山”那种极个别案列,正常情况下不太可能出现人为恶意的可能性。

从现象上看,确实是携程的应用程序和数据库都被删除。我分析,最大的可能还是运维人员在正常的批量操作时出现了误操作。我猜测的版本是:携程网被“乌云”曝光了一个安全漏洞,漏洞涉及到了大部分应用服务器和数据库服务器;运维人员在使用pssh这样的批量操作执行修复漏洞的脚本时,无意中写错了删除命令的对象,发生了无差别的全局删除,所有的应用服务器和数据库服务器都受到了影响。这个段子在运维圈子中作为笑话流传了很多年,没想到居然真的有这样一天。

为什么恢复的如此缓慢?

从上午11点传出故障,到晚上8点,携程网站一直没能恢复。所以很多朋友很疑惑:“为什么网站恢复的如此缓慢?是不是数据库没有备份了?”这也是那个“数据库物理删除”的说法很流行的一个根源。实际上这个还是普通用户,把网站的备份和恢复理解成了类似我们的笔记本的系统备份和恢复的场景,认为只有有备份在,很快就能导入和恢复应用。

实际上大型网站,远不是像把几台应用和数据库服务器那么简单。看似很久都没有变化的一个网站,后台是一个由SOA(面向服务)架构组成的庞大服务器集群,看似简单的一个页面背后由成百上千个应用子系统组成,每个子系统又包括若干台应用和数据库服务器,大家可以理解为每一个从首页跳转过去的二级域名都是一个独立的应用子系统。这上千的个应用子系统,平时真正经常发布和变更的,可能就是不到20%的核心子系统,而且发布时都是做加法,很少完全重新部署一个应用。

在平时的运维过程中,对于常见的故障都会有应急预案。但像携程这次所有系统包括数据库都需要重新部署的极端情况,显然不可能在应急预案的范畴中。在仓促上阵应急的情况下,技术方案的评估和选择问题,不同技术岗位之间的管理协调的问题,不同应用系统之间的耦合和依赖关系,还有很多平时欠下的技术债都集中爆发了,更不用说很多不常用的子系统,可能上线之后就没人动过,一时半会都找不到能处理的人。更要命的是,网站的核心系统,可能会写死依赖了这个平时根本没人关注的应用,想绕开边缘应用只恢复核心业务都做不到。更别说在这样的高压之下,各种噪音和干扰很多,运维工程师的反应也没有平时灵敏。

简单的说,就算所有代码和数据库的备份都存在,想要快速恢复业务也非常困难。携程的工程师今天肯定是一个不眠夜。能在24小时之内恢复核心业务,就已经非常厉害了。

天下运维是一家。携程的同行加油,尽快度过难关!

故障根源反思:黑盒运维之殇

携程的这次事件,不管原因是什么,都会成为IT运维历史上的一个标志性事件。相信之后所有的IT企业和技术人员,都会去认真的反思,总结经验教训。但我相信,不同的人在不同的位置上,看到的东西可能是截然相反的,甚至可能会有不少企业的管理者受到误导,开始制定更严格的规章制度,严犯运维人员再犯事。在此,我想表明一下我的态度:这是一个由运维引发的问题,但真正的根源其实不仅仅在运维,预防和治理更应该从整个企业的治理入手。

长久以来,在所有的企业中,运维部门的地位都是很边缘化的。企业的管理者会觉得运维部门是成本部门,只要能支撑业务就行。业务部门只负责提业务需求,开发部门只管做功能的开发,很多非功能性的问题无人重视,只能靠运维人员肩挑人扛到处救火,可以认为是运维部门靠自己的血肉之躯实现了业务部门的信息化。在这样的场景下,不光企业的管理者不知道该如何评价运维的价值,甚至很多运维从业者都不知道自己除了到处救火外真正应该关注什么,当然也没有时间和精力去思考。

在上文的情况下,传统的运维人员实际上是所谓的“黑盒运维”,不断的去做重复性的操作,时间长了之后,只知道自己管理的服务器能正常对外服务,但是却不知道里面应用的依赖关系,哪些配置是有效配置、哪些是无效配置,只敢加配置,不敢删配置,欠的技术债越来越多。在这样的情况下,遇到这次携程的极端案列,需要完整的重建系统时候,就很容易一筹莫展了。

对于这样的故障,我认为真正有效的根源解决做法是从黑盒运维走向白盒运维。和Puppet这样的运维工具理念一致,运维的核心和难点其实是配置管理,运维人员只有真正的清楚所管理的系统的功能和配置,才能从根源上解决到处救火疲于奔命的情况,也才能真正的杜绝今天携程这样的事件重现,从根本上解决运维的问题。

从黑盒运维走向白盒运维,再进一步实现DevOps(开发运维衔接)和软件定义数据中心,就是所谓的运维2.0了。很显然,这个单靠运维部门自身是做不到的,需要每一个企业的管理者、业务部门、开发部门去思考。因此,我希望今天这个事件,不要简单的让运维来背黑锅,而是让大家真正的从中得到教训和启示。

如何破解安卓手机图案解锁

安卓手机的图形锁(九宫格)是3×3的点阵,按次序连接数个点从而达到锁定/解锁的功能。最少需要连接4个点,最多能连接9个点。网上也有暴力删除手机图形锁的方法,即直接干掉图形锁功能。但假如你想进入别人的手机,但又不想引起其警觉的话……你可以参考一下本文。

前提条件:手机需要root,而且打开调试模式。一般来讲,如果用过诸如“豌豆荚手机助手”、“360手机助手”一类的软件,都会被要求打开调试模式的。如果要删除手机内置软件,则需要将手机root。

原理分析

首先科普一下,安卓手机是如何标记这9个点的。通过阅读安卓系统源码可知,每个点都有其编号,组成了一个3×3的矩阵,形如:

00 01 02

03 04 05

06 07 08

假如设定解锁图形为一个“L”形,如图:

那么这几个点的排列顺序是这样的:00 03 06 07 08。系统就记下来了这一串数字,然后将这一串数字(以十六进制的方式)进行SHA1加密,存储在了手机里的/data/system/gesture.key文件中。我们用数据线连接手机和电脑,然后ADB连接手机,将文件下载到电脑上(命令:adb pull /data/system/gesture.key gesture.key),如图:

用WinHex等十六进制编辑程序打开gesture.key,会发现文件内是SHA1加密过的字符串:c8c0b24a15dc8bbfd411427973574695230458f0,如图:

当你下次解锁的时候,系统就对比你画的图案,看对应的数字串是不是0003060708对应的加密结果。如果是,就解锁;不是就继续保持锁定。那么,如果穷举所有的数字串排列,会有多少呢?联想到高中的阶乘,如果用4个点做解锁图形的话,就是9x8x7x6=3024种可能性,那5个点就是15120,6个点的话60480,7个点181440,8个点362880,9个点362880。总共是985824种可能性(但这么计算并不严密,因为同一条直线上的点只能和他们相邻的点相连)。

满打满算,也不到985824种可能性。乍一看很大,但在计算机面前,穷举出来这些东西用不了几秒钟。

破解过程

知道了原理,就着手写程序来实现吧。这里使用了Python来完成任务。主要应用了hashlib模块(对字符串进行SHA1加密)和itertools模块(Python内置,生成00-09的排列组合)。

主要流程为:

1、ADB连接手机,获取gesture.key文件

2、读取key文件,存入字符串str_A

3、生成全部可能的数字串

4、对这些数字串进行加密,得到字符串str_B

5、将字符串str_A与str_B进行对比

6、如果字符串A,B相同,则说明数字串num就是想要的解锁顺序

7、打印出数字串num

下面为程序:

总结

从程序本身来说,得到解锁密码后应该用break跳出循环并终止程序运行。但Python并没有跳出多重循环的语句,如果要跳出多重循环,只能设置标志位然后不停进行判定。为了运行速度就略去了“跳出循环”这个步骤。(有没有更好的实现跳出多重循环的方法?)另外也略去了很多容错语句。

从破解目的来说,如果单单是忘记了自己的手机图形锁密码,完全可以用更简单的办法:ADB连接手机,然后“adb rm /data/system/gesture.key”删除掉gesture.key文件,此时图形锁就失效了,随意画一下就能解锁。但本文开篇假设的是“为了不被察觉地进入到别人的手机里”,所以就有了这篇文章。

最后提一个安全小建议:如果手机已root,还要用“XX手机助手”,还想设置图形锁的话——在手机“设置”选项里,有一个“锁定状态下取消USB调试模式”(这个名字因手机而异,而且有的有此选项,有的手机就没有),开启此功能之后,在手机锁定状态下就能够防范此类攻击了。此文技术原理很简单,还望各位大大传授些高大上的Python编程技巧。

跟程序员打交道的十大禁忌

如果你正在读这篇文章,那么非常有可能是有人发给你了这条链接。这个人可能是你的朋友,同事,亲戚,父母,儿子,兄弟,表亲,姑嫂,外甥,或者恰巧帮你解决了电脑问题的一个人。他通常是程序员,系统管理员,或者“擅长电脑”名声在外的某个人。

这篇文章的初衷是想要澄清跟程序员打交道的错误方式和禁忌。

1、不要“一遇到问题就去找程序员”

通常,技术问题通过阅读使用说明就可以解决。比如你刚买了一个新的播放器,想要把它连接到你的电视,你只需要找到使用手册里关于如何连接接口的那一页即可。

错误信息通常会被很清晰地列出来。通过仔细阅读并加以思考这个信息传达了什么来解决这个问题是非常有可能的。例如,如果你看到一条信息“你的硬盘已经快满了”,通常这就意味着你的硬盘要满了。为了安抚你的电脑,你需要删除一些你不再需要的文件(或者将它们转移到外部的移动硬盘中)。

2、不要认为“程序员对电脑无所不知”

朋友或某个亲戚可能很擅长电脑,但他们并非无所不知。一般他们也完全不知道怎么使用你电脑中出问题的程序,仅仅是使用一些简单的逻辑判断或google搜索来帮你找到解决方案。

实际上,用谷歌搜索已经足够了,因为它会引导你找到相关的论坛或者博客,早在你遇到这个问题之前已经有网友在讨论并且给出了一个大概的解决方案。通常,你可以不用麻烦你的免费技术支持顾问(你的程序员朋友)。

如果你试过了并且失败了,那么,请无论如何都要找他帮忙。

3、不要说“这并不费什么事儿,你几分钟就可以搞定”

你的程序员朋友可能会提供免费的技术支持,但这并不是说这件事完全不会耗费什么。一般解决一个问题远不止需要两分钟,再加上到你那儿的车程。依据我的经验,平均来说,从你联系他们解决这个问题开始到他们回到家(除非你们是住在一起)在一个小时之内已经是幸运的了。

如果他们不接受报酬,因为他们是家人或者一个老朋友,请依然要考虑补偿他们的时间。偶尔的一件礼物和一张致谢卡或者慷慨的姿态真的可以让他们更欣赏自己的努力。

4、不要以为“他们有义务对你提供无偿帮助”

一些骄傲的父母或者家庭成员会把自己程序员好友的联系信息发送给家族的朋友,这并不罕见。如果这些朋友准备支付酬劳,那没什么问题。然而,这么说吧,向朋友提供免费的程序员服务并不酷。

程序员可能很好心地想要帮助你,但是他们这么做并没有授予你拿他做筹码取悦别人的权利。

5、不要对程序员说“从你动了我的电脑之后,它就不能工作了”

有些人请你提供免费的技术支持,然后因为电脑之后出现的另一个问题控诉你,没有比这更糟的了。实际上,如果程序员仅是远程协助的话,他们压根儿不是导致你电脑变慢或者窗口崩溃的原因。

你确定你没有在他们走后装一些新东西?删除了一些你本不应该删的文件?电脑中了病毒?罪魁祸首怎么可能是专家呢。

6、程序员告诉你别乱装软件,而你一意孤行

如果你信任的程序员阻止你装工具栏,笑脸符号,可疑的注册表清理软件和广告软件,你就不要非得装上。这些程序可能会攻击你的电脑,最后你还得再一次打给他让他来修电脑。要听程序员的话。

7、不要向程序员乱转发邮件

不要给程序员转发那些连锁邮件。他们已经看过数不清的这类邮件,知道大多数情况下很明显都是谣言。就算不是谣言,许多请求在你看到并转发的时候也已经很悲剧地过期了。例如,那个寻找骨髓捐赠的孩子,已经去世好几年了。你的转发只会导致越来越多的人联系他悲伤的家人。

如果程序员回复了你的信息,附了一条Snopes的链接。不要觉得被冒犯了。这无关个人。从这个小插曲里吸取教训,记得忍住不要去转发这类邮件(或者在鉴别真伪前就在Facebook上分享某个故事)

8、“他太多疑了,这是一笔大买卖。”

如果你信任的程序员告诉你网上的某事是个骗局,靠不住的,相信他们。无论是你从未亲身遇到过的外国王子或者完美恋人,成为秘密买手的机会,或者某个伪装成你的银行职员需要验证你的登录认证信息,你正在处理的诈骗邮件被设计成利用人性中的轻信。

和技术及互联网相关的所有事情都请相信程序员。实际上,关于一些网上常见的陷阱一定要问问他们,以免你自己陷于其中。

9、“我有个非常棒的创业想法,你可以帮我实现吗?我们对半分。”

实现一个创业想法并不是一个简单的任务。大多数情况下,你绝妙的创意或服务如果没有认真地思考,投入时间、精力和金钱是不能被创造出来的。即便是在全职工作也要讨论数周甚至数月。

你会请你的表兄,一个建筑工人,抛开原材料的成本帮你免费建造一座房子吗?

甚至如果你的想法很好,在你并没有可能真正实现的情况下,请你的程序员朋友实现并不意味着你可以拿到50%的收益。

想法本身并没有太多价值,如果你是一个企业的合伙人,在市场和生意上像程序员在技术上花费一样多的时间,然后并且是只有这样后你才可以要求对半分。

10、“扎克伯格赚了几十亿,为什么你不行?”

“嗨,John,我在新闻上看什么扎克伯格用Facebook赚了好几十个亿。你为什么不也做点儿这样的事情?我们得为下一代做点儿什么呀。”你知道外面有多少扎克伯格和Facebook吗?他们都是在统计分布的一端。基本上,你的程序员朋友中大乐透的概率和他们成为下一个扎克伯格的概率差不多。

不要误会我们程序员。通过在线的应用和网址是有可能创造财富的,但是只有极少数才能完成像Facebook这种不太可能的壮举。如果你的程序员朋友非常擅长他所作的事情,野心勃勃,并且日以继夜的努力工作,那就有可能看到他们的商业变得可行。再有点儿运气的话,他们有可能成为百万富翁。但是不要指望他圆满完成更不用会所挣个几十亿。同样地,不要因为他们买不起私人岛屿就认为他们是个loser。他们的出色的完成工作,日复一日做一些很棒的有用的或者创造性的事情,就像适用于其他职业道路一样,这在技术领域是非常值得骄傲的事情。

就这样。尊重程序员,你可以享受他们免费的技术支持,长期的专家服务。

Loadavg问题分析

Loadavg分析

Loadavg浅述

cat /proc/loadavg可以看到当前系统的load
$ cat /proc/loadavg
0.01 0.02 0.05 2/317 26207
前面三个值分别对应系统当前1分钟、5分钟、15分钟内的平均load。load用于反映当前系统的负载情况,对于16核的系统,如果每个核上cpu利用率为30%,则在不存在uninterruptible进程的情况下,系统load应该维持在4.8左右。对16核系统,如果load维持在16左右,在不存在uninterrptible进程的情况下,意味着系统CPU几乎不存在空闲状态,利用率接近于100%。结合iowait、vmstat和loadavg可以分析出系统当前的整体负载,各部分负载分布情况。

Loadavg读取

在内核中/proc/loadavg是通过load_read_proc来读取相应数据,下面首先来看一下load_read_proc的实现:

fs/proc/proc_misc.c
static int loadavg_read_proc(char *page, char **start, off_t off, 
                                 int count, int *eof, void *data) 
{ 
        int a, b, c; 
        int len; 

        a = avenrun[0] + (FIXED_1/200); 
        b = avenrun[1] + (FIXED_1/200); 
        c = avenrun[2] + (FIXED_1/200); 
        len = sprintf(page,"%d.%02d %d.%02d %d.%02d %ld/%d %d\n", 
                LOAD_INT(a), LOAD_FRAC(a), 
                LOAD_INT(b), LOAD_FRAC(b), 
                LOAD_INT(c), LOAD_FRAC(c), 
                nr_running(), nr_threads, last_pid); 
        return proc_calc_metrics(page, start, off, count, eof, len); 
}

几个宏定义如下:

#define FSHIFT          11              /* nr of bits of precision */ 
#define FIXED_1         (1<<FSHIFT)     /* 1.0 as fixed-point */ 
#define LOAD_INT(x) ((x) >> FSHIFT) 
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)

根据输出格式,LOAD_INT对应计算的是load的整数部分,LOAD_FRAC计算的是load的小数部分。
将a=avenrun[0] + (FIXED_1/200)带入整数部分和小数部分计算可得:

LOAD_INT(a) = avenrun[0]/(2^11) + 1/200
LOAD_FRAC(a) = ((avenrun[0]%(2^11) + 2^11/200) * 100) / (2^11)
             = (((avenrun[0]%(2^11)) * 100 + 2^10) / (2^11)
             = ((avenrun[0]%(2^11) * 100) / (2^11) + ½

由上述计算结果可以看出,FIXED_1/200在这里是用于小数部分第三位的四舍五入,由于小数部分只取前两位,第三位如果大于5,则进一位,否则直接舍去。

临时变量a/b/c的低11位存放的为load的小数部分值,第11位开始的高位存放的为load整数部分。因此可以得到a=load(1min) * 2^11
因此有: load(1min) * 2^11 = avenrun[0] + 2^11 / 200
进而推导出: load(1min)=avenrun[0]/(2^11) + 1/200
忽略用于小数部分第3位四舍五入的1/200,可以得到load(1min)=avenrun[0] / 2^11,即:
avenrun[0] = load(1min) * 2^11

avenrun是个陌生的量,这个变量是如何计算的,和系统运行进程、cpu之间的关系如何,在第二阶段进行分析。

Loadavg和进程之间的关系

内核将load的计算和load的查看进行了分离,avenrun就是用于连接load计算和load查看的桥梁。
下面开始分析通过avenrun进一步分析系统load的计算。
avenrun数组是在calc_load中进行更新

kernel/timer.c
/* 
* calc_load - given tick count, update the avenrun load estimates. 
* This is called while holding a write_lock on xtime_lock. 
*/ 
static inline void calc_load(unsigned long ticks) 
{ 
        unsigned long active_tasks; /* fixed-point */ 
        static int count = LOAD_FREQ;  
        count -= ticks; 
        if (count < 0) { 
                count += LOAD_FREQ; 
                active_tasks = count_active_tasks(); 
                CALC_LOAD(avenrun[0], EXP_1, active_tasks); 
                CALC_LOAD(avenrun[1], EXP_5, active_tasks); 
                CALC_LOAD(avenrun[2], EXP_15, active_tasks); 
        } 
}
static unsigned long count_active_tasks(void) 
{ 
        return nr_active() * FIXED_1; 
}
#define LOAD_FREQ       (5*HZ)          /* 5 sec intervals */ 
#define EXP_1           1884            /* 1/exp(5sec/1min) as fixed-point */ 
#define EXP_5           2014            /* 1/exp(5sec/5min) */ 
#define EXP_15          2037            /* 1/exp(5sec/15min) */

calc_load在每个tick都会执行一次,每个LOAD_FREQ(5s)周期执行一次avenrun的更新。
active_tasks为系统中当前贡献load的task数nr_active乘于FIXED_1,用于计算avenrun。宏CALC_LOAD定义如下:

#define CALC_LOAD(load,exp,n) \ 
       load *= exp; \ 
       load += n*(FIXED_1-exp); \ 
       load >>= FSHIFT;

用avenrun(t-1)和avenrun(t)分别表示上一次计算的avenrun和本次计算的avenrun,则根据CALC_LOAD宏可以得到如下计算:

avenrun(t)=(avenrun(t-1) * EXP_N + nr_active * FIXED_1*(FIXED_1 – EXP_N)) / FIXED_1
          = avenrun(t-1) + (nr_active*FIXED_1 – avenrun(t-1)) * (FIXED_1 -EXP_N) / FIXED_1

推导出:

avenrun(t) – avenrun(t-1) = (nr_active*FIXED_1 – avenrun(t-1)) * (FIXED_1 – EXP_N) / FIXED_1

将第一阶段推导的结果代入上式,可得:

(load(t) – load(t-1)) * FIXED_1 = (nr_active – load(t-1)) * (FIXED_1 – EXP_N)

进一步得到nr_active变化和load变化之间的关系式:

load(t) – load(t-1) = (nr_active – load(t-1)) * (FIXED_1 – EXP_N) / FIXED_1

这个式子可以反映的内容包含如下两点:
1)当nr_active为常数时,load会不断的趋近于nr_active,趋近速率由快逐渐变缓
2)nr_active的变化反映在load的变化上是被降级了的,系统突然间增加10个进程,
1分钟load的变化每次只能够有不到1的增加(这个也就是权重的的分配)。

另外也可以通过将式子简化为:

load(t)= load(t-1) * EXP_N / FIXED_1 + nr_active * (1 – EXP_N/FIXED_1)

这样可以更加直观的看出nr_active和历史load在当前load中的权重关系 (多谢任震宇大师的指出)

#define EXP_1           1884            /* 1/exp(5sec/1min) as fixed-point */ 
#define EXP_5           2014            /* 1/exp(5sec/5min) */ 
#define EXP_15          2037            /* 1/exp(5sec/15min) */

1分钟、5分钟、15分钟对应的EXP_N值如上,随着EXP_N的增大,(FIXED_1 – EXP_N)/FIXED_1值就越小,
这样nr_active的变化对整体load带来的影响就越小。对于一个nr_active波动较小的系统,load会
不断的趋近于nr_active,最开始趋近比较快,随着相差值变小,趋近慢慢变缓,越接近时越缓慢,并最
终达到nr_active。如下图所示:
文件:load 1515.jpg
也因此得到一个结论,load直接反应的是系统中的nr_active。 那么nr_active又包含哪些? 如何去计算
当前系统中的nr_active? 这些就涉及到了nr_active的采样。

Loadavg采样

nr_active直接反映的是为系统贡献load的进程总数,这个总数在nr_active函数中计算:

kernel/sched.c
unsigned long nr_active(void) 
{ 
        unsigned long i, running = 0, uninterruptible = 0; 

        for_each_online_cpu(i) { 
                running += cpu_rq(i)->nr_running; 
                uninterruptible += cpu_rq(i)->nr_uninterruptible; 
        } 

        if (unlikely((long)uninterruptible < 0)) 
                uninterruptible = 0; 

        return running + uninterruptible; 
}

#define TASK_RUNNING            0 
#define TASK_INTERRUPTIBLE      1 
#define TASK_UNINTERRUPTIBLE    2 
#define TASK_STOPPED            4 
#define TASK_TRACED             8 
/* in tsk->exit_state */ 
#define EXIT_ZOMBIE             16 
#define EXIT_DEAD               32 
/* in tsk->state again */ 
#define TASK_NONINTERACTIVE     64

该函数反映,为系统贡献load的进程主要包括两类,一类是TASK_RUNNING,一类是TASK_UNINTERRUPTIBLE。
当5s采样周期到达时,对各个online-cpu的运行队列进行遍历,取得当前时刻该队列上running和uninterruptible的
进程数作为当前cpu的load,各个cpu load的和即为本次采样得到的nr_active。

下面的示例说明了在2.6.18内核情况下loadavg的计算方法:

18内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load
0HZ+10 1 1 1 0 0 0 0 0 0
5HZ 0 0 0 0 1 1 1 1 4
5HZ+1 0 1 1 1 0 0 0 0 0
5HZ+9 0 0 0 0 0 1 1 1 0
5HZ+11 1 1 1 0 0 0 0 0 0

18内核计算loadavg存在的问题

xtime_lock解析

内核在5s周期执行一次全局load的更新,这些都是在calc_load函数中执行。追寻calc_load的调用:

kernel/timer.c
static inline void update_times(void) 
{  
        unsigned long ticks; 

        ticks = jiffies - wall_jiffies; 
        wall_jiffies += ticks; 
        update_wall_time(); 
        calc_load(ticks); 
}

update_times中更新系统wall time,然后执行全局load的更新。

kernel/timer.c
void do_timer(struct pt_regs *regs) 
{  
        jiffies_64++; 
        /* prevent loading jiffies before storing new jiffies_64 value. */ 
        barrier(); 
        update_times(); 
}

do_timer中首先执行全局时钟jiffies的更新,然后是update_times。

void main_timer_handler(struct pt_regs *regs) 
{ 
...
        write_seqlock(&xtime_lock);
...
        do_timer(regs); 
#ifndef CONFIG_SMP 
        update_process_times(user_mode(regs)); 
#endif 
...
        write_sequnlock(&xtime_lock); 
}

对wall_time和全局jiffies的更新都是在加串行锁(sequence lock)xtime_lock之后执行的。

include/linux/seqlock.h
static inline void write_seqlock(seqlock_t *sl) 
{ 
        spin_lock(&sl->lock);
        ++sl->sequence; 
        smp_wmb(); 
} 

static inline void write_sequnlock(seqlock_t *sl) 
{ 
        smp_wmb(); 
        sl->sequence++; 
        spin_unlock(&sl->lock); 
} 
 
typedef struct { 
        unsigned sequence; 
        spinlock_t lock; 
} seqlock_t;

sequence lock内部保护一个用于计数的sequence。Sequence lock的写锁是通过spin_lock实现的,
在spin_lock后对sequence计数器执行一次自增操作,然后在锁解除之前再次执行sequence的自增操作。
sequence初始化时为0。这样,当锁内部的sequence为奇数时,说明当前该sequence lock的写锁正被拿,
读和写可能不安全。如果在写的过程中,读是不安全的,那么就需要在读的时候等待写锁完成。对应读锁使用如下:

#if (BITS_PER_LONG < 64) 
u64 get_jiffies_64(void) 
{ 
        unsigned long seq; 
        u64 ret;  

        do { 
                seq = read_seqbegin(&xtime_lock); 
                ret = jiffies_64; 
        } while (read_seqretry(&xtime_lock, seq)); 
        return ret; 
} 

EXPORT_SYMBOL(get_jiffies_64); 
#endif 

读锁实现如下:

static __always_inline unsigned read_seqbegin(const seqlock_t *sl) 
{ 
        unsigned ret = sl->sequence; 
        smp_rmb(); 
        return ret; 
} 

static __always_inline int read_seqretry(const seqlock_t *sl, unsigned iv) 
{ 
        smp_rmb(); 
      	/*iv为读之前的锁计数器
        * 当iv为基数时,说明读的过程中写锁被拿,可能读到错误值
        * 当iv为偶数,但是读完之后锁的计数值和读之前不一致,则说明读的过程中写锁被拿,
        * 也可能读到错误值。
        */
        return (iv & 1) | (sl->sequence ^ iv);  
}

至此xtime_lock的实现解析完毕,由于对应写锁基于spin_lock实现,多个程序竞争写锁时等待者会一直循环等待,
当锁里面处理时间过长,会导致整个系统的延时增长。另外,如果系统存在很多xtime_lock的读锁,在某个程
序获取该写锁后,读锁就会进入类似spin_lock的循环查询状态,直到保证可以读取到正确值。因此需要尽可能
短的减少在xtime_lock写锁之间执行的处理流程。

全局load读写分离解xtime_lock问题

在计算全局load函数calc_load中,每5s需要遍历一次所有cpu的运行队列,获取对应cpu上的load。1)由于cpu个数是不固
定的,造成calc_load的执行时间不固定,在核数特别多的情况下会造成xtime_lock获取的时间过长。2)calc_load是
每5s一次的采样程序,本身并不能够精度特别高,对全局avenrun的读和写之间也不需要专门的锁保护,可以将全局load的
更新和读进行分离。
Dimitri Sivanich提出在他们的large SMP系统上,由于calc_load需要遍历所有online CPU,造成系统延迟较大。
基于上述原因Thomas Gleixnert提交了下述patch对该bug进行修复:

[Patch 1/2] sched, timers: move calc_load() to scheduler
[Patch 2/2] sched, timers: cleanup avenrun users

Thomas的两个patch,主要思想如上图所示。首先将全局load的计算分离到per-cpu上,各个cpu上计算load时不加xtime_lock
的锁,计算的load更新到全局calc_load_tasks中,所有cpu上load计算完后calc_load_tasks即为整体的load。在5s定
时器到达时执行calc_global_load,读取全局cacl_load_tasks,更新avenrun。由于只是简单的读取calc_load_tasks,
执行时间和cpu个数没有关系。

几个关键点:

不加xtime_lock的per cpu load计算

在不加xtime_lock的情况下,如何保证每次更新avenrun时候读取的calc_load_tasks为所有cpu已经更新之后的load?

Thomas的解决方案

Thomas的做法是将定时器放到sched_tick中,每个cpu都设置一个LOAD_FREQ定时器。
定时周期到达时执行当前处理器上load的计算。sched_tick在每个tick到达时执行
一次,tick到达是由硬件进行控制的,客观上不受系统运行状况的影响。

sched_tick的时机

将per-cpu load的计算放至sched_tick中执行,第一反应这不是又回到了时间处理中断之间,是否依旧
存在xtime_lock问题? 下面对sched_tick进行分析(以下分析基于linux-2.6.32-220.17.1.el5源码)

static void update_cpu_load_active(struct rq *this_rq) 
{ 
        update_cpu_load(this_rq); 

        calc_load_account_active(this_rq); 
}
 
void scheduler_tick(void) 
{ 
        int cpu = smp_processor_id(); 
        struct rq *rq = cpu_rq(cpu); 
...
        spin_lock(&rq->lock); 
...
        update_cpu_load_active(rq); 
...
        spin_unlock(&rq->lock); 

...
} 
 
void update_process_times(int user_tick) 
{ 
...
        scheduler_tick(); 
...
}
 
static void tick_periodic(int cpu) 
{ 
        if (tick_do_timer_cpu == cpu) { 
                write_seqlock(&xtime_lock); 

                /* Keep track of the next tick event */ 
                tick_next_period = ktime_add(tick_next_period, tick_period); 
           
                do_timer(1);  // calc_global_load在do_timer中被调用
                write_sequnlock(&xtime_lock); 
        } 
 
        update_process_times(user_mode(get_irq_regs())); 
...
}
 
void tick_handle_periodic(struct clock_event_device *dev) 
{ 
        int cpu = smp_processor_id(); 
...
        tick_periodic(cpu); 
...
}

交错的时间差

将per-cpu load的计算放到sched_tick中后,还存在一个问题就是何时执行per-cpu上的load计算,如何保证更新全
局avenrun时读取的全局load为所有cpu都计算之后的? 当前的方法是给所有cpu设定同样的步进时间LOAD_FREQ,
过了这个周期点当有tick到达则执行该cpu上load的计算,更新至全局的calc_load_tasks。calc_global_load
的执行点为LOAD_FREQ+10,即在所有cpu load计算执行完10 ticks之后,读取全局的calc_load_tasks更新avenrun。

32内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load_tasks
0HZ+10 0 0 0 0 0 0 0 0 0
5HZ 1 1 1 1 1 1 1 1 0
5HZ+1 0 1 1 1 0 0 0 0 0
+1 +1 +1 1+1+1=3
5HZ+11 0 1 1 1 0 0 0 0 3
calc_global_load <– 3

通过将calc_global_load和per-cpu load计算的时间进行交错,可以避免calc_global_load在各个cpu load计算之间执行,
导致load采样不准确问题。

32内核Load计数nohz问题

一个问题的解决,往往伴随着无数其他问题的诞生!Per-cpu load的计算能够很好的分离全局load的更新和读取,避免大型系统中cpu
核数过多导致的xtime_lock问题。但是也同时带来了很多其他需要解决的问题。这其中最主要的问题就是nohz问题。

为避免cpu空闲状态时大量无意义的时钟中断,引入了nohz技术。在这种技术下,cpu进入空闲状态之后会关闭该cpu对应的时钟中断,等
到下一个定时器到达,或者该cpu需要执行重新调度时再重新开启时钟中断。

cpu进入nohz状态后该cpu上的时钟tick停止,导致sched_tick并非每个tick都会执行一次。这使得将per-cpu的load计算放在
sched_tick中并不能保证每个LOAD_FREQ都执行一次。如果在执行per-cpu load计算时,当前cpu处于nohz状态,那么当
前cpu上的sched_tick就会错过,进而错过这次load的更新,最终全局的load计算不准确。
基于Thomas第一个patch的思想,可以在cpu调度idle时对nohz情况进行处理。采用的方式是在当前cpu进入idle前进行一次该cpu
上load的更新,这样即便进入了nohz状态,该cpu上的load也已经更新至最新状态,不会出现不更新的情况。如下图所示:

32内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load_tasks
0HZ+11 1 1 1 0 0 0 0 0 3
5HZ 0 0 0 0 3 2 1 3 0
-1 -1 -1 3-3=0
5HZ+1 0 1 1 1 1 1 1 1 1
+1 +1 +1 +1 +1 +1 +1 0+1+…+1=7
5HZ+11 0 1 1 1 1 1 1 1 7
calc_global_load <– 7

理论上,该方案很好的解决了nohz状态导致全局load计数可能不准确的问题,事实上这却是一个苦果的开始。大量线上应用反馈
最新内核的load计数存在问题,在16核机器cpu利用率平均为20%~30%的情况下,整体load却始终低于1。

解决方案

接到我们线上报告load计数偏低的问题之后,进行了研究。最初怀疑对全局load计数更新存在竞争。对16核的系统,如果都没有进入
nohz状态,那么这16个核都将在LOAD_FREQ周期到达的那个tick内执行per-cpu load的计算,并更新到全局的load中,这
之间如果存在竞争,整体计算的load就会出错。当前每个cpu对应rq都维护着该cpu上一次计算的load值,如果发现本次计算load
和上一次维护的load值之间差值为0,则不用更新全局load,否则将差值更新到全局load中。正是由于这个机制,全局load如果被
篡改,那么在各个cpu维护着自己load的情况下,全局load最终将可能出现负值。而负值通过各种观察,并没有在线上出现,最终竞
争条件被排除。

通过/proc/sched_debug对线上调度信息进行分析,发现每个时刻在cpu上运行的进程基本维持在2~3个,每个时刻运行有进程的cpu都
不一样。进一步分析,每个cpu上平均每秒出现sched_goidle的情况大概为1000次左右。因此得到线上每次进入idle的间隔为1ms/次。
结合1HZ=1s=1000ticks,可以得到1tick =1ms。所以可以得到线上应用基本每一个tick就会进入一次idle!!! 这个发现就好比
原来一直用肉眼看一滴水,看着那么完美那么纯净,突然间给你眼前架了一个放大镜,一下出现各种凌乱的杂碎物。 在原有的世界里,
10ticks是那么的短暂,一个进程都可能没有运行完成,如今发现10ticks内调度idle的次数就会有近10次。接着用例子对应用场景进行分析:

32内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load_tasks
0HZ+11 1 1 1 0 0 0 0 0 3
5HZ 0 0 0 1 1 1 0 0
-1 -1 -1 3-3=0
5HZ+1 1 0 0 0 0 0 1 1
+1 +1 +1 0+1+1+1=3
5HZ+3 0 1 1 1 0 0 0 0 3
-1 -1 -1 3-1-1-1=0
5HZ+5 0 0 0 0 1 1 1 0 0
5HZ+11 1 0 0 0 0 0 1 1 0
calc_global_load <– 0

(说明:可能你注意到了在5HZ+5到5HZ+11过程中也有CPU从非idle进入了idle,但是为什么没有-1,这里是由于每个cpu都保留
了一份该CPU上一次计算时的load,如果load没有变化则不进行计算,这几个cpu上一次计算load为0,并没有变化)

Orz!load为3的情况直接算成了0,难怪系统整体load会偏低。这里面的一个关键点是:对已经计算过load的cpu,我们对idle进
行了计算,却从未考虑过这给从idle进入非idle的情况带来的不公平性。这个是当前线上2.6.32系统存在的问题。在定位到问题
之后,跟进到upstream中发现Peter Z针对该load计数问题先后提交了三个patch,最新的一个patch是在4月份提交。这三个
patch如下:

[Patch] sched: Cure load average vs NO_HZ woes
[Patch] sched: Cure more NO_HZ load average woes
[Patch] sched: Fix nohz load accounting – again!

这是目前我们backport的patch,基本思想是将进入idle造成的load变化暂时记录起来,不是每次进入idle都导致全局load的更新。
这里面的难点是什么时候将idle更新至全局的load中?在最开始计算per-cpu load的时候需要将之前所有的idle都计算进来,
由于目前各个CPU执行load计算的先后顺序暂时没有定,所以将这个计算放在每个cpu里面都计算一遍是一种方法。接着用示例进行说明:

32内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load_tasks tasks_idle
0HZ+11 1 1 1 0 0 0 0 0 3 0
5HZ 0 0 0 1 1 1 0 0
-1 -1 -1 3 -3
5HZ+1 1 0 0 0 0 0 1 1 3
+1 +1 +1 3-3+1+1+1=3 0
5HZ+3 0 1 1 1 0 0 0 0 3
5HZ+3 -1 -1 -1 3 -1-1-1=-3
5HZ+5 0 0 0 0 1 1 1 0 3
5HZ+11 1 0 0 0 0 0 1 1 3
calc_global_load <– 3 -3

至此这三个patch能够很好的处理我们的之前碰到的进入idle的问题。
将上述三个patch整理完后,在淘客前端线上机器中进行测试,测试结果表明load得到了明显改善。

更细粒度的时间问题

将上述三个patch整理完后,似乎一切都完美了,idle进行了很好的处理,全局load的读写分离也很好实现。然而在业务线上的测试结果却出乎意料,虽然添加patch之后load计数较之前有明显改善,但是依旧偏低。下面是一个抓取的trace数据(粗体为pick_next_idle):

<...>-9195 [000] 11994.232382: calc_global_load: calc_load_task = 0
<...>-9198 [000] 11999.213365: calc_load_account_active: cpu 0 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 1
<...>-9199 [001] 11999.213379: calc_load_account_active: cpu 1 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 2
<...>-9194 [002] 11999.213394: calc_load_account_active: cpu 2 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 3 
<...>-9198 [000] 11999.213406: calc_load_account_active: cpu 0 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 2
<...>-9201 [003] 11999.213409: calc_load_account_active: cpu 3 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 3
<...>-9190 [004] 11999.213424: calc_load_account_active: cpu 4 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 4
<...>-9197 [005] 11999.213440: calc_load_account_active: cpu 5 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 5
<...>-9194 [002] 11999.213448: calc_load_account_active: cpu 2 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 4
<...>-9203 [006] 11999.213455: calc_load_account_active: cpu 6 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 5
<...>-9202 [007] 11999.213471: calc_load_account_active: cpu 7 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 6
<...>-9195 [008] 11999.213487: calc_load_account_active: cpu 8 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 7
<...>-9204 [009] 11999.213502: calc_load_account_active: cpu 9 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 8
<...>-9190 [004] 11999.213517: calc_load_account_active: cpu 4 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 7
<...>-9192 [010] 11999.213519: calc_load_account_active: cpu 10 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 8
<...>-9200 [011] 11999.213533: calc_load_account_active: cpu 11 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 9
<...>-9189 [012] 11999.213548: calc_load_account_active: cpu 12 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 10
<...>-9196 [013] 11999.213564: calc_load_account_active: cpu 13 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 11
<...>-9193 [014] 11999.213580: calc_load_account_active: cpu 14 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 12
<...>-9191 [015] 11999.213596: calc_load_account_active: cpu 15 nr_run 1 nr_uni 0 nr_act 1 delta 1 calc 13
<...>-9204 [009] 11999.213610: calc_load_account_active: cpu 9 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 12
<...>-9195 [008] 11999.213645: calc_load_account_active: cpu 8 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 11
<...>-9203 [006] 11999.213782: calc_load_account_active: cpu 6 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 10
<...>-9197 [005] 11999.213809: calc_load_account_active: cpu 5 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 9
<...>-9196 [013] 11999.213930: calc_load_account_active: cpu 13 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 8
<...>-9193 [014] 11999.213971: calc_load_account_active: cpu 14 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 7
<...>-9189 [012] 11999.214004: calc_load_account_active: cpu 12 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 6
<...>-9199 [001] 11999.214032: calc_load_account_active: cpu 1 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 5
<...>-9191 [015] 11999.214164: calc_load_account_active: cpu 15 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 4
<...>-9202 [007] 11999.214201: calc_load_account_active: cpu 7 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 3
<...>-9201 [003] 11999.214353: calc_load_account_active: cpu 3 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 2
<...>-9192 [010] 11999.214998: calc_load_account_active: cpu 10 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 1
<...>-9200 [011] 11999.215115: calc_load_account_active: cpu 11 nr_run 0 nr_uni 0 nr_act 0 delta -1 calc 0
<...>-9198 [000] 11999.223342: calc_global_load: calc_load_task = 0

虽然这个是未加三个patch之前的trace数据,但是我们依旧能够发现一些问题:原来的10tick对我们来说从一个微不足道的小时间片被提升为一个大时间片,相对此低了一个数量级的1 tick却一直未真正被我们所重视。trace数据中,cpu0、2、4在计算完自己的load之后,其他cpu计算完自己的load之前,进入了idle,由于默认情况下每个cpu都会去将idle计算入全局的load中,这部分进入idle造成的cpu load发生的变化会被计算到全局load中。依旧出现了之前10ticks的不公平问题。示例如下:

32内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load_tasks tasks_idle
0HZ+11 1 1 1 0 0 0 0 0 3 0
5HZ 0 0 0 1 1 1 0 0
-1 -1 -1 3 -3
5HZ+1.3 1 0 0 0 0 0 1 1
+1 3-3+1=1 0
5HZ+1.5 0 1 1 1 0 0 0 0 1 0
-1 +1 1+1-1=1 0
5HZ+1.7 0 0 0 0 1 1 1 0 0 0
-1 +1 1-1+1=3 0
5HZ+3 0 1 1 1 0 0 1 0
-1 1 -1
5HZ+5 0 0 0 0 1 1 1 0
5HZ+11 1 1 0 0 0 0 1 -1
calc_global_load <– 1 -1

线上业务平均每个任务运行时间为0.3ms,任务运行周期为0.5ms,因此每个周期idle执行时间为0.2ms。在1个tick内,cpu执行完自己load的计算之后,很大的概率会在其他cpu执行自己load计算之前进入idle,致使整体load计算对idle和非idle不公平,load计数不准确。 针对该问题,一个简单的方案是检测第一个开始执行load计算的CPU,只在该CPU上将之前所有进入idle计算的load更新至全局的load,之后的CPU不在将idle更新至全局的load中。这个方案中检测第一个开始执行load计算的CPU是难点。另外一个解决方案是将LOAD_FREQ周期点和全局load更新至avenren的LOAD_FREQ+10时间点作为分界点。对上一次LOAD_FREQ+10到本次周期点之间的idle load,可以在本次CPU执行load计算时更新至全局的load;对周期点之后到LOAD_FREQ+10时间点之间的idle load可以在全局load更新至avenrun之后更新至全局load。
Peter Z采用的是上述第二个解决,使用idx翻转的技术实现。通过LOAD_FREQ和LOAD_FREQ+10两个时间点,可以将idle导致的load分为两部分,一部分为LOAD_FREQ至LOAD_FREQ+10这部分,这部分load由于在各个cpu计算load之后到全局avenrun更新之间,不应该直接更新至全局load中;另一部分为LOAD_FREQ+10至下一个周期点LOAD_FREQ,这部分idle导致的load可以随时更新至全局的load中。实现中使用了一个含2个元素的数组,用于对这两部分load进行存储,但这两部分并不是分别存储在数组的不同元素中,而是每个LOAD_FREQ周期存储一个元素。如下图所示,在0~5周期中,这两部分idle都存储在数组下标为1的元素中。5~10周期内,这两个部分都存储在数组下标为0的元素中。在5~10周期中,各个cpu计算load时读取的idle为0~5周期存储的;在计算完avenrun之后,更新idle至全局load时读取的为5~10周期中前10个ticks的idle导致的load。这样在10~15周期中,各个cpu计算load时读取的idle即为更新avenrun之后产生的idle load。具体实现方案如下:

      0             5             10            15          --->HZ
        +10           +10           +10           +10       ---> ticks
      |-|-----------|-|-----------|-|-----------|-|
idx:0   1     1       0     0       1      1      0   
  w:0 1 1         1 0 0         0 1 1         1 0 0
  r:0 0 1         1 1 0         0 0 1         1 1 0
说明:1)0 5 10 15代表的为0HZ、5HZ、10HZ、15HZ,这个就是各个cpu执行load计算的周期点
     2)+10表示周期点之后10ticks(即为计算avenrun的时间点)
     3)idx表示当前的idx值(每次只取最后一位的值,因此变化范围为0~1)
     4)w后面3列值,第一列表示周期点之前idle计算值写入的数组idx;第二列表示周期点到+10之间idle导致的load变化写入的数
       组idx;第三列表示计算万avenrun之后到下一个周期点之间idle写入的数组idx;

用如下示例进行说明(假定0HZ+11之后idx为0):

32内核loadavg计算
cpu0 cpu1 cpu2 cpu3 cpu4 cpu5 cpu6 cpu7 calc_load_tasks idle[0] idle[1] idx
0HZ+11 1 1 1 0 0 0 0 0 3 0 0 0
5HZ 0 0 0 1 1 1 0 0
-1 -1 -1 3 -3 0 0
5HZ+1.3 1 0 0 0 0 0 1 1
+1 3-3+1=1 0 0 0
5HZ+1.5 0 1 1 1 0 0 0 0 1 0
-1 +1 1+1=2 0 -1 0
5HZ+1.7 0 0 0 0 1 1 1 0 0 0
-1 +1 2+1=3 0 -2 0
5HZ+3 0 1 1 1 0 0 1 0 0
5HZ+3 3 0 -2 0
5HZ+5 0 0 0 0 1 1 1 0 0
5HZ+11 1 1 0 0 0 0 1 1
calc_global_load <– 3 0 -2 0
3-2=1 0 0 1
5HZ+15 1 1 0 0 0 0 0 1
-1 1 0 -1 1

再次回归到公平性问题

经过对细粒度idle调度问题进行解决,在线上业务整体load得到了很好的改善。原来平均运行进程数在16的情况下,load一直徘徊在1左右,改善之后load回升到了15左右。
然而这个patch发布到社区,经过相关报告load计数有问题的社区人员进行测试之后,发现系统的load整体偏高,而且很多时候都是趋近于系统总运行进程数。为了验证这个patch的效果,升级了一台添加该patch的机器,进行观察,确实发现升级之后机器的load比原有18还高出1左右。
又是一次深度的思考,是否当前这个patch中存在BUG? 是否从第一个CPU到最后一个CPU之间的idle就应该直接计算在整体load中? 对于高频度调度idle的情况,这部分idle是不应该加入到全局load中,否则无论系统运行多少进程,最终load都会始终徘徊在0左右。因此这部分idle必须不能够加入到全局load中。通过trace数据进行分析,也证明了patch运行的行为符合预期,并不存在异常。
如果假设之前所有的patch都没有问题,是否存在其他情况会导致系统load偏高?导致load偏高,一个很可能的原因就是在该计算为idle时,计算为非idle情况。为此先后提出了负载均衡的假设、计算load时有进程wakeup到当前运行队列的假设,最终都被一一排除。
进一步观察trace数据,发现几乎每次都是在做完该CPU上load计算之后,该CPU立即就进入idle。16个CPU,每个CPU都是在非idle的时候执行load计算,执行完load计算之后又都是立即进入idle。而且这种情况是在每一次做load计算时都是如此,并非偶然。按照采样逻辑,由于采样时间点不受系统运行状况影响,对于频繁进出idle的情况,采样时idle和非idle都应该会出现。如今只有非idle情况,意味着采样时间点选取存在问题。
进一步分析,如果采样点处于idle内部,由于nohz导致进入idle之后并不会周期执行sched_tick,也就无法执行load计算,看起来似乎会导致idle load计算丢失。事实并不是,之前计算idle load就是为了避免进入nohz导致load计算丢失的问题,在进入idle调度前会将当前cpu上的load计算入idle load中,这样其他cpu执行load计算时会将这部分load一同计算入内。
但是基于上述逻辑,也可以得到一个结论:如果采样点在idle内部,默认应该是将进入idle时的load作为该cpu上采样load。事实是否如此?继续分析,该CPU如果从nohz重新进入调度,这个时候由于采样时间点还存在,而且间隔上一次采样已经超过一个LOAD_FREQ周期,会再次执行load计算。再次执行load计算会覆盖原有进入idle时计算的load,这直接的一个结果是,该CPU上的采样点从idle内部变成了非idle! 问题已经变得清晰,对采样点在idle内部的情况,实际计算load应该为进入idle时该cpu上的load,然而由于该cpu上采样时间点没有更新,导致退出nohz状态之后会再次执行load计算,最终将退出nohz状态之后的load作为采样的load。
问题已经清楚,解决方案也比较简单:在退出nohz状态时检测采样时间点在当前时间点之前,如果是,则意味着这次采样时间点在idle内部,这 个周期内不需要再次计算该CPU上的load。

使用limit_conn模块限制连接数

如何设置能限制某个IP某一时间段的访问次数是一个让人头疼的问题,特别面对恶意的ddos攻击的时候。其中CC攻击(Challenge Collapsar)是DDOS(分布式拒绝服务)的一种,也是一种常见的网站攻击方法,攻击者通过代理服务器或者肉鸡向向受害主机不停地发大量数据包,造成对方服务器资源耗尽,一直到宕机崩溃。

cc攻击一般就是使用有限的ip数对服务器频繁发送数据来达到攻击的目的,nginx可以通过HttpLimitReqModul和HttpLimitZoneModule配置来限制ip在同一时间段的访问次数来防cc攻击。

HttpLimitReqModul用来限制连单位时间内连接数的模块,使用limit_req_zone和limit_req指令配合使用来达到限制。一旦并发连接超过指定数量,就会返回503错误。

HttpLimitConnModul用来限制单个ip的并发连接数,使用limit_zone和limit_conn指令

这两个模块的区别前一个是对一段时间内的连接数限制,后者是对同一时刻的连接数限制

HTTP Header中Accept-Encoding

HTTP Header中Accept-Encoding 是浏览器发给服务器,声明浏览器支持的编码类型的,Python的HTTPConnection默认是identitiy类型.
常见的有

Accept-Encoding: compress, gzip            //支持compress 和gzip类型
Accept-Encoding:                    //默认是identity
Accept-Encoding: *                    //支持所有类型
Accept-Encoding: compress;q=0.5, gzip;q=1.0      //按顺序支持 gzip , compress

Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0       // 按顺序支持 gzip , identity

服务器返回的对应的类型编码header是 content-encoding

服务器处理accept-encoding的规则如下所示

1. 如果服务器可以返回定义在Accept-Encoding 中的任何一种Encoding类型, 那么处理成功(除非q的值等于0, 等于0代表不可接受)

2. * 代表任意一种Encoding类型 (除了在Accept-Encoding中显示定义的类型)

3.如果有多个Encoding同时匹配, 按照q值顺序排列

4. identity总是可被接受的encoding类型(除非显示的标记这个类型q=0) ,   如果Accept-Encoding的值是空  那么只有identity是会被接受的类型

如果Accept-Encoding中的所有类型服务器都没发返回, 那么应该返回406错误给客户端

如果request中没有Accept-Encoding  那么服务器会假设所有的Encoding都是可以被接受的,

如果Accept-Encoding中有identity  那么应该优先返回identity (除非有q值的定义,或者你认为另外一种类型是更有意义的)

注意:

如果服务器不支持identity 并且浏览器没有发送Accept-Encoding,那么服务器应该倾向于使用HTTP1.0中的 “gzip” and “compress” ,  服务器可能按照客户端类型 发送更适合的encoding类型

大部分HTTP1.0的客户端无法处理q值.并且有的服务器无论是否传递了Accept-Encoding,都会返回压缩数据,所以还是要对response的Content-Encoding进行监测。

TCP三路握手流程简介

TCP(Transmission Control Protocol) 传输控制协议

TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:

位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)

Sequence number(顺序号码) Acknowledge number(确认号码)

第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;

第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包

第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

完成三次握手,主机A与主机B开始传送数据。
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据.

实例:

IP 192.168.1.116.3337 > 192.168.1.123.7788: S 3626544836:3626544836
IP 192.168.1.123.7788 > 192.168.1.116.3337: S 1739326486:1739326486 ack 3626544837
IP 192.168.1.116.3337 > 192.168.1.123.7788: ack 1739326487,ack 1

第一次握手:192.168.1.116发送位码syn=1,随机产生seq number=3626544836的数据包到192.168.1.123,192.168.1.123由SYN=1知道192.168.1.116要求建立联机;

第二次握手:192.168.1.123收到请求后要确认联机信息,向192.168.1.116发送ack number=3626544837,syn=1,ack=1,随机产生seq=1739326486的包;

第三次握手:192.168.1.116收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,192.168.1.116会再发送ack number=1739326487,ack=1,192.168.1.123收到后确认seq=seq+1,ack=1则连接建立成功。

 

图解:
一个三次握手的过程(图1,图2)

(图1)

(图2)
第一次握手的标志位(图3)
我们可以看到标志位里面只有个同步位,也就是在做请求(SYN)
3
(图3)

第二次握手的标志位(图4)
我们可以看到标志位里面有个确认位和同步位,也就是在做应答(SYN + ACK)
4
(图4)

第三次握手的标志位(图5)
我们可以看到标志位里面只有个确认位,也就是再做再次确认(ACK)
5

(图5)

一个完整的三次握手也就是 请求—应答—再次确认

ARP欺骗网页劫持攻击分析

前两天问了个大牛master关于arp劫持方面的东西,大致了解了一些,然后在大牛的帮助下完成了这篇文章,希望对一些安全爱好者有一些帮助,大牛可以直接飞过…..

一、攻击概要

通过 ARP欺骗进行网页劫持的攻击有时会使 Web服务访问变得很慢,这类攻击利用 ARP欺骗手段,截取并篡改网络上访问网页的数据包,添加包含有木马程序的网页的链接。比如,在网页源码的头部插入下面这行代码:

<iframe src=’包含有木马程序网页的地址’ > </iframe >

而当我们检查被访问的服务器上面的网页原始代码时,却并没有上面这行代码。这种情况很可能就是 ARP欺骗网页劫持攻击引起的。根据当前 ARP欺骗劫持网页的位置不同,已知的攻击可以分为两种:

劫持并篡改局域网内客户端访问来的网页;

劫持并篡改局域网内 web服务器对外提供的网页。

 

二、原理

这类攻击都是通过 ARP欺骗在用户端和 web服务端之间建立起代理,并对特定的 HTTP应答包进行篡改从而达到传播网页木马的目的。它们进行 ARP欺骗的原理与传统的 ARP欺骗的原理基本相同,但是传统的 ARP欺骗一般仅做网络监听,不会去修改数据包中的内容。而这类攻击除了监听以外,还会对链路上的 HTTP应答包进行篡改。以下是这种攻击与传统的 ARP欺骗攻击的一些区别:

1、传统欺骗方式

监听用户所有的网络连接数据包,以此获取有用的信息;监听链路是双向的,内到外,外到内。既向网关发送arp包,也向用户发送arp包

用户这时会感觉到内部网络会非常的慢,时常会掉线。

 

2、客户端网页劫持

监听并篡改外部对用户的HTTP 应答包,以达到在网页代码中添加木马链接传播木马病毒为目的,但监听的是单向链路,仅监听外部发往用户的数据包,单向欺骗,仅向网关发送ARP欺骗包,不会向用户端发送ARP 欺骗包,用户访问网络的速度变慢,访问外部网站时特定或者全部的网页代码中都存在木马链接,并且这些木马链接的代码都插入在网页代码的头部。

 

ARP欺骗网页劫持攻击分析 - break - break的博客

3、web服务端网页劫持

监听并篡改web服务器对外的所有http应答包,以达到在网页中添加木马链接传播病毒的目的,它仅监听服务器发向外部的数据包,所以是单向的,仅想服务器端发送arp欺骗包,不会向网关发送arp包,中毒后web服务器的访问速度变慢,所有用户访问web服务器上的页面均有木马链接存在,而服务器上的网页代码是正常的,所以这种危害性极高。

ARP欺骗网页劫持攻击分析 - break - break的博客

三、检测和预防

对客户端网页劫持检测:在局域网内听包、抓包无用的,必须登录网关查询网关的arp缓存表,如果发现一个mac对应多个ip地址,那说明这个mac就可能是arp欺骗网页劫持攻击的主机,这类攻击由于欺骗只发生在网关上,因此即使在用户终端上安装了arp防火墙也不讷讷感有效地抵挡这类攻击,我们可以通过在网关行绑定用户端的mac地址来防范这类攻击,也要学会和善意识的保护好自己的电脑,及时更新杀毒软件,从而有效的抵挡网页木马程序。

对服务器端万股额劫持检测:在受影响的服务器上敲击 arp –a 的命令,确认列表中绑定的网关MAC 是不是真实网关MAC,如果不是这个MAC就是攻击主机的MAC。在局域网内部使用sniffer 软件听包,从而判断攻击主机的mac,这类攻击由于ARP 欺骗只针对服务器,因此服务器上绑定网关的MAC 就行,windows 底下的命令如下:Arp –s 网关地址网关真实MAC或者在服务器上安装ARP 防火墙。