TLS 加速技术:Intel QuickAssist Technology(QAT)解决方案

本文介绍了 Intel QAT 技术方案,通过 Multi-Buffer 技术和 QAT 硬件加速卡的两种方式实现对 TLS 的加速

一、背景

当前 TLS 已经成为了互联网安全的主要传输协议,TLS 带来更高的安全性的同时,也带来了更多的性能开销。特别是在建连握手阶段,TLS 的 CPU 开销,相对于 TCP 要大很多。

业界在优化 TLS 性能上已经做了很多软件和协议层面的优化,包括:Session 复用、OCSP Stapling、TLS1.3等。然而在摩尔定律"失效"的今日,软件层面的优化很难满足日益增长的流量,使用专用的硬件技术卸载 CPU 计算成为目前通用的解决方案。本文将介绍 Intel 在 TLS 加速领域提供的 QAT 技术方案。

二、Intel QuickAssist Technology(QAT)技术方案

Intel 提供了 TLS 异步加速的完整解决方案: Intel QuickAssist Technology(QAT),简称 Intel QAT 技术。

如下图所示,QAT 支持加速的密码算法覆盖了 TLS 的整个流程,包括:握手阶段的签名、秘钥交换算法,数据传输的 AES 加解密算法等。

图1. QAT 对TLS流程的密码算法的支持
图1. QAT 对 TLS 流程的密码算法的支持(图片来源

QAT 提供了对称与非对称两类密码算法的支持,主要包括:

  1. 非对称加密算法:RSA, ECDSA, ECDHE
  2. 对称加密算法:AES-GCM(128,192,256) 

注:QAT 加速的优势主要体现在非对称加密上,从官方的整体性能数据看,非对称算法性能提升 1.6~2 倍,对称算法性能提升 10%~15%

2.1 QAT Engine 软件栈

QAT Engine 是 QAT 技术方案的核心模块,主要的作用是作为应用程序和硬件之间的中间层,负责 “加解密操作的输入输出数据” 在用户应用程序与硬件卡之间进行传递,主要操作就是IO的读写。

QAT Engine 是以 OpenSSL 第三方插件的方式提供给用户,这个意味用户可以使用 OpenSSL 标准的 API,就可以实现对 TLS 的加速,只需要对原有代码做 OpenSSL 异步改造,就可以享受 QAT 技术带来的 TLS 性能加速,业务侵入性较小。

图2. Intel QAT Engine 软件栈
图2. Intel QAT Engine 软件栈(图片来源

如上图所示,QAT  Engine 支持两种加速方式:

  • 软件加速(qat_sw):使用 Multi-Buffer (SIMD)技术,对密码算法进行并行处理优化。
  • 硬件加速(qat_hw):使用 QAT 硬件加速卡,将密码算法计算从 CPU OffLoad 到硬件加速卡。

下面将介绍软件和硬件两种加速路径的实现方式。

三、软件加速:采用 Intel Multi-Buffer 技术

Intel 从 Whitely 平台开始加入了新的指令集,结合 Intel Multi-Buffer 技术,实现对密码算法的 SIMD 优化方案。

3.1 Intel Multi-Buffer 技术

Intel Multi-buffer 基本原理就是使用 CPU 的 SIMD 机制,通过 AVX-512 指令集并行处理数据,来提升RSA/ECDSA算法性能。

  • SIMD (Single Instruction Multiple Data) 即单指令流多数据流,是一种采用一个控制器来控制多个处理器,同时对一组数据(又称“数据向量”)中的每一个分别执行相同的操作从而实现空间上的并行性的技术。简单来说就是一个指令能够同时处理多个数据。
  • Multi-Buffer 技术基于 SIMD AVX-512 指令集,通过队列和批量提交策略,结合 OpenSSL 的异步能力,每次可以最多并行处理8个密码算法操作。

Intel 的 Multi-Buffer 方案,实际上是对应 Intel 两个开源工程( Multi Buffer 技术实现的通用密码算法底层lib库),集成在 QAT Engine 里,从而实现软件加速。

图3. Intel Engine集成了基于Multi-Buffer技术的密码算法lib
图3. Intel Engine集成了基于Multi-Buffer技术的密码算法lib

1、IP SEC lib

2、IPP CRYPTO lib

简而言之,QAT 的软件加速的本质就是通过 AVX-512 指令集进行并行处理优化,针对并发场景性能有显著提升(下文有针对 Multi-Buffer 优化场景的性能测试)。

四、 硬件加速:采用QAT硬件加速卡卸载

除了通过 Multi-Buffer 技术进行软件加速外,QAT Engine 还支持 QAT 硬件加速卡,通过将密码算法的计算卸载(OffLoad)到硬件加速卡,实现性能加速。

硬件加速核心是将 TLS 中的非对称加解密操作剥离出来,放到硬件加速卡里计算,即解放了 CPU,同时专用的硬件加速卡也提供了更高的加解密性能,这是典型的硬件 OffLoad 技术方案。

下图为典型的 Nginx+ Intel QAT Software Stack + QAT 硬件加速卡的典型应用场景:

图4.  nginx+ Intel QAT Software Stack + QAT硬件加速卡的典型应用场景
图4.  Nginx+ Intel QAT Software Stack + QAT 硬件加速卡的典型应用场景(图片来源)(本站链接)

这个典型应用场景包括四个部分:

  1. Nginx (Async Mode):  Intel 基于官方 Nginx(Version 1.18)提供了 Patch,支持 Nginx 工作在 OpenSSL 的异步模式。Patch开源在:https://github.com/intel/asynch_mode_nginx
  2. OpenSSL(支持Async Mode):  OpenSSL-1.1.1 新增了 async mode 特性,应用层软件可以通过标准的 OpenSSL 接口,实现异步调用,提升性能。
  3. QAT Engine:   OpenSSL Engine 插件。向下和 QAT API 交互,将处理请求提交给硬件。详见项目开源地址: https://github.com/intel/QAT_Engine
  4. QAT Driver:QAT 加速卡的驱动程序。分为用户态和内核态两个部分。用户态的 LIB 库提供 QAT API,内核态的driver则直接和 QAT 硬件加速卡打交道。

Intel QAT 依赖了 OpenSSL 的两个特性 OpenSSL Async Mode 和 OpenSSL Engine:

  • OpenSSL Async Mode 能够在 async_job 执行过程中,在等待加速卡结果的时候,将 CPU 让出去;如果没有开启 async 模式,调用 OpenSSL 函数会阻塞,CPU 会阻塞等待。
  • OpenSSL Engine 则是提供了自定义注册加解密的方法,可以不使用 OpenSSL 自带的加解密库,指定调用第三方的加解密库。

基于两个特性,应用程序的加解密操作只需要保持使用原来相同 OpenSSL API,只需要做异步模式的兼容。另外,可以在调用 OpenSSL 的 API 时,指定到 Engine QAT上就行,不需要做任何额外的修改,就可以使用 QAT 卡进行加解密加速。

4.1 OpenSSL 的Async Mode特性

通过上面的介绍,我们可以看到 QAT 卡的本质是让一部分原本由 CPU 进行的计算转移到 QAT 卡上进行,因此提高 QAT 的利用率,降低 CPU 的切换开销和等待时间是性能最大化的核心工作。

OpenSSL 未启用异步ASYNC模式时,OpenSSL 调用是同步阻塞的,直到QAT_Engine返回结果。如下图的同步模式,在并发处理执行流的场景,大量CPU处于空闲等待的状态(图中虚线表示CPU处于空闲状态),无法有效地利用CPU。

图5. QAT_Engine + OpenSSL 同步模式
图5. QAT_Engine + OpenSSL 同步模式(图片来源)(本站链接)

OpenSSL 开启异步 ASYNC 模式后,OpenSSL 调用是非阻塞的。如下图的异步模式,OpenSSL 的调用不需要等待QAT_engine的处理完成,可以有效地利用 CPU,提高 QAT 的利用率,提升并发处理性能。

图6. QAT_Engine + OpenSSL 异步模式
图6. QAT_Engine + OpenSSL 异步模式(图片来源)(本站链接)

通过 OpenSSL 的同步和异步模式的对比,可以看到 OpenSSL-1.1.1 新增的异步 Async 特性,支持了异步非阻塞调用,提高了 QAT 的利用率,可以显著提升加解密性能。

4.2 QAT Engine ASYNC运行流程

接下来还有一个问题,CPU 如何知道 QAT 卡完成了计算呢? 

Async模块为了达到并行的目的,在单线程中实现了协程(async job)。加解密操作抽象为job,多个job同时运行,使用协程进行调度。

在async job执行的过程中,当计算操作提交给QAT卡后,CPU 可以把当前任务暂停,切换上下文(保存/恢复栈,寄存器等)返回给用户态。

用户态需要主动去poll这个async job的状态,是否是ASYNC_FINISHED状态。如果是,说明之前的任务已经完成,则可以继续后面的操作(取回加密/解密结果)。

注:QAT Engine 通过轮询来获取QAT卡的计算状态,基本原理是启动一个线程,不停的调用qatdriver的polling api,轮询获取qat的计算状态,得到相应结果后,写入eventfd,唤醒async job。

图7. QAT engine ASYNC运行流程
图7. QAT engine ASYNC运行流程(图片来源)(本站链接)

如上图所示,QAT Engine Async的基本流程为:

  1. 主 job 调用 SSL_accept,等待 TLS 客户端发起 TLS handshake。
  2. SSL 内部组织了一个状态机,将握手,读写等操作抽象为两个job,ssl_io_intern(读写), ssl_do_handshake_intern(握手), 统一通过api ASYNC_start_job()进行job调度。这里启动了一个握手的job协程。
  3. 握手job执行 RSA_sign 签名操作时,将sign算法卸载到硬件上计算。调用 ASYNC_pause_job()  切回主job, 并将job状态设置为 ASYNC_PAUSE ,这个时候 CPU 会交还给主 job 进行其它计算工作,同时 QAT 并行的进行自己的计算。
  4. 主job通过SSL_waiting_for_async() 接口获得的一个eventfd,并epoll这个eventfd。当 QAT 卡计算完成,会执行回调写入eventfd,通知主job计算已完成。
  5. 主job切换回握手job,握手job的完成剩余流程后,再调用ASYNC_pause_job()切换主job,并将job状态设置为ASYNC_FINISH,结束协程完成握手动作。

五、QAT 性能评测

通过上面的介绍,我们了解了 QAT 技术方案的基本原理,下面我们看下 QAT 的实际加速效果。

QAT Multi-Buffer 加速方案,依赖的 OpenSSL、QAT Engine、ipp-crypto、 Intel-ipsec-mb 软件栈都是开源项目,我们可以方便的使用 OpenSSL Speed 原生加解密算法对 Multi-Buffer 方案进行性能评估。

硬件环境

  • Intel(R) Xeon(R) Platinum 8369B CPU @ 2.70GHz
  • CPU(s): 8

软件环境

  • Linux Kernel: 4.19.91-24.1.al7.x86_64
  • OpenSSL 1.1.1g
  • gcc (GCC) 8.3.0
  • cmake version 3.15.5
  • NASM version 2.15.05
  • GNU Binutils 2.32
  • 安装 QAT Engine
  • 安装 ipp-crypto、 Intel-ipsec-mb 开源lib库

测试数据

  • numactl -C 0 ./openssl speed rsa2048
  • numactl -C 0 ./openssl speed -engine qatengine -async_jobs 8 rsa2048

  • numactl -C 0 ./openssl speed ecdhp384
  • numactl -C 0 ./openssl speed -engine qatengine -async_jobs 8 ecdhp384

  • numactl -C 0 ./openssl speed aes-256-cbc
  • numactl -C 0 ./openssl speed -engine qatengine -async_jobs 8 aes-256-cbc

TLS 握手阶段的签名和秘钥交换算法

  • RSA 2048 Sign/Verify  提升 4.9/2.9倍
  • ECDH Key Exchange 提升 12倍

对称加解密算法

  • AES-256-CBC 性能持平

根据性能测试结果,QAT 的加速优势在于 TLS 握手阶段的签名和秘钥交换算法,适合频繁进行 TLS 建连的应用场景,比如:Nginx 网关、长连接网关等。

六、总结

本文介绍了 Intel QAT 技术方案,并讨论了方案提供的 Multi-Buffer 软件加速以及 QAT 硬件加速两种方式。同时,通过性能评估测试,我们可以看到 QAT 技术对 TLS 握手阶段的加解密算法有显著的性能提升。

最后,我们讨论一下 Intel QAT 技术的优缺点和应用场景:

6.1 优点和缺点

主要的优点

  • 高性能:可以显著提高计算密集型任务的性能,减少 CPU 的负载,提高系统吞吐量和响应速度。
  • 低功耗:可以将计算密集型任务卸载到专用硬件上,降低系统功耗,提高能效比。

主要的缺点

  • 成本较高:需要额外的硬件支持,增加了系统的成本。
  • 应用范围受限:主要适用于计算密集型任务,对于其他类型的任务可能没有显著的性能提升。
  • 不支持所有处理器:只支持 Intel 特定系列的处理器,需要特定的硬件和软件支持。
  • 改造成本高:需要对应用程序进行 QAT 异步化改造,需要一定的学习成本和技术支持。
6.2 应用场景

除了加解密算法之外,Intel QAT 还支持压缩和解压缩、随机数生成、数字签名、视频编解码等算法。Intel QAT 主要可以用于以下场景:

  • 接入网关:比如 Nginx 网关、长连接网关。QAT 可以加速 TLS 协议处理,提升网关的性能
  • 虚拟私人网络(VPN):QAT 可以加速 VPN 流量的加密和解密过程,提高吞吐量,减少延迟
  • 存储加速:QAT 可以加速数据压缩和解压缩,减少需要传输和存储的数据量
  • 视频编解码:QAT 可以加速视频编解码算法,提高视频处理的效率和质量

总的来说,Intel QAT 可以将计算密集型任务从 CPU 中分离出来,显著提高系统的性能和能效比,可以广泛应用于计算密集型任务的加速,包括网络安全、数据处理、云计算、存储加速、视频处理等多个领域。

参考资料


蓝牙BLE: GATT Profile 简介(GATT 与 GAP)

一. 引言

现在低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。

二. GAP

详细介绍 GATT 之前,需要了解 GAP(Generic Access Profile),它在用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。

2.1 设备角色

GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central)。

外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。
中心设备:中心设备相对比较强大,用来连接其他外围设备。例如手机等。

2.2 广播数据

在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。(广播的数据格式我将另外专门写一个篇博客来讲。)

2.3 广播流程

GAP 的广播工作流程如下图所示。

从图中我们可以清晰看出广播数据和扫描回复数据是怎么工作的。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

2.4 广播的网络拓扑结构

大部分情况下,外设通过广播自己来让中心设备发现自己,并建立GATT连接,从而进行更多的数据交换。也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。因为基于GATT连接的方式的,只能是一个外设连接一个中心设备。使用广播这种方式最典型的应用就是苹果的iBeacon。广播工作模式下的网络拓扑图如下:

三. GATT

GATT 的全名是 Generic Attribute Profile(姑且翻译成:普通属性协议),它定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic遗迹对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。

一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的 GAP 协议。这里需要说明的是,GATT 连接,必需先经过 GAP 协议。实际上,我们在 Android 开发中,可以直接使用设备的 MAC 地址,发起连接,可以不经过扫描的步骤。这并不意味不需要经过 GAP,实际上在芯片级别已经给你做好了,蓝牙芯片发起连接,总是先扫描设备,扫描到了才会发起连接。

GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。

中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

3.1 GATT连接的网络拓扑

下图展示了 GTT 连接网络拓扑结构。这里很清楚的显示,一个外设只能连接一个中心设备,而一个中心设备可以连接多个外设。

 一旦建立了连接,通信就是双向的了,对比前面的GAP广播的网络塔扑,GAP通信是单向的。如果你要让两个外围设备能通信,就只能通过中心设备中转

3.2 GATT 通信事务

GATT通信的双方是C/S关系。外设作为GATT服务端(Server),它维持了ATT的查找表以及service和characteristic的定义。中心设备是GATT客户端(Client),他向Server发起请求。需要注意的是,所有的通信事件,都是由客户端(也叫主设备,Master)发起,并且接收服务端(也叫从设备,Slava)的响应。
一旦连接建立,外设将会给中心设备建议一个 连接间隔(Connection Interval),这样,中心设备就会在每个连接间隔尝试去重新连接,检查是否有新的数据。但是,这个连接间隔只是一个建议,你的中心设备可能并不会严格按照这个间隔来执行,例如你的中心设备正在忙于连接其他的外设,或者中心设备资源太忙。

下图展示一个外设(GATT服务端)和中心设备(GATT客户端)之间的数据交流流程,可以看到的是,每次都是主设备发起请求:

3.3 GATT 结构

GATT事务是建立在嵌套的Profiles,Services和Characteristics之上的,如下如所示:

  • Profile Profile并不是实际存在于BLE外设上的,它只是一个被Bluetooth SIG或者外设设计者预先定义的Service的集合。例如心率Profile(Heart Rate Profile)就是结合了Heart Rate Service和Device Information Sercvice。所有官方通过GATT Profile的列表可以从这里找到。

  • Service Service是把数据分成一个个的独立逻辑项,它包含一个或者多个Characteristic。每个Service有一个UUID唯一标识。UUID有16bit的,或者128bit的。16bit的UUID是官方通过认证的,需要花钱购买,128bit是自定义的,这个就可以自己随便设置。

    官方通过了一些标准Service,完整列表在这里。以Heart Rate Service为例,可以看到它的官方通过16bitUUID是 0x180D,包含3个Characteristic:Heart Rate Measurement,Body Sensor Location和Heart Control Point,并且定义了只有一个第一个必须的,它是可选实现的。

  • Characteristic 在GATT事务中的最低界别的是Characteristic,Characteristic是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的X/Y/Z三轴值。与Service类似,每个Characteristic用16bit或者128bit的UUID唯一标识。你可以免费使用Bluetooth SIG官方定义的标准Characteristic,使用官方定义的,可以确保BLE的软件和硬件能相互理解。当然,你可以自定义Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。

    举个例子,Heart Rate Measurement Characteristic,这是上面提到的Heart Rate Service必需实现的Characteristic,它的UUID是 0x2A37。它的数据结构是,开始8bit定义心率数据格式(是UINT8还是UINT16?),接下来就是对应格式的实际心率数据。

    实际上,和BLE外设打交道,主要是通过Characteristic。你可以从Characteristic读取数据,也可以往Characteristic写数据。这样就实现了双向的通信。所以你可以自己实现一个类似串口(UART)的service,这个Service中包含两个Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。

更多内容

参考链接


蓝牙BLE: GATT Profile 简介(GATT 与 GAP)

Coping with the TCP TIME-WAIT state on busy Linux servers

TL;DR

Do not enable net.ipv4.tcp_tw_recycle—it doesn’t even exist anymore since Linux 4.12. Most of the time, TIME-WAIT sockets are harmless. Otherwise, jump to the summary for the recommended solutions.

The Linux kernel documentation is not very helpful about what net.ipv4.tcp_tw_recycle and net.ipv4.tcp_tw_reuse do. This lack of documentation opens the path to numerous tuning guides advising to set both these settings to 1 to reduce the number of entries in the TIME-WAIT state. However, as stated by the tcp(7) manual page, the net.ipv4.tcp_tw_recycle option is quite problematic for public-facing servers as it won’t handle connections from two different computers behind the same NAT device, which is a problem hard to detect and waiting to bite you:

Enable fast recycling of TIME-WAIT sockets. Enabling this option is not recommended since this causes problems when wrking with NAT (Network Address Translation).

I will provide here a more detailed explanation of how to properly handle the TIME-WAIT state. Also, keep in mind we are looking at the TCP stack of Linux. This is completely unrelated to Netfilter connection tracking which may be tweaked in other ways.1

继续阅读Coping with the TCP TIME-WAIT state on busy Linux servers

WireGuard 教程:WireGuard 的工作原理

本文翻译自:https://github.com/pirate/wireguard-docs

WireGuard 是由 Jason Donenfeld 等人用 C 语言编写的一个开源 VPN 协议,被视为下一代 VPN 协议,旨在解决许多困扰 IPSec/IKEv2OpenVPN 或 L2TP 等其他 VPN 协议的问题。它与 Tinc 和 MeshBird 等现代 VPN 产品有一些相似之处,即加密技术先进、配置简单。从 2020 年 1 月开始,它已经并入了 Linux 内核的 5.6 版本,这意味着大多数 Linux 发行版的用户将拥有一个开箱即用的 WireGuard。

无论你是想破墙而出,还是想在服务器之间组网,WireGuard 都不会让你失望,它就是组网的『乐高积木』,就像 ZFS 是构建文件系统的『乐高积木』一样。

继续阅读WireGuard 教程:WireGuard 的工作原理

UWB中TOF测距法的公式推导

UWB中TOF测距法的公式推导

UWB常用测距方法有两种:飞行时间测距法(TOF)和到达时间差法(TDOA)。这里说一下TOF。

TOF

飞行时间法(Time of Flight,TOF)是一种双向测距技术,它通过测量UWB信号在基站与标签之间往返的飞行时间来计算距离。根据数学关系,一点到已知点的距离为常数,那么这点一定在以已知点为圆心,以该常数为半径的圆上。有两个已知点,就有两个交点。以三个已知点和距离作三个圆,他们交于同一个点,该点就是标签的位置。

TOF定位方式需要基站和标签往返通信,因此就造成了TOF功耗大大提高,续航时间相对较短.

TOF又分为两种:单边双向测距和双边双向测距。

单边双向测距

单边双向测距(Single-sided Two-way Ranging: SS-TWR)是对单个往返消息时间上的简单测量,设备A主动发送数据到设备B,设备B返回数据响应设备A。如下图所示:

单边双向测距的流程是这样的:设备A(Device A)主动发送(TX)数据,同时记录发送时间戳,设备B(Device B)接收到之后记录接收时间戳;延时 $T_{reply}$ 之后,设备B发送数据,同时记录发送时间戳,设备A接收数据,同时记录接收时间戳。

所以可以拿到两个时间差数据,设备A的时间差 $T_{round}$ 和设备B的时间差 $T_{reply}$ ,最终得到无线信号的飞行时间 $T_{prop}$ 如下:

$ T_{prop} = \frac{1}{2}(T_{round}-T_{reply}) $

两个差值时间都是基于本地的时钟计算得到的,本地时钟误差可以抵消,但是不同设备之间会存在微小的时钟偏移,假设设备A和B的时钟偏移分别为 $e_A$ 和 $e_B$ ,则飞行时间测量值为:

$ \hat{T}_{prop} = \frac{1}{2}[T_{round}(1+e_A)-T_{reply}(1+e_B)] $

于是测距误差如下:

$ Error = \hat{T}_{prop} - T_{prop} = \frac{1}{2}(T_{round}\cdot e_A-T_{reply}\cdot e_B) = \frac{1}{2}T_{reply}(e_A-e_B) + T_{prop}\cdot e_A $

因为 $ T_{reply} >> T_{prop} $ , 所以可以忽略后一项,得到

$ Error = \hat{T}_{prop} - T_{prop} \approx \frac{1}{2}T_{reply}(e_A-e_B) $

由此可以看出,随着 $T_{reply} $ 和时钟偏移的增加,会增加飞行时间的误差,从而使得测距不准确。因此单边双向测距(SS-TWR)并不常用,但对于特定的应用,如果对于精度要求不是很高,但是需要更短的测距时间可以采用。注意 $ T_{reply} $ 不仅仅是设备B接收到发送的时间,也包括装载数据和发送数据耗费的时间(UWB除了支持定位之外,也可以传输数据,标准可以装载128字节,扩展模式可以装载1024字节数据)。

双边双向测距

双边双向测距(Double-sided Two-way Ranging)是单边双向测距的一种扩展测距方法,记录了两个往返的时间戳,最后得到飞行时间,虽然增加了响应的时间,但会降低测距误差。

双边双向测距分为两次测距,设备A主动发起第一次测距消息,设备B响应,当设备A收到数据之后,再返回数据,最终可以得到如下四个时间差:$ T_{round1} $ 、$ T_{reply1} $ 、$ T_{round2} $ 、$ T_{reply2} $ ,如下图所示:

双边双向测距飞行时间计算方法:

由单边双向测距方法可得

$ T_{prop} = \frac{1}{2}(T_{round1}-T_{reply1}) $

$ T_{prop} = \frac{1}{2}(T_{round2}-T_{reply2}) $

所以

$ \begin{flalign}T_{round1} \times T_{round2} = (2T_{prop}+T_{reply1})(2T_{prop}+T_{reply2}) = 4T_{prop}^2+2T_{prop}(T_{reply1}+T_{reply2})+T_{reply1}T_{reply2} \end{flalign}$

$ \begin{flalign} T_{round1} \times T_{round2} - T_{reply1}T_{reply2} = 4T_{prop}^2+2T_{prop}(T_{reply1}+T_{reply2}) \\ = T_{prop}(4T_{prop}+2T_{reply1}+2T_{reply2}) \\ = T_{prop}(T_{round1} + T_{round2} + T_{reply1} + T_{reply2}) \end{flalign}$

于是可以得到如下计算 $ T_{prop} $ 的公式:

$ T_{prop} = \frac{T_{round1} \times T_{round2} - T_{reply1} \times T_{reply2}}{T_{round1} + T_{round2} + T_{reply1} + T_{reply2}} $

以上测距的机制是非对称的测距方法,因为他们对于响应时间不要求是相同的。下面分析双边双向测距飞行时间的误差:

$ \begin{flalign} \hat{T}_{prop} = \frac{T_{round1}(1+e_A) \times T_{round2}(1+e_B) - T_{reply1}(1+e_B) \times T_{reply2}(1+e_A)}{T_{round1}(1+e_A) + T_{round2}(1+e_B) + T_{reply1}(1+e_B) + T_{reply2}(1+e_A)} \\ = \frac{(4T_{prop}^2+2T_{prop}(T_{reply1}+T_{reply2}))(1+e_A)(1+e_B)} {4T_{prop}+2(T_{reply1}+T_{reply2})+(2T_{prop}+T_{reply1}+T_{reply2})(e_A+e_B)}\\ =\frac{2(1+e_A)(1+e_B)}{(1+e_A)+(1+e_B)}T_{prop} \end{flalign}$

于是

$ T_{prop} = \frac{(1+e_A)+(1+e_B)}{2(1+e_A)(1+e_B)}\hat{T}_{prop} $

$ \begin{flalign} Error = \hat{T}_{prop} - T_{prop} = \left(1-\frac{(1+e_A)+(1+e_B)}{2(1+e_A)(1+e_B)}\right)\hat{T}_{prop} \\ = \frac{e_A+e_B+2e_A e_B}{2(1+e_A)(1+e_B)}\hat{T}_{prop} \end{flalign} $

因为 $ e_A <<1 $,$ e_B<<1$,略去高次项,可得

$ Error \approx \frac{e_A+e_B}{2}\hat{T}_{prop} $

由此可以看出,误差仅与钟漂和飞行时间有关。

假设一个使用场景:使用20ppm的晶体,UWB的工作距离范围为300m,则无线信号空中飞行时间大概为$1 \mu s$,误差为$ 20 \times 10^{-6} \times 1 \times 10^{-6} = 20 \times 10^{-12} = 20ps $时钟误差是在ps级别的,换算为距离之后仅为6mm。

注意:响应时间是不需要相等的,也就是 $ T_{reply1} $ 不一定要等于 $ T_{reply2} $ ,这样对于MCU系统的处理带来了很多便利。

若双边双向测距方法响应时间对称,也就是 $ T_{reply1} $ 和 $ T_{reply2} $ 相等,飞行时间计算方法如下:

$ T_{prop} = \frac{1}{4}(T_{round1}-T_{reply1}+T_{round2}-T_{reply2}) $

这种方法比较简单,只是需要一些时间戳做加减法,但其难点在于,怎么保证 $T_{reply1} $ 和 $ T_{reply2} $ 是相等的。

此种方法的误差分析如下:

$ \begin{flalign} \hat{T}_{prop} = \frac{1}{4}\left[T_{round1}(1+e_A)-T_{reply1}(1+e_B)+T_{round2}(1+e_B)-T_{reply2}(1+e_A)\right] \end{flalign}$

$ \begin{flalign} Error = \hat{T}_{prop} - T_{prop} = \frac{1}{4}\left[(T_{round1}-T_{reply2})e_A +(T_{round2}-T_{reply1})e_B\right] \\ =\frac{1}{4}\left[2(e_A+e_B)T_{prop} +(e_A-e_B)(T_{reply1}-T_{reply2})\right] \end{flalign} $

因为 $ T_{reply1}-T_{reply2} >> T_{prop} $ ,可忽略$ T_{prop} $项,从而得到

$ Error \approx \frac{1}{4}(e_A-e_B)(T_{reply1}-T_{reply2})$

可见此误差与响应时间差成正比。

参考链接


智能硬件Nvidia Jetson Nano B01

产品参数

GPU 128-core Maxwell
CPU Quad-core ARM A57 @ 1.43 GHz
内存 4 GB 64-bit LPDDR4 25.6 GB/s
存储 micro SD 卡 
视频编码 4K @ 30   |   4x 1080p @ 30   |   9x 720p @ 30
(H.264/H.265)
视频解码 4K @ 60   |   2x 4K @ 30   |   8x 1080p @ 30   |   18x 720p @ 30
(H.264/H.265)
摄像头 2x MIPI CSI-2 D-PHY lanes
联网 千兆以太网,M.2 Key E 接口外扩 (可外接: AC8265 双模网卡 )
显示 HDMI 和 DP 显示接口
USB 4x USB 3.0,USB 2.0 Micro-B
扩展接口 GPIO,I2C,I2S,SPI,UART
其他 260-pin 连接器

Jetson Nano系统安装

1、JetPack介绍

JetPack SDK包括最新的Linux驱动程序包(L4T),带有Linux操作系统和CUDA-X加速库,以及用于深度学习、计算机视觉、加速计算和多媒体的API。 它还包括用于主机和开发人员套件的示例、文档和开发人员工具,并支持更高级别的SDK,例如用于流式视频分析的DeepStream和用于机器人的Isaac。

2、JetPack 4.4

JetPack 目前最新版本是4.4,支持Vulkan 1.2、TensorRT 7.1.3 、cuDNN 8.0、CUDA 10.2 等。

3、下载和安装

  • 下载 Jetson Nano镜像,镜像中包含提供引导加载程序、Ubuntu18.04、必要的固件、NVIDIA驱动程序、示例文件系统等。
  • 使用 Etcher 或者 Raspberry Pi Imager 将镜像烧录到SD卡(建议至少32G)中。

设置VNC服务

1.执行更新

2.安装vino服务端

这个vino服务端我使用的镜像文件是安装好了的,但是古早版的镜像文件可能没有,所以可以执行下代码看看是否有安装。

3.开启VNC 服务

4.配置VNC服务

设置开机自启动

1.创建VNC自动启动文件

创建文件夹,然后创建一个自动启动文件

2.添加以下内容到vino-server.desktop文件中

这个时候,虽说是自动启动了,但是只有进入桌面后才自动启动服务,所以需要取消登录密码,启动就进入桌面。

参考链接


关于HTTP请求走私的小记

0x00 写在前面

之前一次线上赛,遇到一道Web题,涉及了HTTP请求走私。由于之前未学习过,从而我展开了HTTP请求走私的学习之旅。

0x01 HTTP请求走私是什么

HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列的方式的技术。使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。

0x02 为什么会产生HTTP请求走私

请求走私漏洞成因

前端服务器(CDN)和后端服务器接收数据不同步,引起对客户端传入的数据理解不一致,从而导致漏洞的产生。

大多数HTTP请求走私漏洞的出现是因为HTTP规范提供了两种不同的方法来指定请求的结束位置:Content-Length标头和Transfer-Encoding标头。
同时使用两种不同的方法时,Content-Length无效。当使用多个服务器时,对客户端传入的数据理解不一致时,就会出现有些服务器认为Content-Length的长度有效,有些以Transfer-Encoding有效。而一般情况下,反向代理服务器与后端的源站服务器之间,会重用TCP链接。这样超出的长度就会拼接到下一次请求进行请求,从而导致HTTP请求走私漏洞。

RFC2616规范

如果接收的消息同时包含传输编码头字段(Transfer-Encoding)和内容长度头(Content-Length)字段,则必须忽略后者。

由于规范默许可以使用Transfer-EncodingContent-Length处理请求,因此很少有服务器拒绝此类请求。每当我们找到一种方法,将Transfer-Encoding隐藏在服务端的一个chain中时,它将会回退到使用Content-Length去发送请求。

走私攻击实现

当向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,代理服务器可能认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。

扩展:为什么会出现多次请求

这与最为广泛的HTTP 1.1的协议特性——Keep-Alive&Pipeline有关。

HTTP1.0之前的协议设计中,客户端每进行一次HTTP请求,需要同服务器建立一个TCP链接。

而现代的Web页面是由多种资源组成的,要获取一个网页的内容,不仅要请求HTML文档,还有JS、CSS、图片等各种资源,如果按照之前的协议设计,就会导致HTTP服务器的负载开销增大。于是在HTTP1.1中,增加了Keep-AlivePipeline这两个特性。

Keep-Alive:在HTTP请求中增加一个特殊的请求头Connection: Keep-Alive,告诉服务器,接收完这次HTTP请求后,不要关闭TCP链接,后面对相同目标服务器的HTTP请求,重用这一个TCP链接。这样只需要进行一次TCP握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。这个特性在HTTP1.1中默认开启的。

Pipeline(http管线化):http管线化是一项实现了多个http请求但不需要等待响应就能够写进同一个socket的技术,仅有http1.1规范支持http管线化。在这里,客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。

现在,浏览器默认不启用Pipeline的,但是一般的服务器都提供了对Pipleline的支持。

继续阅读关于HTTP请求走私的小记

Transfer-Encoding 的作用

通过HTTP传送数据时,有些时候并不能事先确定body的长度,因此无法得到Content-Length的值, 就不能在header中指定Content-Length了,造成的最直接的影响就是:接收方无法通过Content-Length得到报文体的长度, 那怎么判断发送方发送完毕了呢?HTTP 1.1协议在header中引入了Transfer-Encoding,当其值为chunked时, 表明采用chunked编码方式来进行报文体的传输

HTTP 1.1中有两个实体头(Entity-Header)直接与编码相关,分别为Content-Encoding和Transfer-Encoding.
先说Content-Encoding, 该头表示实体已经采用了的编码方式.Content-Encoding是请求URL对应实体(Entity)本身的一部分.比如请求URL为http://host/image.png.gz时,可能会得到的Content-Encoding为gzip.Content-Encoding的值是不区分大小写的,目前HTTP1.1标准中已包括的有gzip/compress/deflate/identity等.
与Content-Encoding头对应,HTTP请求中包含了一个Accept-Encoding头,该头用来说明用户代理(User-Agent,一般也就是浏览器)能接受哪些类型的编码. 如果HTTP请求中不存在该头,服务器可以认为用户代理能接受任何编码类型.

接下来重点描述Transfer-Encoding, 该头表示为了达到安全传输或者数据压缩等目的而对实体进行的编码. Transfer-Encoding与Content-Encoding的不同之处在于:
1, Transfer-Encoding只是在传输过程中才有的,并非请求URL对应实体的本身特性.
2, Transfer-Encoding是一个"跳到跳"头,而Content-Encoding是"端到端"头.
该头的用途举例如,请求URL为http://host/abc.txt,服务器发送数据时认为该文件可用gzip方式压缩以节省带宽,接收端看到Transfer-Encoding为gzip首先进行解码然后才能得到请求实体.
此外多个编码可能同时对同一实体使用,所以Transfer-Encoding头中编码顺序相当重要,它代表了解码的顺序过程.同样,Transfer-Encoding的值也是不区分大小写的,目前HTTP1.1标准中已包括的有gzip/compress/deflate/identity/chunked等.
Transfer-Encoding中有一类特定编码:chunked编码.该编码将实体分块传送并逐块标明长度,直到长度为0块表示传输结束, 这在实体长度未知时特别有用(比如由数据库动态产生的数据). HTTP1.1标准规定,只要使用了Transfer-Encoding的地方就必须使用chunked编码,并且chunked必须为最后一层编码.任何HTTP 1.1应用都必须能处理chunked编码.
与Transfer-Encoding对应的请求头为TE,它主要表示请求发起者愿意接收的Transfer-Encoding类型. 如果TE为空或者不存在,则表示唯一能接受的类型为chunked.
其他与Transfer-Encoding相关的头还包括Trailer,它与chunked编码相关,就不细述了.

顾名思义,Content-Length表示传输的实体长度,以字节为单位(在请求方法为HEAD时表示会要发送的长度,但并不实际发送.).Content-Length受Transfer-Encoding影响很大,只要Transfer-Encoding不为identity,则实际传输长度由编码中的chunked决定,Content-Length即使存在也被忽略.

关于HTTP Message Body的长度
在HTTP中有消息体(Message body)和实体(Entity body)之分,简单说来在没有Transfer-Encoding作用时,消息体就是实体,而应用了Transfer-Encoding后,消息体就是编码后的实体,如下:

具体详细的 RFC 7230 说明如下:

参考链接


Transfer-Encoding 的作用

RS485 软件流控 及 数据完整性确保

首先 串口 的流控大家应该都有所了解,通常是硬件 CTS/RTS 或软件 XON/XOFF 这两种流控方式,然而因为 RS485 是总线形式,所以传统的方法都不再适用。

有人会觉得奇怪,貌似从来没有考虑过 RS485 流控的问题,没错,传统 RS485 都是一收一发,用不着考虑流控,然而这种一收一发的效率比较低,譬如在 IoT 火热的今天,如果用 RS485 来传输网络数据,那么传统的做法就很低效了。

然后,针对数据完整性确保的问题,很多同行都没有留意到一个细节问题,他们通常判断是否收到回复 OK 的数据包,如果没收到数据包就超时重发一次。 这种做法大多情况都没有问题,但是某些场景,譬如发送一个命令让滑轨左移 10mm, 滑轨成功接收命令并返回 OK, 然而主机因为干扰等各种问题没有收到滑轨的回复,那么重发命令就会导致滑轨错误左移 20mm. 当然你可以说目前用到的设备都是绝对位置控制,不会有影响,但万一哪天新做一个设备,到那时再改协议,难道就不考虑兼容自己以往的产品了吗?

当然还是有很多朋友有注意到这个问题,本文使用的解决方法原理上跟这些朋友也是相同的。

我接下来提出的方案最大的亮点是共用同一套机制,同时解决了流控、数据完整性确保、大数据分包等功能,而且比较高效和简单。

同样,最底层的协议我们依然使用 CDBUS, 因为它比较简单,又支持硬件增强(可以主动避让冲突,实现多主机、对等通讯、主动上报数据等功能),能最大程度体现出本文方法的性能优势。

你可能没有听过 CDBUS 这个名字,但你可能曾经或正在使用相似的协议,它的组成包含 3 个部分: - 3 个字节的头:「源地址,目标地址,用户数据长度」 - 0~255 字节的用户数据(因为数据长度用 1 个字节表示) - 2 个字节的 CRC 校验,涵盖整个数据包,校验算法同 ModBus.

数据包与数据包之间要有一定的空闲时间,来隔开不同的数据包,详细请参见 CDBUS 的协议定义: https://github.com/dukelec/cdbus_ip

譬如地址 0x00 为主机,0x01 为 1 号从机,那么主机发送两个字节数据 0x10 0x11 给 1 号从机的完整数据为:

然后 1 号从机回覆单个 0x10 给主机:

然而 CDBUS 只是最底层的协议,接下来我们要定义上述用户数据的格式,最简单常用的方式就是首字节为命令号,然后后面跟可选命令参数; 回覆数据第一个字节通常为状态,然后是返回的数据。

这种方式完善之后也有一个名字,叫 CDNET, 它的定义在:https://github.com/dukelec/cdnet
(本文的内容这个连接都有包含,但本文会更加通俗的讲解一下关键细节。)

CDNET 协议有 3 个级别,由首字节的高两位决定:

实际使用根据情况自由选择某一个或某几个来用就好。

Level 0 格式
请求
首字节:

CDNET 的端口号可以看做类似电脑的 UDP 端口,也可以看做是一个命令号。

第二字节及其后:命令参数

回复
首字节:

例如: 回复 [0x40, 0x0c] 和回复 [0x6c] 是相同的意思。

首字节的用户数据(如果有)、第二个字节及其后:回复的状态 和/或 数据。

Level 1 格式
首字节:

MULTI_NET & MULTICAST

这个与本文主题无关,就不展开了。

SEQUENCE
0: 无序列号;
1: 追加 1 字节序列号 SEQ_NUM, 这个是重点,稍后会主要说明。

PORT_SIZE:

注: - 默认端口通常定为 0xcdcd, 所以不用额外追加字节. - 追加的字节按顺序,先是 src_port 再是 dst_port.

Level 2 格式
首字节:

注: - 使用分包功能的时候必须同时选择使用 SEQUENCE. - 开始分包的时候 SEQ_NUM 不需要归零.

一般情况下,要求不高,使用最简单的 Level 0 格式就好了,如果命令比较多,那么就可以用 Level 1 格式,用不到的功能不用理会即可。

Level 1 没有大数据分包功能,因为通常 MCU 也用不到那么大的数据包,即使是烧录代码这种要传大数据的功能,也是可以在命令内部定义地址和数据长度的,譬如我的 STM32 总线代码升级的命令定义:

而 Level 2 譬如可以用来传 IPv4/v6 数据包,那么就不得不加入拆包的功能了。因为 Level 1 和 Level 2 的序列号部分是一样的,所以接下来就混在一起讲了。

CDBUS 协议将前 0~9 保留专用,10 及其后的用户可以随便用,保留的部分目前也就用了 4 个,而且也不是强制的,用户愿意实现就实现,不用或者自己想怎么用就怎么用也没问题。 上篇文章说了端口或命令 0x01 是用来查询设备信息的,命令 0x03 是用来设置地址的,还详细说了如何使用这两个端口来实现地址自动分配,剩下两个端口其中 0x02 是用来设置波特率的, 对于本文最关键的端口 0x00 是用于流控、完整性确保、大数据拆包的了,其定义如下:

Port 0
配合 Level 1 和 2 头中的 SEQUENCE 字段使用。
命令启用 SEQUENCE 后追加的对应字节 SEQ_NUM[6:0] 的低 7 位会每次自动加 1.
而 SEQ_NUM 的第 7 位用来指示接收方是否要报告状态。
Port 0 本身的命令不可以启用 SEQUENCE.

Port 0 命令定义:

实际示例:
(-> 和 <- 是端口层的数据流, >> 和 << 是 CDNET 数据包层面的数据流,不含最低层的 CDBUS 的部分)

效率提升的重点就在这里,我们可以自行选择多久回复一次,而不是每次都要回复状态,如果最后一次数据包没有标注需要回复,那么会引发超时,然后主动读一次目标的 SEQ_NUM 以做同步。 之所以引发超时,是因为所有发出的数据包都不能立刻释放,要等确认对方收到才会释放,以防需要出错重传。 因为有 SEQ_NUM 号,所以即使同一个命令重复发送,对方也会只执行一次。

流控的功能也包含在内,譬如发送方时刻只允许最多 6 个数据包没有释放,那么等收到回复,释放掉 3 个,再发送 3 个数据包,这样可以最大化的利用总线带宽。 而且万一有多方发送数据至同一个节点,发送方也可以因频繁超时,来动态降低最大允许 pending 的数据包数量。

再来说大包拆分,也是很简单,拆分包有 3 个标记,分别是起始、继续、结束,譬如一个大包拆开了 4 个小包,且如果当前 SEQ_NUM 为 23,那么这四个小包的 SEQ_NUM 和标记对应关系就是:

这样接收方也就很容易的把四个小包还原成原始的大包,万一出问题,也只是重新传输错掉或丢掉的包(及其后的包)。

为了简便,对于 CDNET 协议,并不是丢一个包就只重传一个包,其后传的包也需要重传,因为接收方只是简单判断序号,不对便拒绝接收,这么做是为了保持简单,毕竟错包、丢包的概率很低。

最后,想说的是,这篇文章的内容都是经过实践检验的,我有用来传输摄像头视频,DEMO 可以在这篇介绍文章中看到:https://github.com/dukelec/cdbus_doc/blob/master/intro_zh.md

协议的实现部分代码也是开源的,就是上面的 CDNET 连接,另外有一些使用 CDNET 的示例代码,譬如这个 STM32F103 的步进电机控制器:https://github.com/dukelec/stepper_motor_controller

当然,这些代码、库我也会进一步优化改善。

参考链接


RS485 软件流控 及 数据完整性确保

完整教程:设计一款小巧但强大的传感器

由IEEE制定的新型单对以太网(SPE)或10BASE-T1L物理层标准,为传输设备运行状况信息实施状态监测(CbM)应用提供了新的连接解决方案。SPE提供共享电源和高带宽数据架构,可通过低成本双线电缆在超过1000米的距离实现10 Mbps数据和电源的共享。

ADI公司设计了业界首款10BASE-T1L MAC-PHY(ADIN1110),这是一款集成MAC的单对以太网收发器。ADIN1110使用简单的SPI总线与嵌入式微控制器通信,从而可降低传感器的功耗并减少固件开发时间。

在本文中,您将了解如何设计一款体型小巧但功能强大的传感器,如图1所示。本文将介绍:

● 如何设计小型共享数据和电源通信接口

● 如何为传感器设计超低噪声电源

● 微控制器和软件架构选择

● 选择合适的MEMS振动传感器

● 集成数字硬件设计和机械外壳

● 电脑上的数据采集UI示例

继续阅读完整教程:设计一款小巧但强大的传感器