quic中的拥塞算法bbr

BBR

BBR = Bottleneck Bandwidth and Round-trip time

拥塞控制算法

tcp默认使用cubic算法做拥塞控制;
谷歌的quic开发了新的拥塞控制算法,吞吐量更大,更能利用带宽开发了新的拥塞控制算法bbr,bbr_v2;

CUBIC vs BBR

cubic vs bbr:

可以看到tcp默认的cubic拥塞控制算法频繁上下调整滑动窗口大小,锯齿状;
而bbr倾向于平稳发送,在实际带宽比较平稳的场景下,吞吐量更大。(图中折线下方的面积更大)

原来tcp为什么没有解决这个问题:
(1)tcp在linux内核里,升级太困难了。
(2)tcp的一些约束导致rtt算不准。比如ack delay、重传包的seq number不变。

cubic: 基于丢包;锯齿形吞吐;事件驱动; tcp的重传包seqId不变,rtt算不准;
BBR: 基于延迟; 有平滑区间;根据rtt建立对带宽(窗口大小)的模型,再加上定时器;
quic的重传包,seqId增加,rtt算得准。区分了具体的重传类型。
需要注意:tcp的ack delay时间影响rtt计算;(默认40ms)

BBR的思想:
当rtt开始增长的时候,就达到了最大带宽。

cubic:把缓存塞满一直到丢包;
对丢包率的容忍非常低,即使只有极少的丢包,吞吐量也会急剧下降。

缓冲膨胀
指的网络设备或者系统不必要地设计了过大的缓冲区。
当网络链路拥塞时,就会发生缓冲膨胀,从而导致数据包在这些超大缓冲区中长时间排队。
在先进先出队列系统中,过大的缓冲区会导致更长的队列和更高的延迟,并且不会提高网络吞吐量。
由于BBR并不会试图填满缓冲区,所以在避免缓冲区膨胀方面往往会有更好的表现。

弱网环境,引入1.5%的丢包:
cubic:吞吐量下降99.7%
bbr: 吞吐量下降45%

BBR的缺点

1。wifi环境网速变慢;
2。网络公平性下降: 挤占cubic算法带宽;
3。重传率会更高;
4。bbr对于rrt的测量是包级别的,可能容易受波动影响,可以考虑统计学上进行优化;

bbr的平稳发送时期本质上假设网络环境有一段时间是平稳的,因此比cubic抖动少,大部分情况下实际情况确实如此。

参考资料

https://aws.amazon.com/cn/blogs/china/talking-about-network-optimization-from-the-flow-control-algorithm/
bbr思想:https://blog.csdn.net/dog250/article/details/52962727

http3_quic优缺点及原因

WHAT: HTTP3/QUIC是什么?

QUIC: Quick UDP Internet Connections
谷歌主导的网络协议,用udp代替tcp来作为http的传输协议。
HTTP3就是HTTP over QUIC

如上图所示是http2和http over quic的对比。
可以看到图中的分层不是完全对齐的,这是因为以前的分层方式不合理,影响了性能,因此quic重新划分了更合理的分层结构。
原来的tcp(传输层)和tls(会话层)要分别建立连接,增加了不必要的rtt。
quic在tls1.3的基础上,统一建立连接,然后再基于udp传输。
QUIC层的分层:
UDP层: 只管传输,不管连接;
Connection层: 复杂建连相关、拥塞控制(bbr_v2)、安全(tls1.3);通过cid来确认唯一连接;
Stream层: 负责多路复用;基于Connection层,通过StreamID进行唯一流确认,stream对stream frame进行传输管理;

使用TCP的协议: SPDY/HTTP2
使用UDP的协议: quic/HTTP3

WHY: QUIC的优点(为啥要使用QUIC)

  • 连接迁移优化
  • 队头阻塞优化
  • 拥塞控制优化
  • 握手优化

quic的优点其实也对应着解决了以前http协议栈中的几个缺点。

连接迁移功能

以前tcp的缺点

移动客户端ip变化以后,连接断开,需要重新握手、创建新的连接。
我们日常生活中坐车、旅行等场景下,用手机上网,经过不同的基站,移动网络切换是比较常见的场景,
因此可能会经历频繁的连接重建,体验较差。

为什么tcp有这个缺点:
tcp用通信双方的ip+port(4元组)来标示一个连接,因此客户端ip变了的话,连接就变了。
tcp很老了,最早的时候手机还不普及,固定电脑的ip基本上不会变。

http over quic则可以在客户端ip变化时,依然保持连接不断,减少了rtt,提高了体验;

quic如何解决这个问题的

上面有说的tcp这个缺点的原因是标示连接唯一性的方式,所以很自然的思路是用别的方式来标示连接唯一性。
quic的方法是用connectionId(cid,64位)来唯一确定一个连接,客户端ip变化以后,cid不变,因此连接可以不断开。

实现

对于quic server:
如果根据客户端ip路由,连接迁移会失败;得根据cid路由;
一般得路由到同主机、同进程,才能保证连接迁移成功。
或者把连接相关的有状态信息存储在分布式缓存中(比如把session ticket存储在redis中)。

quic协议考虑到cid对于路由的重要性,因此cid是明文存储的。

解决了队头阻塞问题(多路复用功能增强)

以前tcp的缺点

tcp队头阻塞的原因:
窗口更新机制: 滑动窗口的更新依赖于最左边的包的ack,如果队头的包一直没有ack,窗口不往后滑;
拥塞控制算法: 丢包以后启动丢包重传,窗口变小;
这些最终表现是tcp层面的队头阻塞。

  • http1.0: 明文短连接,多个tcp连接;
  • http1.1: keepalive机制,长连接,复用同一个tcp连接,降低了建立连接的开销(节省了握手时间);
    客户端:pipeline机制,可以并行发;
    服务端:必须顺序回,有队头阻塞;(同域名下的请求复用同一个连接,但必须排队)
  • http2: 用stream层解决http层面的队头阻塞(让同域名下的请求不排队了,间隔进行),但是还是有tcp层面的队头阻塞;

    quic如何解决这个问题的

    解决TCP层面的队头阻塞,本质上是要开发一个新的流量控制、拥塞控制的整套方案放在UDP层之上。
    quic的改进:
    (1)窗口更新机制: 当已经读取的数据大于最大接收窗口的一半时,发送WINDOW_UPDATE帧告诉发送者,接收窗口已经更新。
    (2)拥塞控制算法: 由于底层是udp,没有拥塞控制算法,因此quic需要实现拥塞控制算法。
    quic在应用层实现拥塞控制算法,如bbr,bbr_v2,核心思想是用rtt来预测带宽情况。
    tcp用丢包事件来判断带宽情况,比较不准。但tcp之所以这么傻,也是因为tcp中的一些约束,很难准确判断rtt的大小。
    比如seq number机制,重传的时候无法判断是超时重传的包还是第一个包经过很长时间收到了,因为seq number相同。
    quic的seq number改进: 通过streamId+offset, packet number的机制,重传的包packet number递增,这样可以算准rtt。

吞吐量更大(新拥塞控制算法)

接上一节,quic开发了新的拥塞控制算法,吞吐量更大,更能利用带宽:
cubic vs bbr:

可以看到tcp默认的cubic拥塞控制算法频繁上下调整滑动窗口大小,锯齿状;
而bbr倾向于平稳发送,在实际带宽比较平稳的场景下,吞吐量更大。(图中折线下方的面积更大)

原来tcp为什么没有解决这个问题:
(1)tcp在linux内核里,升级太困难了。
(2)参考上一节,tcp的一些约束导致rtt算不准。比如ack delay、重传包的seq number不变。

耗时短。握手-rtt减少;重连-0RTT特性。

原来https over tcp的缺点:
(1)握手阶段: tls层和tcp层重复建立连接;
(2)握手阶段: tls1.2需要考虑历史包袱,考虑达到最大兼容性,握手需要2RTT;
(3)重连阶段: tls1.2需要一次握手;

quic如何解决这个问题的

(1)握手阶段合并: 只建立一次连接,省掉tcp握手的1rtt。
将建立连接这件事统一放在quic层里,udp只专心做传输的事情。
(2)握手阶段: 1RTT。运气不好的话还是2RTT。
参考tls1.3,去掉不安全的算法,在客户端预置一些密码套件。
第一个握手客户端就直接选定加密协议,并生成相应的随机数,相当于跳过了tls1.2第一个rtt的协商;
如果服务端不支持,则使用HelloRetryRequest继续。
大部分情况下,都可以省掉第一次的rtt;
(3)重连阶段: 0 RTT。
参考tls1.3的session ticket(quic叫server config),客户端和服务端都缓存之前握手协商好的配置。
重连的时候,客户端直接把配置和数据一起发给服务端。

0RTT的前提:

1。client不清缓存;(过期前)
2。通过cid路由到同一个server进程;
(或者服务端对于server config有统一缓存)
缺点:牺牲了一定时间内的前向安全;(过期时间内)

QUIC的缺点

CPU开销大

http over quic与http over tcp相比,cpu开销可能会更大。
如果没有特别优化,单机qps可能下降50%。
主要有以下几个原因:

内核态vs用户态

(1)原来拥塞控制在tcp,在内核,执行在内核态,性能更高;
解决方案: 暂时没想到;
(2)quic限定udp报文大小<=1mtu(IPV6下为1350,IPV4下为1370)。
由于包的大小不能很大 => 因此包的数量会较多=> 系统调用很多 => 性能下降。
解决方案:
(2.1)批处理;用sendmmsg,一次系统调用发多个udp包;
(2.2)网卡GSO offload方案,降低cpu消耗;

quic对包的大小有两个限制
(1) initial包>=1200: 预防UDP攻击;(反射攻击)
如果数据本身不足1200,用padding方式填充到1200字节。
反射攻击:
被利用服务器输出流量与输入流量的比值我们称之为放大系数。
这个系数与被利用服务器所提供的 UDP 服务有关。
之前提到的利用 Memcache 漏洞的 DRDoS 攻击,可以获得稳定的 60000 倍放大系数。
而我们日常使用的 DNS 则可以轻松的获得 50 倍的放大系数。
由放大系数反推,我们可以知道,如果一个 UDP 服务被利用以后,放大系数小于等于1的话,则不存在利用价值.
因为这个时候,只从带宽流量方面考虑的话,还不如直接利用攻击主机对被攻击服务器进行攻击效率高。

(2)<=1MTU(IPV6下为1350,IPV4下为1370):
IP层是没有超时重传机制的,如果IP层对一个数据包进行了分片,只要有一个分片丢失了,只能依赖于传输层进行重传。
结果是所有的分片都要重传一遍,这个代价有点大。
由此可见,IP分片会大大降低传输层传送数据的成功率,所以我们要避免IP分片。

加解密开销大

谷歌fork的openssl分支:boringSSL(笑死)
具体原因不太清楚,可能是谷歌选择的加解密算法比较安全、计算开销比较大。
也有可能是开源实现里,选择密码编码学算法比较普通,没有给生产环境级别的优化。

解决方案:
如果在生产环境使用,需要相应修改加密套件。

容易被拦截

因为一些运营商不待见udp。
根据腾讯的文档:https://toutiao.io/posts/tixau8w/preview
QUIC失败率较高的三个省份为:贵州、广西和新疆;
失败率较高的运营商为:教育网和长城宽带。

解决方案:
需要支持降级到普通https。

nginx支持不全

比如nginx reload会断开已建立的quic连接.(因为对ng来说是udp)

解决方案:
需要修改nginx内核。

  • 内核逻辑调试工具
    systemtap 内核逻辑调试工具,查看内核调用栈
    内核的timewait监控: /proc/net/netstat中 的 TCPTimeWaitOverflow
    Broken pipe(32) tcp_max_tw_buckets

QUIC应用场景

弱网环境:因为quic的拥塞控制算法bbr在丢包时性能更好;
因为quic的重连、建连rtt少;
对安全重视的环境:因为tls1.3更安全;
移动端:因为连接迁移功能;
并发请求多的应用: 因为quic没有tcp层的队头阻塞,多路复用更彻底;

参考

可以参考谷歌开源的chromium中的quic协议栈,以及相应的server demo。
谷歌浏览器内核支持quic,其他浏览器内核就不一定了。
谷歌: https://www.chromium.org/quic/
腾讯:https://zhuanlan.zhihu.com/p/32560981
微博: https://www.infoq.cn/article/2018%2F03%2Fweibo-quic

http_range

http协议header中的range相关

客户端可以在http请求的header中指定请求资源的范围(range),如果服务端支持的话,就可以只返回部分数据,节约网络资源。
如果服务端实现了这部分协议,客户端就可以进行断点续传、多线程下载、在线播放视频修改进度等功能了。
应用场景:
1。在线播放视频:调整进度;(seek offset)
2。下载文件:多线程下载、断点续传。

检查服务端是否支持

可以通过命令:

script
1
curl -I https://cdn.com/xxx.png

来检查服务端是否支持。
如果支持,服务端的回复是bytes:

1
Accept-Ranges: bytes

如果不支持,服务端的回复是none:

1
Accept-Ranges: none

(curl -I只显示header; curl -i显示header和body)

实际应用中的回复

客户端发送:

script
1
curl -i -H "Range: bytes=0-1023" https://cdn.com/xxx.png

服务端正常回复206(单个区间的话):

script
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 206 Partial Content
Content-Type: image/png
Content-Length: 1024
Connection: keep-alive
ETag: "2F4AF992F06D75CA7BE353ED2A981C45"
Date: Tue, 01 Feb 2022 15:54:43 GMT
Last-Modified: Tue, 25 Jan 2022 15:54:42 GMT
Expires: Tue, 08 Feb 2022 15:54:43 GMT
Age: 579015
Cache-Control: max-age=604800
Content-Range: bytes 0-1023/9404
Accept-Ranges: bytes

如果range不合法的话,服务端回复:

script
1
HTTP/1.1 416 Requested Range Not Satisfiable

如果服务端不支持range请求,会回复200,并且返回全部数据:

script
1
HTTP/1.1 200 OK

其他Range相关的一些姿势可以参考:
https://www.greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#rule.ranges-specifier
实际还可以指定多区间,指定末尾100个字节(Range: bytes=-100),
还可以用If-Range避免文件发生变更的情况:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/If-Range
只有Last-Modified或者ETag字段验证通过的时候,才处理Range请求,否则返回200(整个文件)。

实现:spring中

方案1,交给spring接管

如果是本地机器中的资源,可以封装成FileSystemResource
如果要从别的地方获取byte数组,可以封装成ByteArrayResource;
状态直接写200(OK),spring会自动转成206。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping(value = "/v3/csc/center/range")
public class CscCenterRangeController {
@RequestMapping("/load")
@ResponseBody
public ResponseEntity<ByteArrayResource> load(@RequestParam(value = "fileKey") String fileKey) {
String res = "1234";
final HttpHeaders responseHeaders = new HttpHeaders();
// responseHeaders.add("Content-Type", "video/mp4");
return new ResponseEntity<>(new ByteArrayResource(res.getBytes()), responseHeaders, HttpStatus.OK);
}

@RequestMapping("/load-file")
@ResponseBody
public ResponseEntity<FileSystemResource> loadFile(@RequestParam(value = "fileKey") String fileKey) {
String filePathString = "/Users/fengmengqi/Documents/DH_scret.png";
final HttpHeaders responseHeaders = new HttpHeaders();
// responseHeaders.add("Content-Type", "video/mp4");
return new ResponseEntity<>(new FileSystemResource(filePathString), responseHeaders, HttpStatus.OK);
}
}

缺点是spring似乎没有实现Range协议中的If-Range头。

方案2,在spring提供的HttpRange类基础上实现

可以参考org.springframework.web.servlet.resource.ResourceHttpRequestHandler#handleRequest
在HttpRange类的基础上实现自定义的Range相关协议,这样可以增添If-Range的功能。

参考资料

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Range_requests
https://www.greenbytes.de/tech/webdav/draft-ietf-httpbis-p5-range-latest.html#rule.ranges-specifier

VoIP和RTC

术语

VoIP

VoIP: Voice over Internet Protocol
基于IP的语音传输。只是概念,不对应具体协议栈、有不同实现。
别名:IP电话、互联网电话、宽带电话、宽带电话服务。
VoIP可用于包括VoIP电话、智能手机、个人计算机在内的诸多互联网接入设备,通过蜂窝网络、Wi-Fi进行通话及发送短信。

VoIP的实现迭代:
(1)DSL/cable调制解调器
(2)Wifi/3G
(3)LTE
(4)RCS

V.VoIP

Video over IP
一些V.VoIP的基本要素包括信令、媒体引擎、会话描述协议(SDP)、实时传输协议/实时控制协议(RTP/RTCP)、
网络地址转换(NAT)、安全协议、服务质量(QoS),以及其余电话组件。
V.VoIP实质上是封装了全部这一切,再加上用户界面,包括拨号、通信录/联系人列表和呼叫历史记录(未接/已接/已拨电话),
以提供一个完整的V.VoIP客户端。

RTC

RTC: Real-Time Communications
RTC也就是实时交流。一般是指音\视频。日常生活中的例子包括:微信通话、视频会议、直播PK连麦、在线教育。

上述两种概念,都看重”端到端延时”指标,大致定义如下:

从这个流程,可以看出相比于普通文本传输,主要是多了和音视频相关的”采集”、”处理”、”编码”等操作。
其中的”网络”部分,由于VoIP是从电话网络演化而来,主要侧重于原来的电话网到IP网络;
RTC则主要侧重于IP网络。

SIP协议

Session initialization Protocol
一种信令协议。
VoIP的具体实现的其中一部分的协议,主要做控制层(信令),基于文本。(RFC 2543, RFC 3261~3265)大致开始于1999年。

WebRTC

谷歌收购GIPS引擎,与GTalk通信库合并,2011年纳入Chrome体系并开源,命名为WebRTC。
2012年获各大浏览器厂商支持,纳入W3C标准。

WebRTC的架构大致如下,主要是给前端提供了封装好的API,屏蔽底层硬件。:

绝大部分浏览器的新版本都支持webRTC:

RTMP

RTMP: 是Real Time Messaging Protocol的简称,
基于 TCP 协议,用来进⾏实时数据通信的⽹络协议,
推流到CDN即用的是RTMP;

SRS

Simple Realtime Server
互联网直播服务器集群的开源框架;
https://github.com/ossrs/srs/wiki/v2_CN_Home

发展历史

1996~2004:H323

电话线上网时代;

H.323标准

ITU(国际电信联盟)推出,
基于传统 PSTN 架构(Public Switched Telephone Network,公共交换电话网)。
(MCU+RTP)
H.323标准的VOIP网络中:
用户呼叫021号码:先送到上海、再到目的地;
在SIP中:直接查,无短途长途之分。

场景:PC语音会议;
优点:达到了可通话的质量;
缺点:MCU是中心化的,拓展性有上限。有合流成本。

2001~2007: P2P/Mesh

IETF, SIP(UDP + HTTP) 软交换

Skype: 超级节点+P2P/Mesh通信, 通信质量接近PSTN网络;
GTalk: 谷歌山寨的Skype

瑞典GIPS公司:开发出3A算法;
GIPS被谷歌收购,GIPS与 GTalk 的libjingle合并成为早期WebRTC一部分。

场景:社交IM的1v1通信;
优点:轻量级、架构拓展性强;
缺点:跨运营商无法保证Qos,去中心化后无法监控、管控;

2003~2012: SFU

MMO RPG游戏兴起;

场景:游戏频道语音沟通、直播聊天室。
优点:架构简洁、实时性强,无需中心化合流聚合;
缺点:客户端下行压力大,互动人数有最大上限;

SFU vs MCU

SFU : Selective Forwarding Unit, 上行一路流,下行N路流,服务端仅转发。客户端压力大。
MCU: MultiPoint Control Unit, 中心化,上行下行都一路流,服务端负责合流。服务端压力大。

基础评估指标

性能相关:

  • 功耗
  • cpu/gpu/内存占用
  • 引发降频时间(手机)

    屏幕分享相关

  • 清晰度&分辨率
  • 色彩准确度
  • 相对静止场景最低码率
  • 相对运动场景流畅度(文档滚动、局部动画)
    基础端到端延时(<1s)

    音频相关

  • 频宽
  • 基础音质(POLQA评分>2.5)
  • 基础端到端延时
  • 3A(增益控制、噪声一直、回声抵消)

    视频相关

  • 基础画质
  • 基础流畅度(帧率、帧间隔)
  • 基础端到端延时
  • 音画同步

直播过程

对于主播:

1。申请开始直播:http; // 返回ok
2。获取推流目标:信令服务器 -> 创建房间-> 获取当前位置合适的MCU节点;
3。KTP推流到MCU节点;(UDP)
4。MCU存到SRS(经过RTMP转码);

对于用户,拉数据:

用户 -(flv over http)-> CDN -> Router -> SRS

参考资料

https://segmentfault.com/a/1190000040760567

metaspace笔记

metaspace内容

Klass Metaspace+NoKlass Metaspace:

Klass Metaspace

如果关闭压缩指针,或者堆大于32G,这块儿会存到NoKlass Metaspace里。

存class元数据,JVM对class的表示。
vtable: 类中的方法(我理解类似虚函数指针,指到NoKlass区域)
itable: 类实现的接口
oop map: 类引用的对象地址

NoKlass Metaspace

方法相关:

方法的字节码
参数信息
局部变量表
异常表

常量池
注解
方法计数器(JIT)

相关参数

-Xnoclassgc: 让 JVM 在垃圾收集的时候不去卸载类.
尽量别开启这个选项。

–XX:+CMSClassUnloadingEnabled: 如果使用CMS,需要开启这个选项。

CompressedClassSpaceSize: Klass空间的大小。默认1G,可能要改大。
比如可以CompressedClassSpaceSize设置2G, MaxMetaspaceSize设置3G.(看klass和noklass的比例)

jstat

主要看MC,MU,CCSC,CCSU:
MC: Klass+NoKlass Metaspace已committed的内存大小/KB
MU: Klass+NoKlass Metaspace已使用的内存大小
CCSC: Klass Metaspace已commit的内存大小/KB
CCSU: Klass Metaspace已使用的内存大小/KB

其他:
M: Klass+NoKlass总共使用率
CCS: CCSU/CCSC
MCMN和CCSMN: 0
MCMX: Klass+NoKlass Metaspace两者总共的reserved的内存大小
CCSMX: Klass Metaspace reserved的内存大小

生命周期

每个class loader都会有自己的metaspace,各自分配(metachunk,metablock),但是总和是公用的。

普通类metaspace: 基本不太可能卸载,因为需要等class loader卸载;
匿名类metaspace: 跟着匿名类的生命周期。lambda和method handler可以提前卸载,不等待class loader。

卸载以后内存是否会归还:

  1. Klass空间必不归还;
  2. noKlass空间: 如果Node分配给同一个class loader,比较容易归还;
    如果分配给了多个class loader,不释放空间。
    一个Node多个chunk;
    单个chunk整体属于一个class loader;
    一个chunk多个block,每个block是最小分配单元(分配给调用者)。

参考

https://javadoop.com/post/metaspace
https://segmentfault.com/a/1190000023235677
https://www.infoq.cn/article/troubleshooting-java-memory-issues

网络安全笔记-1

黑客一般如何行动

知己知彼百战百胜,正如我们安全人员试图了解黑客,黑客也试图了解我们安全人员是如何防守的。
所以大方向上,黑客一般是先观察再行动、整个过程中隐藏自己(确保了解了对方,但不被对方了解)。细化以后就是:

1
2
3
4
5
6
7
8
1.踩点(Footprinting): 确定目标地址范围、查询名字空间,收集所有细节
2.扫描(Scaning): 评估目标、识别监听服务,集中于最有希望攻克的
3.查点(Enumeration): 识别合法用户账号、保护力度不够的共享资源
4.访问(Gaining Access): 发送木马或链接
5.提权(Escalating Privilege): 从普通用户提到特权用户(比如ROOT)
6.窃取(Pilfering): 数据和文件,收集入侵机制和途径;
7.灭迹(Covering tracks): 掩盖来访痕迹;(显然这个是最重要的,别的失败了都没关系)
8.创建后门(create backdoor): 为下次入侵准备便利

其中步骤6窃取了其他账号的话,可以跳到步骤4重新访问,从而以更高权限用户进行后续步骤。

此外还有一招就是拒绝服务(DDOS),步骤4访问失败的话可能会采用。(强行让目标系统无法正常提供服务)

上述步骤的具体技术手段实现

1. 踩点

打开源查询: Usenet,搜索引擎,Edgar,GooScan,FingerGoogle
whois: dig,nslookup ls -d,Sam spade,dnsmap
whois的web接口: www.networksolutions.com/whois
ARIN whois: www.arin.net/whois

其他思路扩展:
1.谷歌搜索返回值为”it worked!”的网站,就能找到初始配置apache的服务;
// intitle:Test.Page.for.Apache “it worked!” “this Web site!”
2.从目标公司的招聘要求(比如5年xxx防火墙经验,使用过xxx安全系统)就能猜到目标公司的现有安全软件是什么(甚至是几年前的版本);
3.从地理位置,翻垃圾桶;
4.从有目标公司工作经历的简历库,能收集相应信息;

如何防范踩点:
可以参考RFC2196(公众信息安全对策)

2.扫描

Ping系列工具:fping,hping,nmap
TCP/UDP端口扫描: SuperScan,AutoScan,Scanline
OS检测: nmap,amap,SinFP,xprobe2

3.查点(Enumeration)

列出用户账号:空会话、DumpSec,PSTools
列出共享文件: showmount,SMB-NAT,nmbscan
识别应用程序: telnet/netcat抓取旗标
SNMP: rpcinfo,amap,nmap,Cisco Torch

4.访问

密码嗅探: airsnarf,dsniff,Cain and Abel,phoss
蛮力攻击: hydra,medusa
渗透工具: SIPcrack, Metasploit Framework,Canvas

5.提权

破解密码: John The Ripper,L0phtcrack,rcr
利用漏洞: Metasploit Framework

6.窃取

评估可信系统: rhots,LAS Secrets
搜索明文密码: 用户数据,配置文件,WINDOWS R

7.灭迹

清除日志记录: logclean-ng,wtmpclean
掩藏工具: rootkits,文件流

8.创建后门

创建流氓用户账号: members of wheel,Administrators
安排批处理作业: cron, AT
感染启动文件: rc,启动文件夹,注册表键
植入远程控制服务: netcat, psexec, VNC
安装监控机制: 键击记录器
用木马替换真实应用: login, fpnwclnt.dll,patched SSH version

9. DDOS

Syn泛洪: synk4
ICMP技术: ping of death, smurf, ICMP nuke
Overlapping fragment/offset bugs: teardrop,bonk,newtear
Out of bounds TCP options: supernuke.ext
(OOB)DDOS: trincoo/TFN/stacheldraht

多项目共享代码之——git subtree

需求

多个项目之间共享代码。
背景是前后端之间有一些protoc代码需要共享,避免互相发送文件(像某广东大厂这么山寨)。
由于protoc文件一般很小,因此首先考虑使用git subtree来建立子项目。

如果需要共享的部分占的存储空间很大(比如是一个超大的分词字典,则应该考虑git submodule)。

WHY: 为什么选择git substree

优点

简单、不容易出错
只有用到了这个子目录、子仓库的人才会需要使用git subtree相关命令去更新代码;也就是对于其他模块的开发人员,这就是一个普通目录,完全无感知,对无关人员完全透明。

劣势

性能
由于git substree本质上是跟踪了子仓库的所有文件来实现的,因此如果子仓库的存储空间占用很大,父仓库的git性能就会受到影响。(这个时候就需要考虑用别的办法了,比如从外部存储读取、或者用git submodule。)

原理

git subtree:

本质上是通过分支合并的方法模拟了一个子仓库,对于父仓库来说其实相当于从头到尾只有一个git。
首先git fetch就可以拉取任意git地址的代码,所以先用git fetch拉到某个目录,然后通过分支合并的方式把它们合并到当前分支。
因此本质上会跟踪子仓库所有文件。存储开销比较大。适用于子仓库全是纯代码的情况。

git subtree是由第三方开发者贡献的脚本,用git底层命令封装出来的。
从v1.5.2开始,git新增并推荐使用这个功能来管理子项目。

git submodule:

本质上父仓库只记录子仓库的commit-id,因此存储开销很小。但是缺点就是直接git clone的时候只会拉父仓库的代码,子仓库是空的,需要每个开发人员都主动拉子仓库代码,或者用递归的参数去拉。开发过程中出错的概率很高、比较繁琐,除非万不得已最好别用。

HOW: git subtree使用

首先新建一个git项目,补充.gitignore文件。
然后在原有的项目中通过以下命令来引入该项目到某个子目录(不需要预先创建子目录):

1
2
3
4
5
6
# 首次下载
git remote add -f <子仓库名> <子仓库地址>
git subtree add --prefix=<子目录名> <子仓库名> <分支> --squash
git fetch <远程仓库名> <分支>
git subtree pull --prefix=<子目录名> <远程仓库名> <分支> --squash
# git push review

其中的<分支>指的是子项目的分支。
以上前4个命令对应的操作就是:
1.给远程的某个xxx.git地址取个别名记录下来;
2.创建一个子目录专门跟踪子仓库;
3.拉取子仓库的某分支的代码;
4.把拉取的代码放到子目录。
建立关联以后,以后如果想要pull子项目代码,就只需要执行以下几步:

1
2
3
4
# 非首次下载
git fetch <远程仓库名> <分支>
git subtree pull --prefix=<子目录名> <远程仓库名> <分支> --squash
# git push review

以上就是一般情况下使用git subtree所需的所有命令了,非常得简单,新命令其实也只有两个git subtree命令。

这里大家可能会疑惑为什么没有提到给子仓库提交\push的命令,
其实命令是有的,但是没有必要在父仓库里提交子仓库的代码;
如果想更新子仓库的代码,直接随便找个目录git clone,git add,git commit, git push就行了,没有必要多记1个非必要的命令。
不过如果只是出于收藏爱好,可以收藏一下这个非必要的命令:

1
git subtree push --prefix=<子目录名> <远程仓库名> 分支

参考资料

https://my.oschina.net/jellypie/blog/487619
https://zhuanlan.zhihu.com/p/100214931

跨域相关

目标

部分url:
1.接受公司旗下域名跨域请求;
2.post请求+json返回值;

设计

可选方案

1.jsonp: 只支持GET, 否决;

2.nginx配置: 需要改配置上线,备选;

3.java配置注解@CrossOrigin: 经测试可行,新域名或新url需要重新打包上线,备选;

4.使用filter/interceptor统一配置,可方便得使用配置中心上线新域名、url,备选;

对于目标1,理想情况是使用配置中心来快速增加支持的域名,因此优先考虑方案4;

由于目标2,我们基本上用的都是post+json的接口,因此浏览器跨域前会用Options命令询问服务器的跨域权限配置。

这里需要注意的问题:

Options命令不带cookie,因此过不了鉴权,需要考虑放行或者不加鉴权;
1.多一次网络请求,考虑转化为简单请求避免Options命令。(如text/plain
2.Options命令通过的结果缓存在浏览器的时间由Access-Control-Max-Age控制(秒)。

日志相关

如果设置 Access-Control-Allow-Credentialstrue;
则可以在日志打印cookie信息。(除options请求外的)

但如果没有鉴权需求这里应该不允许携带cookie,避免提权攻击。
比如后端有执行代码的漏洞,又允许别人带着cookie过来,就造成CSRF攻击。
参考: https://zhuanlan.zhihu.com/p/86626693
所以将allowCredentials设置成false。

可能的坑

本质上是浏览器进行的同源控制提高安全性,需要服务器告诉浏览器哪些跨域是安全的,
因此最重要的是与浏览器相连的第一跳返回的header情况。
如果前面几跳(如SSR,nginx)都是直接透传Header的话,才能确保我们的后端配置生效。

chrome浏览器查看options请求可能需要修改配置,不属于XHR请求;
wireshard可以抓到不加密(vpn往往加密了)的options请求。

参考

http://www.ruanyifeng.com/blog/2016/04/cors.html

线上性能debug-查看方法级耗时

摘要

可以手动挨个儿方法打debug日志;
可以用AOP集中拦截打日志;
可以用async-profiler查看火焰图;
可以用groovy动态执行benchmark;
可以用arthas的trace命令。

目标(问题背景)

背景是线上某个接口慢,需要优化。为了找到优化的方向,首先需要知道接口的各个部分的耗时,这样才能找到耗时比例最高的部分针对性地优化。
有些时候我们可以使用公司基础架构提供的trace\ rpc monitor来查看rpc调用粒度的耗时;(很多时候慢都是因为某个rpc服务调用慢)
但有些时候我们还是需要知道更细粒度、方法粒度的耗时或者耗时占比, 因为trace中的span一般粒度都是IO调用、如数据库访问、redis访问、rpc调用这种级别。

实操

方法1:每个方法打日志

直接在每个方法开始结束的地方打上debug日志,然后在需要查看耗时的时候,运行时调整日志级别,用arthas:

1
ognl '@com.xxx.spring.BeanFactory@getBean("xxxxServiceImpl").logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)'

这里不一定用com.xxx.spring.BeanFactory, 也可以用某个applicationAware、能拿到applicatoinContext的类,反正只要能用一行代码访问到logger就行。
如果logger所在的bean没有在ApplicationContext里(比如interceptor),可以考虑把logger改成static的。

方法2:AOP集中拦截打日志

这个方法其实算是方法1的简单升级,毕竟每个方法都加上两行开始结束的日志代码量太大,实现上不可能做到。可以用AOP的表达式把所有需要监控的方法都覆盖进去。

这种方法也需要改动代码。

方法3:用async-profiler查看火焰图

前两种方法都需要改动代码,改造成本较高。一个很自然的思路查看耗时占比是我们手动疯狂jstack,统计一下最多次停留的方法,就是耗时占比最高的方法。

代替手动jstack的工具就是https://github.com/jvm-profiling-tools/async-profiler。
可以使用命令(采样30秒):

1
./profiler.sh -d 30 -f time.svg <pid>

有时候会发现意外的耗时瓶颈。(简单的过滤入口操作)
生成的svg文件可以直接用谷歌浏览器打开,可视化的结果,支持点击展开任意层级,非常得方便。

方法4:用groovy脚本动态执行benchmark

有时候线上接口有缓存,阻碍了我们收集p95耗时的瓶颈。这个时候我们可以用groovy脚本动态传入需要执行的java代码。

1
2
3
4
5
6
7
8
// 某个bean中的预留方法:
private Object debug(String code){
String groovyStr = code; // 需要执行的benchmark代码
Binding binding = new Binding();
GroovyShell groovyShell = new GroovyShell(binding);
Script script = groovyShell.parse(groovyStr);
return script.run();
}

这里由于当做脚本语言来用了,安全隐患就是不能让我们以外的人调用,因此如果是内网业务可以鉴权后调用;
外网业务则让可以这个方法没有任何地方调用,需要用的时候我们自己登到机器上用arthas来调用。
(如果黑客入侵到这个程度的话,也无所谓有没有这个方法了)

方法5:用arthas的trace命令

如果不仅需要知道耗时占比,还想知道耗时的具体大小,可以用arthas的trace命令:

1
trace --skipJDKMethod false com.xxx.service.impl.XXXXServiceImpl method1 '#cost > 200'

需要注意一定要加--skipJDKMethod false这个参数,因为我们很多时候用了java8的stream方法,如果跳过了jdk方法的统计,会发现各个子方法的耗时加起来不等于总耗时。

这个不像async-profiler一样能看到所有层级的调用,默认是一层,如果加了这个参数,能看到很多时候耗时最高的方法是 collect方法。

总结

方法3可以看到整个调用栈展开后的耗时占比;
方法4可以构造多次的调用;
方法5只能每次看一层,需要一层一层手动去找,而且官方给的通过listenId增强的方法需要用到telnet,而容器里往往根本没有telnet。因此实际上需要我们一层层去找。

实际工作上往往需要我们结合方法3、4、5。

升级HTTP2笔记

已知的坑

header大小写

header names按http1.1协议是不区分大小写的,http2里全是小写,nginx反向代理会保留大小写,所以如果以前的代码依赖大写,就会挂掉。

(为啥h2里变小写了: HPACK算法, 解压的时候查表还原header names, 类似于哈夫曼算法)

与websocket不能共用域名, 只支持https

websocket和http2都是从http协议握手协商升级过去的,也就是起点是http1.1。 两种协商过程:

  • http1.1 -Upgrade=> websocket+ssl (wss)
  • http1.1 -Upgrade=> http2

H2是基于https的,因此如果某个域名切换到H2以后,就只能支持https的链接了,不再支持http;
如果硬要不加密、不安全,可以使用H2C,但是主流浏览器都声称不会支持H2C,因此这个选项其实并不实际。

(这个倒是不能算坑,只能算一个特性)

Why: 为什么要升级到H2

主要是性能上的优化。

Http2的修改:

1.header压缩: HPACK算法;

2.服务器推送: server push,推送html里的css,js;

3.pipeline请求;

4.多路复用,同域名单个TCP连接,划分stream id;

5.二进制传输;

其中1、3、5肯定提升性能;

2则取决于缓存策略,因为可能服务器push了客户端已经缓存的资源,浪费带宽;

4取决于优先级策略,因为把以前前端手动控制的优先级策略,交给浏览器内核来自动实现,按https://blog.cloudflare.com/zh/better-http-2-prioritization-for-a-faster-web-zh/ , 谷歌内核是最接近最优策略的。但是由于以前可以多开TCP连接,现在是单开,因此如果有大图片,会阻塞后面的小图片。

谷歌的优先级处理逻辑如下:

此外,由于有了特性4,不再需要前端内联资源,因此一些针对http1.1的优化可以回滚,好处是可以简化代码,提高缓存效率,去掉重复建立连接的开销;

坏处就是不能再依赖多开TCP连接了,图片只能一张一张刷开。

HOW:怎么升级到H2

根据nginx官网的指导,没有必要全链路H2,只需要client到nginx是H2就够了(terminate protocol)。后端服务可以维持原来的协议。(类似于以前升级https)

因此只需要修改nginx配置即可,对后端服务无感知。(如下图)

ng官网指导: https://www.nginx.com/blog/7-tips-for-faster-http2-performance/

为什么没必要全链路H2:

按ng官网的说法,H2的主要优点是性能提高,对于内网网速来说,这点提升意义不大;

因此nginx只在服务端支持H2, 不支持客户端H2(转发的时候)。

如果想要全链路H2, 就不能用nginx了。

参考资料:

1.https://zhuanlan.zhihu.com/p/276057825

2.https://www.cnblogs.com/confach/p/10141273.html

3.https://zhuanlan.zhihu.com/p/89471776

4.https://juejin.im/post/6844903745218674695

5.https://blog.cloudflare.com/zh/better-http-2-prioritization-for-a-faster-web-zh/

6.https://hpbn.co/http2/#stream-prioritization

7.https://calendar.perfplanet.com/2018/http2-prioritization/

8.https://www.jianshu.com/p/e57ca4fec26f

9.https://zhuanlan.zhihu.com/p/26559480

10.https://www.cnblogs.com/ranFengHua/p/10816956.html

11.https://blog.csdn.net/liujiyong7/article/details/64478317

12.https://www.nginx.com/blog/7-tips-for-faster-http2-performance/

13.https://www.cnblogs.com/operationhome/p/12577540.html