分类 网络 下的文章

SPDY真的快吗?

SPDY真的快吗?

Google SPDY协议(现已加入HTTP2.0草案)宣称能够提升浏览器速度一倍,HTTPS同SPDY协议在前端加载速度上的对比如下(参考极客公园):

8bb3b4b0e21d86701f1ebd880768ec84.gif

针对该优点,小伙伴进行了对比测试,发现http协议SPDY协议在前端响应速度上没太大出入。

Protocol 1 2 3 4 5 Avg
http 35.91 35.80 35.86 36.95 37.80 36.46
spdy 36.26 36.21 36.24 36.27 36.30 36.26

注:所有统计时间均在chrome下禁用缓存并开启调试模式进行,时间单位为秒。

看到这个测试,心都凉了半截,为什么spdy的加载时间和http的加载时间相差无几,完全没有Google宣称的那般美好,甚至大部分的耗时比http还长,感觉Google忽悠了整个世界,再也无法相信美帝国主义的公司。

面对他的这个测试报告,我只能说一定是开门的方式不对。

先来说结论:SPDY无法改善用户的上网带宽,用这么大的图片来测试,时间基本都花在传输上了,自然难以看出区别。

传统浏览器慢在哪里

要了解SPDY到底快在哪里,先得看HTTP协议性能都消耗在了哪里,干脆我们看看传统的浏览器都需要把时间花到哪里:

  1. DNS查询时间(把域名转成IP地址,一般从几毫秒到几秒不等,移动网络中此过程有时需要十几秒)
  2. TCP握手时间(取决于你和服务器之间的Ping有多高,TCP的三次握手决定了每建立一个TCP连接都需要三倍单程时间)
  3. TCP慢启动窗口时间(刚建立的TCP连接不会以最大速度发送数据,数据传输速度会有一个慢慢增长的过程)
  4. HTTP请求发送时间(一般的GET请求头有几百字节到几千字节)
  5. 等待服务器处理时间(读取磁盘,查询数据库,运行服务端程序逻辑等等,一般几毫秒到几秒不等)
  6. HTTP回应接收时间(下载服务器返回的数据,一般文本内容只有几KB到几十KB,图片等内容可能有几MB)
  7. 浏览器解析处理时间(处理DOM,排版,屏幕绘图等等)

那么Google又针对以上几种时间消耗又分别做了什么优化呢?

  1. Chrome浏览器会Cache下来DNS查询结果,Google甚至鼓励你用Google的DNS服务器。
  2. 网页总有许多资源需要加载,包括CSS、JS、图片等,为了并行加载提高效率,现代浏览器会为每个域名建立6个TCP连接,每个TCP连接都需要经历握手过程。
  3. 每个TCP连接都会经历慢启动的过程,Google也没办法,因为TCP的慢启动策略是操作系统管理的。不过现代浏览器都实现了HTTP/1.1的Keep-Alive支持,能用一个连接发送多个请求,虽然由于复杂度的原因Pipeline基本都没有实现,但也能有效避免像HTTP/1.0那样每传输一项资源都重新建立一次连接。
  4. 第4、第6点Chrome是无法优化的,因为HTTP协议就那么定的。
  5. 第5点掌握在网站开发者手中,Google虽然无力改变别的网站,但他可以把自家的网站优化好,比如YouTube、Picasa、Gmail等等Google应用都有做诸多优化,响应速度也是业内领先的。
  6. 浏览器解析处理方案,Chrome下了不少工夫,先是借力Webkit的轻巧高效,又有引入强大的带JIT的V8 JavaScript解释器,后来更是搞出了Blink渲染引擎,可谓Webkit的再优化版。

SPDY到底快在哪里

当Google发现自己把能做的都做了之后,只能向他们掌控不了的领域动刀,前面时间消耗的第2、3、4、6点都值得从协议上优化,于是Google抛出了SPDY协议。

  1. SPDY支持多路复用。并行化的好处显而易见,Google Chrome为每个域名开6个TCP就会让服务器需要维护的TCP连接数量上涨到三倍,而且每个连接都要经历三次握手和慢启动过程。SPDY干脆就只用一个TCP连接,这样三次握手和慢启动过程只走一遍,因为支持多路复用,所以SPDY仍可以享受并行化加载的好处,Pipeline与否也显得无关紧要。
  2. SPDY支持优先级设定,对于渲染页面而言,关键的CSS和图标资源可以优先加载,非关键的CSS和JS以及图片可以低优先级加载,这样可以帮助浏览器提高渲染效率。
  3. SPDY支持HTTP头压缩,并强制启用正文压缩,能有效减少请求和回应的数据量,优化HTTP的数据传输量。
  4. 此外,SPDY还有Server Push这种需要服务器开发人员支持的大杀器帮助提升速度。一般浏览器都是先下载HTML,然后看到里面有引用某CSS再去请求这个CSS,解析CSS看到里面需要一张图片再去请求这张图片。SPDY协议允许网页开发者直接把这些次生资源主动Push到浏览器中,这样当浏览器解析HTML发现需要一个CSS的时候,会发现这个CSS已经下载好了,不用再发请求下载了,解析CSS发现需要某张图片的时候,发现它也已经下载好了。
  5. 对于第二次来访者,考虑到次生资源可能已经有浏览器缓存,SPDY允许服务器发送Server Hint,告诉浏览器本网页需要哪些资源,没Cache的直接下载,不用等解析到才来请求。这样也能有效提升速度。

SPDY慢在哪里

俗话说有得必有失,SPDY也是有额外开销的,这些开销会减慢它的速度。那么SPDY的开销在哪里呢?和HTTP协议相比,SPDY的额外开销只有一个,那就是它全程使用SSL/TLS,存在证书交换时间。和HTTPS差不多,每次浏览器连上SPDY服务器之后,先会收到一个长达几KB的SSL安全证书,然后浏览器还要计算证书签名是否合法,并搜索一遍证书吊销记录中有没有这个证书的指纹信息,然后才开始生成密钥传输数据。即便是现代计算机,这个过程也会花掉几十到几百毫秒的时间。

正确的测试方法

由于Server Push和Server Hint这种大杀器都需要服务端跟踪记录每个网页会引用到的资源,并确定用户浏览器是否首次来访,不容易准备测试用例也不容易在自己的服务中实施,暂时先不考虑。要对照测试SPDY和HTTP的性能差异,至少应该用接近真实网页的例子,或者用比较能突出并行化优势的例子。像前面小伙伴测试的例子,主要性能开销都在网络传输二进制图片文件之上,SPDY的性能优势被全部淹没在了统计误差之中。

极客公园文章中用GIF演示的例子是一个偏向SPDY优势的例子,大量的小图标文件非常适合高度并行化的传输,而且几百字节的HTTP Header和大量不到1KB的图片文件相比,额外的负载显得如此严重,SPDY的头压缩功能可以很有效地降低传输数据量,HTTP的并行度也远无法和SPDY相提并论,非常容易看到SPDY在性能提升上的效果。考虑到实际的确会有一些网页会出现这样的情况(比如国家/地区选择页),这个例子也不算太背离实用场景。当然,真正的前端高手遇到这种情况会选择使用CSS Image Sprite技术来减少HTTP请求量,提升加载速度。不过如果HTTP本身能支持高度并行化地加载,CSS Image Sprite技术也不过是奇技淫巧罢了。

我制作了类似极客公园的例子,大家可以自己测试比较HTTP版本SPDY/HTTP2版本的加载速度差异。(我的测试服务器同时支持SPDY和HTTP2,具体选用哪个协议取决于浏览器)

下面是我测试出来的数据

Protocol 1 2 3 4 5 Avg
http 8.52 7.65 8.25 7.48 8.00 7.98
spdy 3.06 2.73 2.69 2.87 2.74 2.82

我取用南方周末的首页制作了另一个测试用例,这是典型的新闻类网站首页的情况,大家可以自行比较HTTP版本SPDY/HTTP2版本的性能。下面是我的数据:

Protocol 1 2 3 4 5 Avg
http 2.94 2.09 2.04 3.21 2.71 2.60
spdy 3.15 2.79 2.66 1.99 2.35 2.59

这份数据告诉我们,像南方周末这样新闻类网站,大图片一堆一堆的,SPDY并不能提升多少访问速度,至少速度的提升是无法感知的。用Page Speed Insight测试之后可以发现,南方周末网站首页的新闻图片大都可以在不损失画质的前提下压缩超过80%的文件大小。

总结

SPDY协议无法改善用户的上网带宽,以数据传输为时间开销的网站测试无法突显出SPDY协议的优势,不过即便是这种情况,SPDY的性能表现也不会比传统的HTTP慢。SPDY协议是Google在自家网站优化到极致之后,把手伸到HTTP协议上而祭出的神器。借它优化自家网站的访问速度之前,还是先把其它该优化的点都充分做好吧。

为nginx配置安全的SSL证书

最近Google放了一个大招,在所有基于Chromium的浏览器中把SHA-1签名认证的https证书全部加入了连接可能不具备私密性警告。于是绿色的锁头变成灰色,还带一个黄色的大三角。如下图:

1125923241.jpg

想了解关于这个警告的含义和来龙去脉,请移驾Fredrik Luo的博客,上面有又详细又通俗的解释。现在是时候该升级一下服务器的TLS安全设置了。

对于使用SHA-1签名的证书的网站,唯一的方法是生成更高强度的哈希并重新签名。可以用下面的命令生成新的CSR,并提交给你的CA进行reissue操作:

openssl req -new -key theOriginPrivate.key -out example.com.csr -sha256

好吧,尽管安全要求只是至少升级到SHA-2,但干脆直接上SHA-256或者SHA-384算了,反正碰撞的可能性更低,而且现代计算机做个正向计算简直没难度。致使用CNNIC做根证书签名的用户:你们还是赶快换CA吧,CNNIC整个证书链都是SHA-1的,光升级你的CSR没用。

一般的域名验证证书只需通过电子邮件就能验证完成,和申请新证书基本一样。建议取得新的签名证书文件之后不要着急安装,等几个小时再安装。因为新的证书有效期是从重新签名当时起算的,可能有的客户的设备时钟不准,马上换装可能导致他们的设备报告证书不合法。一般新的证书签发后,旧的证书还能继续用三天,三天后负责任的CA会把你的旧证书写到已吊销列表中。

换装了新证书后,顺便找了个叫ssllabs的网站测试https的安全情况,用他们主页上的Test Your Server功能可以测试你的网站https的安全性和兼容性。测试结果里Signature algorithm一栏透露了证书的签名算法,我的已经是SHA256了。

650315878.jpg

nginx使用OpenSSL作为底层加密库,所以影响安全性的Cipher Suites设定至关重要,如果设置得安全性极高,可能失去对早期版本浏览器的兼容性而流失用户;反之如果设置的安全性过低,又会陷用户于易受攻击的状态并被新版浏览器嫌弃。在cipherli.st上提供了包括nginx在内的常用Web服务器安装设置,可惜这些设置对早期版本的浏览器兼容得不太好,几经周拆和尝试,我终于敲定使用以下设置:

ssl_certificate /etc/ssl/public.crt;
ssl_certificate_key /etc/ssl/private.key;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_ciphers EECDH+AESGCM:AES256+EECDH:EDH+AESGCM:AES256+EDH:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;

其中第三行那个dhparam的设置,是因为nginx会自动使用OpenSSL默认的1024位的DH Pool,这在现在已被认为是不安全的,所以需要自己生成一个新的2048位的DH Pool。

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

本来cipherli.st推荐的是4096位的DH Pool,这当然能更安全,但无奈Java 7并不支持超过2048位的DH Pool,所以只好退而求其次,所幸2048位的DH Pool也算阶段性安全。

上面的配置并不支持Windows XP上的IE6,考虑到IE6默认设置只能用已经被证实有重大安全问题的SSL3.0,而坚持不升级的用户多半也不知道https为何物,更不知道怎么把TLS1.0打开,所以还是放弃这些用户吧。

让nginx把http来访者重定向到https

连百度都已经开始全面支持https了——https加密网络上传输的数据,它在保护用户隐私和防止http注入攻击方面有着不错的效果。以前提供https的站长为了保证SEO,不得不对百度的搜索引擎往开一面,提供http的版本。现在已经可以放心地在全站用https提供服务了。

全站https虽然好,但也需要用户主动在地址栏输入https的协议名,然而用户输入网址,往往直接从域名开始输入,比如直接打www.baidu.com,这样浏览器会默认用http协议访问,这就需要我们把用户从http自动导向https站点了。不过这倒是不难,只需往nginx的server配置中加入一个跳转的配置,就能完成:

server {
    listen 80;
    listen 443 ssl;
    server_name www.example.com example.com;

    add_header Strict-Transport-Security "max-age=31536000";
    if ( $scheme !~ "https" ) {
        return 301 https://$host$request_uri;
    }
    #......
}

新加入的Strict-Transport-Security标头是遵循HSTS标准,浏览器看到这个header后就会记住当前网站支持安全加密访问。在max-age的时间内,浏览器会自动请求https的版本,而不是http的版本。后面的if语句意思是,如果用户连接协议不是https,就301永久重定向到https的版本。

防御CC攻击

什么是CC攻击

CC攻击,英文Challenge Collapsar,译为“挑战黑洞”,是一种以服务系统性能薄弱环节为目标的分布式拒绝服务(DDoS)攻击。传统的DDoS攻击一般利用受害者服务器底层网络技术的缺陷发动攻击,攻击者流量消耗比较小。随着技术的进步,已经基本实现有效防御。CC攻击则和传统的DDoS攻击方式不同,它针对的是业务系统应用层的薄弱环节,攻击者需要消耗较多的网络带宽才能发动,但由于根据业务特点发动攻击,目前没有通用的有效的防御手段,所以攻击成功率比较高。

服务器业务中的性能薄弱环节,未必是软件实施的缺陷。比如常见的CMS网站,首页在Cache等技术下,承载上万RPS(每秒请求)不会有任何问题,但是CMS的搜索功能,也许只能承载几百RPS。考虑到平时网站用户量不大,实际首页压力只有几百RPS,而搜索只有几RPS的实际情况,网站完全能够正常运行。此时如果黑客断定搜索是性能薄弱环节,针对搜索功能发起CC攻击,通过遍布全国的几百个代理服务器,向网站发送搜索请求,很快网站就会不堪重负,被迫下线了。

网站中需要处理复杂事务的接口,一般都有可能会成为CC攻击的目标,包括搜索、聊天、短信发送、验证码生成等等。

侦测CC攻击

防御CC攻击较之防御传统的DDoS攻击,一大难点是不易侦测攻击的存在。从单个请求来看,CC攻击者和普通用户几乎完全相同,一般只能从宏观层面,比如一段时间的网站流量统计中才能发现自己被攻击了。找到方法甄别来访者是普通用户还是攻击者,是解决CC攻击的关键。

检查代理服务器

一般用户都是直接访问互联网,而大多数CC攻击采用代理服务器发起,检测来访用户是否使用代理服务器是一个判别的方法。公共HTTP代理一般会在请求中加入X-Forwarded-ForVia请求标头,可以以此为据侦测用户是否使用了代理服务器。

不过近年出现了不少高匿名的代理服务器,完全不透传任何信息,这个方法就无能为力了。

检查IP的并发连接数

根据HTTP协议的规定,用户代理对每个域应该允许最多2个并发连接,这一标准在06年以来有所提高,每个域允许6个并发连接。正常情况下,每个连接到服务器的IP处于ESTABLISHED状态的连接数应该不超过n*6个,n是该服务器同时对外服务的域名数量。如果来自同一个IP存在大量的并发连接,则有可能是攻击者。我们可以使用netstat命令实时地查看TCP连接的情况,配合脚本可以统计出当前各个客户端IP的并发连接数。

当然这个方法并不绝对,有的网吧几十台机器共用一个IP作为出口,那么这个IP的并发连接数量就会大得多。还有一些小的ISP可能只有少量的公网IP,它们在建设小区宽带的时候甚至会让一幢楼的用户共用一个公网IP。

高频请求检测

发动CC攻击一般都用脚本让机器自动发起请求,如果发现某个IP以非常高的频率发起HTTP请求,这个IP就有可能是攻击者。收集和处理高频请求的数据,需要占用一些服务器的CPU和内存资源。判定是否高频,一般以正常用户的页面平均停留时间乘以一个系数来计算需要根据实际情况不断调整。

这一方法和前面的并发连接数法有着相同的问题,如果用户存在共用出口公网IP的情况,就可能误判。另一个问题是,如果攻击者采用游击战术,每个代理只发起一波请求,然后弃之不用,这一方法就不会有实际效果,因为发现攻击者之后,攻击者已经换地方了。

重复IP检测

对于低留存率的网站,可以计算出连续若干天的来访者IP交集,去掉搜索引擎的和白名单上的IP之后,可以认为这些IP是攻击者。

这一方法的局限性在于需要攻击持续一段时间,并且攻击者只使用少数固定的代理服务器。如果攻击者打游击,这一方法也不会有效。另外,高留存率的网站可能不适用此方法,因为本来用户就会来了又来。

浏览器特性检测

有的CC攻击发起者只是为了对服务器的复杂逻辑发起请求,让服务器负担加大。有的攻击者会放弃使用全功能的浏览器,而改用一般的Python脚本发起。如果攻击者使用的不是完整的浏览器,我们就可以下发JS脚本运行,并用Feature Hack的方法确认用户的浏览器和它在UA中声称的自己的身份是否一致,进而推断是否为攻击者。

反向扫描来访IP的端口

一般的家用宽带用户不会开启大量非常用端口,反向扫描来访者IP开启的端口情况,如果发现对方在Listen许多非常用端口,则说明对方很可能是一台代理服务器或者被黑客操控的肉机。

反向扫描的方法耗时比较长,对一波流的攻击者效果不显著。

业务保护

如果我们无法有效辨别一个来访请求是CC攻击还是普通用户,也可以从业务角度入手,保护复杂业务正常进行。CC攻击是由机器自动完成,如果被攻击的功能能够识别人类用户,虽然无法降低带宽成本,但是可以有效减少业务损失。

加验证码

在关键功能上加验证码是一种直接的方法,可以参考12306的登录功能,使用验证码保护登录接口不会被机器自动提交攻击。

对抗非浏览器

如果攻击者使用的客户端不是浏览器,无法运行网页中的JavaScript程序,可以在页面中加入JavaScript验证程序,让一般的网页抓取工具失效。Wordpress的反垃圾留言就有类似的实现方法,它使用随机生成的JS和JS注释拼装出一个Token和留言一起提交,在服务器端检查Token是否合法,因为Token是混在大量JS脚本中的,一般的正则工具很难把Token分析出来,从而实现了无需输入验证码的简单人机区分。

鼠标行为探测

如果攻击者使用的是全功能浏览器,那么前面的方法不会有效,但依然有办法侦测对方是人类还是机器。鼠标移动轨迹就是一个方法。人们访问网页的时候,鼠标一般不会以固定的方式移动。对于那些不希望验证码降低用户体验的网页,可以用鼠标移动监测的方式判断是否人类用户。这一方法可以和滚动条状态等方法一起使用,对于平板电脑用户,还可以加入触屏行为监测。

当然,如果被攻击者发现了,攻击者也能写出模拟鼠标随机移动的程序,此时这一方法的效果就会打折扣了。

延迟业务自动开启

我们被攻击的地方是浏览器自动弹出的客服聊天窗口,考虑到自动机攻击时页面开启时间比较短,而正常用户页面开启时间比较长,可以延迟自动聊天窗口的弹出,绕过攻击。

按地区过滤非主流用户

这个方法是结合具体业务而设计的。由于被攻击的业务集中在临近的几个省,可以利用IP地理位置库设置规则,只放行主要业务地区的线上来访请求,其它地区则转向线下电话咨询。

为什么Wireshark无法解开使用Diffie-Hellman加密的SSL数据

前段时间升级了服务器的OpenSSL库,今天突然发现使用Wireshark已经无法解密https内容。查了一下资料才知道这是因为新版OpenSSL支持了Diffie-Hellman系列加密算法导致的。我的抓到的数据显示,密钥交换的过程使用的是一种叫ECDH的密钥交换算法。

以前的SSL通信,都是在客户端验证完服务器的证书值得信赖之后,产生随机密钥并直接用服务器的公钥加密发送出去,所以在向Wireshark提供了服务器的私钥之后,Wireshark能用私钥解开网络上传输的客户端生成的密钥,并用它解后续的所有SSL数据。然而用DH算法交换的密钥根本就不会在网络上传输,所以Wireshark自然无法解密了。

下面是从维基百科查到的一个简单版本的DH密钥交换过程:

  1. 一方选择质数p=23和基数g=5发送给另一方,这两个数可以公开明文传输;
  2. 甲方生成私钥a=6,并计算公钥A = ga mod p = 56 mod 23 = 8;
  3. 乙方生成私钥b=15,并计算公钥B = gb mod p = 515 mod 23 = 19;
  4. 甲乙双方互换公钥;
  5. 甲方计算s = Ba mod p = 196 mod 23 = 2;
  6. 乙方计算s = Ab mod p = 815 mod 23 = 2;
  7. 最后双方的共同计算结果2就是接下来进行对称加密传输的密钥。

Wireshark作为中间人,即使以服务器的SSL私钥解密,取得了p、g、A、B,在不知道a、b任一数值的情况下,也难以计算出s是多少。现实情况中,双方选用的p、g、a、b都会非常大,以确保难以被攻破。

要想在Wireshark上进行debug,只能要么在服务器禁用DH系的所有加密算法,要么干脆直接先用明文协议开发,完成后再接入SSL。