安全增强
设置高低水位线
Netty OOM的根本原因
根源:进(读速度)大于出(写速度)
表象:
- 上游发送太快:任务重
- 自己:处理慢/不发或者发的慢:处理能力有限,流量控制等原因
- 网速
- 下游处理速度慢:导致不及时读取接收Buffer数据,然后反馈到这边,发送速度降速
Netty OOM - ChannelOutboundBuffer
ChannelOutboundBuffer就相当于Netty发送数据的仓库,如果存的数据过多,就会OOM
存的对象:LinkedList存ChannelOutBoundBuffer.Entry
解决方式:判断totalPendingSize>writeBufferWaterMark.high()设置unwritable,写完之后会移除entry
Netty OOM - TrafficShapingHandler
以ChannelTrafficShapingHandler为例
存的对象:messageQueue存ChannelTrafficShapingHandler.ToSend
解决方式:判断queueSize>maxWriteSize或delay>maxWriteDelay,设置unwritable
unwritable
Netty OOM的对策
设置好参数:
- 高低水位线(默认32k到64k)
- 启动流量整形时才需要考虑
- maxWrite(默认4M)
- maxGlobalWriteSize(默认400M)
- maxWriteDelay(默认4s)
判断channel.isWirtable()
启用空闲检测
示例:实现一个小目标
- 服务器加上read idle check - 服务器10s接收不到channel的请求就断掉连接
- 保护自己、瘦身(及时清理空闲的连接)
- 客户端加上 write idle check + keepalive - 客户端5s不发送数据就发一个keepalive
- 避免连接被断
- 启用不频繁的keepalive
Server端Idle
创建Server端的Idle检测,IdleStateHandler不支持共享
将ServerIdelCheckHandler加入到Server的pipeline中
启动Server和Client,查看Server端的日志
可以看到日志中输出了USER_EVENT: IdleStateEvent(READER_IDLE, first)和USER_EVENT: IdleStateEvent(READER_IDLE),时间间隔10秒,说明我们设置的readIdle成功了,但是连接并没有断开。
修改ServerIdleCheckHandler,重写channelIdle方法
启动Server和Client,查看Server日志
可以看到Server端我们加入的日志已经打印出来了,再查看Client端日志
可以看到连接断开了
Client端Idle+Keepalive
创建Client端的Idle
Keepalive就是向Server发送一条信息
创建KeepaliveHandler
将ClientIdleCheckHandler和KeepaliveHandler都加入到Client的pipeline中。
keepalive可以共享,所以可以添加@Sharable注解
Keepalive要处理编解码,所以要放在比较靠后的位置。
启动Server和Client,查看日志
Client:
Server:
黑白名单
Netty中的”cidrPrefix“
比如判断两台主机的网络是不是在同一个局域网内,将IP地址划分为两部分:网络位和主机位
IP地址: 11000000.10101000.00000001.00000001
子网掩码: 11111111.11111111.11111111.00000000
前24位为网络位,后8位为主机位
A/B/C…类
网络 | 格式 | 子网掩码 |
---|---|---|
A类(前8位为网络位) | network.node.node.node | 255.0.0.0 |
B类(前16位网络位) | network.network.node.node | 255.255.0.0 |
C类(前24位为网络位) | network.network.network.node | 255.255.255.0 |
这样的切分就很浪费
CIDR:无分类域间路由选择,又常被称为无分类编址,表示前多少位为网络位
子网掩码 | CIDR值 |
---|---|
255.0.0.0 | /8 |
255.128.0.0 | /9 |
255.192.0.0 | /10 |
Netty地址过滤功能源码
- 同一个IP只能有一个连接
- IP地址过滤:黑名单、白名单
IP过滤
AbstractRemoteAddressFilter
在channelRegistered()和channelActive()的时候,调用了handleNewChannel方法
IpSubnetFilterRule:用于判断ip是不是在一个局域网内
示例:使用黑名单
在Server端代码中使用RuleBasedIpFilter
运行Server和Client,可以看到Client端显示连接断开。
将ipSubnetFilterRule修改为new IpSubnetFilterRule(“127.1.0.1”, 16, IpFilterRuleType.REJECT)。
远程IP是127.1.0.1(转换之后networkAddress为2130771969), cidrPrefix为16(子网掩码为255.255.0.0,转换之后subnetMask为-65536),策略为拒绝,IP & 子网掩码 = 2130771968,地址描述为127.1.0.0。而我们请求时,IP为127.0.0.1,计算出的IP & 子网掩码 = 2130706432,地址描述为127.0.0.0,就不会被拦截了。
自定义授权
在Server端创建授权Handler
此时启动Server和Client,可以看到Client断开了连接,Server端则打出了第一个handler不是auth的日志
在Client添加Auth的请求
此时启动Server和Client,可以看到Client没有被断开,Server的日志中也出现了pass auth
的日志,如果将userName改为admin2,再次执行,可以看到Client被断开连接了,Server中出现了fail to auth
的日志
使用SSL
什么是SSL
SSL/TLS协议在传输层之上封装了应用层数据,不需要修改应用层协议的前提下提供安全保障
第二行为IP层,第三行为TCP层,第四层为应用层,但是这里可以看到是一个SSL,比如说http,那http的数据可以放到加密的application data里面。
应用层的数据,通过加密,存储到ssl协议中的application data。
TLS(传输层安全)是更为安全的升级版SSL。
SSL的功能与设计
表象
内容加密方式:对称加密
对称加密秘钥传递:非对称加密
对称加密的秘钥产生:三个随机数一起产生,client hello是携带随机数,server hello时携带随机数,client产生并发送给server的pre master key用非对称加密的方式传递
Note:本节示例基于”单向验证+交换秘钥方式为RSA方式“是简单,最雏形的SSL
为什么采用对称加密而不是非对称加密:因为对称加密简单,效率高。
非对称加密的公钥信息是放在证书上的。
证书的来源:自己做证书或者购买授权的证书。
SSL的抓包演示与解析
抓包工具:wireshark https://www.wireshark.org/#download
低版本需要安装Npcap https://wiki.wireshark.org/CaptureSetup/Loopback
抓包案例
-
io.netty.example.securechat.SecureChatClient:
final SslContext sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)
.ciphers(Arrays.asList("TLS_RSA_WITH_AES_256_CBC_SHA")
).build(); -
io.netty.example.securechat.SecureChatServer:
SelffSignedCertificate ssc = new SelfSignedCertificate();
System.out.pintln(ssc.privateKey());
修改SecureChatClient
修改SecureChatServer
启动Server和Client,Server端可以看到证书地址和秘钥地址,Client端可以看到日志
启动wireshark,并选择Loopback
在过滤器中输入过滤条件ssl && tcp.dstport==8992 || tcp.srcport==8992
,表示ssl协议并且接收端口或者发送端口是8992的,重启Server和Client,可以看到有5组消息被筛选出来了,此时可以停止wireshare的分析和代码
Client Hello
可以看到clien-hello中有一个Random,就是之前所说的随机数,然后提供了Cipher Suites秘钥套件,类似于自己提供出来,然后让别人去选
Server Hello, Certificate, Server Hello Done
server-hello中也带了一个随机数,同时告诉client选择一个什么加密方式
同时还带了证书的公钥信息,最后执行了一个hello done
Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
客户端拿到证书的公钥信息,自己有一个随机数,server hello也给它了一个随机数,这个时候,他就产生了pre master key,通过公钥加密传给server,对端收到PreMaster之后,就可以解密出这个对称加密的秘钥。
Change Cipher Spec是告诉对方接下来要开始加密了,接着就开始进行加密了。
New Session Ticket, Change Cipher Spec, Encrypted Handshake Message, Application Data
告诉对方我也可以进行加密了
Application Data
被加密的数据,就是日志打出的信息
SSL流程
使用SSL
在Netty中使用SSL:io.netty.handler.ssl.SslHandler
在Netty中使用SSL:单向认证
- 服务器端准备证书:自签或购买
- 服务器端加上SSL功能
- 导入证书到客户端
- 客户端加入SSL功能
Server端
Client端:
启动Server端和Client端
此时会发现Client端报错了,不信任证书,所以将证书加入信任
添加信任之后,Client端就可以正常执行了