关于 k210 的 micropython 添加 ussl 模块,实现 https 访问支持的那些事。
2020年5月15日 更新
修复了一下网络层的接口,之前写得太累了疏忽了细节问题,导致 http/https 的请求没有正确处理 eof 无法直接退出,导致陷入多次超时从而访问时间很长,高达 30s ,现在仔细处理了一下 EOF 的问题,基本上 http get 就可以 0.2s,https get 就改参数先优化到 4s 一次了,https 主要还是双向通信的细节没处理好,明天起来接着 XD 。
现在 https 优化到 2s 了,基本符合预期。
起因
事情已经过去快一周了吧,继上次修复 maixpy k210 的 esp8285 at 通信后,突然遇到泽畔大大问,要不要做 ussl 的支持?
评估了一下各方的实现,想了一下自己也刚好在做网络层的优化和处理,况且 micropython 在 stm32 、 esp32 上的也有对应的实现,那就添加实现进去吧,选取了 mbedtls 版本的 ussl 模块,实现相关文件如下。
这里说一下 ussl 的工作机制。
首先建立在 micropython 的 network 架构下的 socket 模块,提供了关键的 steam->write 和 steam->read 基础接口,实际上就是继承一个抽象 steam 对象的接口。
因此 ussl 提供了 wrap_socket 用来提升 socket 的功能,从而支持 https 的访问。
我们看一下 micropython 的实例就知道了。
try:
import usocket as _socket
except:
import _socket
try:
import ussl as ssl
except:
import ssl
def main(use_stream=True):
s = _socket.socket()
ai = _socket.getaddrinfo("google.com", 443)
print("Address infos:", ai)
addr = ai[0][-1]
print("Connect address:", addr)
s.connect(addr)
s = ssl.wrap_socket(s)
print(s)
if use_stream:
# Both CPython and MicroPython SSLSocket objects support read() and
# write() methods.
s.write(b"GET / HTTP/1.0\r\n\r\n")
print(s.read(4096))
else:
# MicroPython SSLSocket objects implement only stream interface, not
# socket interface
s.send(b"GET / HTTP/1.0\r\n\r\n")
print(s.recv(4096))
s.close()
main()
实现的最终结果如下,不过目前的实测效果距离商业使用,保守来讲,还有很大的优化空间,主要在核心函数和配置方面要改善性能。
- esp32 的效果
- k210 的效果
实现细节
MaixPy k210 采用 components/micropython/CMakeLists.txt 来管理 micropython 的编译命令。
所以在不脱离主流的基础上,在 micropython-ulab 的配置后面继续添加如下配置。
if(1 OR CONFIG_MICROPY_SSL_MBEDTLS)
list(APPEND ADD_INCLUDE "${mpy_core_dir}/lib/mbedtls/include")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBEDTLS_CONFIG_FILE='\"${mpy_port_dir}/src/mbedtls/include/mbedtls_config.h\"'")
# message(${CMAKE_C_FLAGS})
append_srcs_dir(ADD_SRCS "port/src/mbedtls")
append_srcs_dir(ADD_SRCS "core/lib/mbedtls/library")
list(REMOVE_ITEM ADD_SRCS "${mpy_core_dir}/lib/mbedtls/library/net_sockets.c")
endif()
稍微解释一下
- 将 mbedtls/include 和 mbedtls/library 添加到环境中,并排除 net_sockets.c 的实现,问题下述。
- 给 CMAKE_C_FLAGS 添加 -DMBEDTLS_CONFIG_FILE 自定义的 mbedtls 配置文件,从而屏蔽内置的 config.h 。(奇怪的是 add_definitions 不 work ,就直接用 set 了)
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
所以现在把代码编译了进去,就完成了大部分的移植,是不是很简单?
当然,事情不会这么顺利的,在没有进行专门配置的时候,启动模块是可以的,现在开始实践发起一次 get https 网站的请求,测试 python code 如下:
wCli = MicroWebCli('https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13631786501')
# wCli = MicroWebCli('https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13631786501')
# wCli = MicroWebCli('https://github.com')
# wCli = MicroWebCli('https://ssl.logink.cn/')
# wCli = MicroWebCli('https://cn.bing.com/?FORM=Z9FD1')
# wCli = MicroWebCli('https://www.sojson.com')
# wCli = MicroWebCli('https://www.baidu.com')
while True:
try:
print('GET %s' % wCli.URL)
wCli.OpenRequest()
buf = memoryview(bytearray(1024))
resp = wCli.GetResponse()
if resp.IsSuccess() :
while not resp.IsClosed() :
x = resp.ReadContentInto(buf)
if x < len(buf) :
buf = buf[:x]
print(bytes(buf))
print('GET success with "%s" content type' % resp.GetContentType())
else :
print('GET return %d code (%s)' % (resp.GetStatusCode(), resp.GetStatusMessage()))
except Exception as E:
print(E)
time.sleep(2)
问题一: -0x0034 错误代码
Gather entropy_len bytes of entropy to seed state
mbedtls_ctr_drbg_seed returned -52 或者-0x0034
错误解释:
MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED -0x0034 /**< The entropy source failed. */
原因:
给mbedtls提供的熵源不够混乱,应该用硬件随机数发生器。
解决:
mbedtls\config.h
#define MBEDTLS_ENTROPY_HARDWARE_ALT
解决方案可以参考
https://blog.csdn.net/liaofeifly/article/details/88899655
mbedtls_hardware_poll 实现可以参考 esp32 、stm32 的,如下是我后来实践到 k210 的,这个函数只会在发起链接的时候调用。
#include <stdlib.h>
#include <stdio.h>
#include "rng.h"
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
#include "mphalport.h"
int os_get_random(unsigned char *buf, size_t len)
{
int i, j;
unsigned long tmp;
for (i = 0; i < ((len + 3) & ~3) / 4; i++) {
tmp = rng_get() + systick_current_millis();
for (j = 0; j < 4; j++) {
if ((i * 4 + j) < len) {
buf[i * 4 + j] = (uint8_t)(tmp >> (j * 8));
} else {
break;
}
}
}
return 0;
}
int mbedtls_hardware_poll( void *data, unsigned char *output, size_t len, size_t *olen )
{
int res = os_get_random(output, len);
*olen = len;
return 0;
}
// int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t *olen) {
// uint32_t val;
// int n = 0;
// *olen = len;
// while (len--) {
// if (!n) {
// val = rng_get();
// n = 4;
// }
// *output++ = val;
// val >>= 8;
// --n;
// }
// return 0;
// }
问题二: -0x7280 错误代码
错误类型是
#define MBEDTLS_ERR_SSL_CONN_EOF -0x7280 /**< The connection indicated an EOF. */
这个问题我看了很久,因为上来就给我当头一棒,任何 https 的访问都布星。
那就奇了怪了,然后我开了 debug 开关,开到等级 4 后。
#ifdef MBEDTLS_DEBUG_C
// Debug level (0-4)
mbedtls_debug_set_threshold(4);
#endif
发现 TM 有在流动数据,而且是在工作的,而且看数据发现是正常工作的。
那么开了 debug 和没有开 debug 的区别在哪里呢?
可以想象的是 debug 肯定会对程序执行产生一些细微的差距,最后通过几次 debug 的后定位到是这个函数的延时保障了程序的执行。
看起来是不是很奇怪?为什么呢?如果在这里简单的延时就会产生下述的效果。
也就是请求时好时坏,如果知道是需要延时的话,那么又该延时多少呢?
此时交给❤名侦探登场❤,真相只有一个!
首先函数来自于 f_send 操作,这个肯定是个回调,然后定位它。
void mbedtls_ssl_set_bio( mbedtls_ssl_context *ssl,
void *p_bio,
mbedtls_ssl_send_t *f_send,
mbedtls_ssl_recv_t *f_recv,
mbedtls_ssl_recv_timeout_t *f_recv_timeout )
{
ssl->p_bio = p_bio;
ssl->f_send = f_send;
ssl->f_recv = f_recv;
ssl->f_recv_timeout = f_recv_timeout;
}
说明来自上层,那么继续,定位到 /home/junhuanchen/MaixPy/components/micropython/core/extmod/modussl_mbedtls.c 。
mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL);
说明回调的是 _mbedtls_ssl_send 函数,如下。
STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
mp_obj_t sock = *(mp_obj_t*)ctx;
const mp_stream_p_t *sock_stream = mp_get_stream(sock);
int err;
mp_uint_t out_sz = sock_stream->write(sock, buf, len, &err);
if (out_sz == MP_STREAM_ERROR) {
if (mp_is_nonblocking_error(err)) {
return MBEDTLS_ERR_SSL_WANT_WRITE;
}
return -err;
} else {
return out_sz;
}
}
经过测试发现,延时不应该上在 sock_stream->write 之前,而是之后,那么这说明什么问题呢?
如果要解决问题,是可以在这里添加延时解决问题,但是这里是问题的源头吗?显然不是。
判断有二
- esp32 为什么没有这个问题?
- 这个模块的位置在 micropython core 体系下,说明有大量的实例支撑它们的逻辑正确性。
那么说明问题并不是这个地方导致的,在这个 micropython 的体系里,这时候只能说明一个问题,是网卡的 sock_stream->write 请求过快返回导致的问题。
而我在用的是 ESP8285 的 AT 网卡,这就可以联想到 esp_send 的工作机制不一定符合标准 socket 工作时序。
为什么呢?
因为 AT 的请求只需要将数据发送过去即可完成传输,但传输正确与否请求这端不直接参与,这与其他芯片的工作到链路层发送数据的机制不同,所以就会提前返回。
如果我的假设是正确的,那么我应该把问题定位到这里 components/micropython/port/src/standard_lib/network/esp8285/modesp8285.c 之中的 esp8285_socket_send 函数。
STATIC mp_uint_t esp8285_socket_send(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) {
if((mp_obj_type_t*)&mod_network_nic_type_esp8285 != mp_obj_get_type(MP_OBJ_TO_PTR(socket->nic)))
{
*_errno = MP_EPIPE;
return MP_STREAM_ERROR;
}
nic_obj_t* self = MP_OBJ_TO_PTR(socket->nic);
if(socket->peer_closed)
{
*_errno = MP_ENOTCONN;
return MP_STREAM_ERROR;
}
Buffer_Clear(&self->esp8285.buffer);//clear receive buffer
socket->first_read_after_write = true;
if(0 == esp_send(&self->esp8285,(const char*)buf,len, (uint32_t)(socket->timeout*1000) ) )
{
*_errno = MP_EPIPE;
return MP_STREAM_ERROR;
}
// printk("%s len %d\r\n", __func__, len);
mp_hal_delay_us(len * 50); // maybe 50 us time required to send per byte
// vTaskDelay(len / portTICK_PERIOD_MS);
return len;
}
在这里我做了一个假设性的 timing 测试,假设为了修复它与其他芯片同步的工作时序,那么我应该在发送数据后进行一段时间的延时,但这个延时是多久呢?
我现在通信在用的波特率都是 921600 ,在这个假设可行的情况下,我假定每个字节发送到对端需要的时间为 100 us 即 mp_hal_delay_us(len * 50);
,以此进行测试。
结论是实测 100 us 恢复正常工作,表示已经修复成功,那么就结束了吗?
还没,我们应该还要继续确定,真正预期的延时应该是多少?
接着二分法到 50 us 也成功。
继续二分法到 25 us 也成功。
继续二分法到 10 us 却不成功。// 虚伪二分
那么结论也有个大概了,考虑到 921600 的速率过快,我原本思考要么就 25 us 最优方案。
但后来发现,每次请求的数据并不会应该这个值而有所改善性能,反而可能会存在小概率请求失败,那既然没影响,应该保守设置到 50 us 比较合理。
此时修改的位置非常合理,一方面不破坏 esp32 spi 那端的网卡,另一方面也不破坏 micropython core 的代码,从而比较优雅的解决问题。
最后做个总结
实测 https 的网站都有如下,不过 github.com 的 hostname 经常获取不到,dns 炸裂
wCli = MicroWebCli('https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13631786501')
wCli = MicroWebCli('https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13631786501')
wCli = MicroWebCli('https://github.com')
wCli = MicroWebCli('https://ssl.logink.cn/')
wCli = MicroWebCli('https://cn.bing.com/?FORM=Z9FD1')
wCli = MicroWebCli('https://www.sojson.com')
wCli = MicroWebCli('https://www.baidu.com')
已修复如下问题。
另外 HTTPS 的访问速度没有想象的快,从建立链接到收发数据就需要差不多 20s,相比 HTTP 的 10 秒一次请求,这中间有很大的优化空间。
简单判断跟加密解密的软实现函数的效率有关,也可能跟目标网站的响应速度以及对应的 TLS 传输协议有关,可以进一步优化 mbedtls 的配置文件。
需要实测真实用户的 https 的使用环境才能进一步优化。
2020年5月11日 junhuanchen 留
关于 k210 的 micropython 添加 ussl 模块,实现 https 访问支持的那些事。的更多相关文章
- nginx添加 nginx_heath模块
原因?为什么会使用nginx_heath 这个模块,主要是如nginx+tomcat部署的时,tomcat挂了之后nginx->upstream 轮询是可以踢掉挂掉的tomcat服务的,如果部署 ...
- nginx 添加nginx-http-concat模块
github地址:https://github.com/alibaba/nginx-http-concat/tree/master 简单的描述一下吧,网上说的安装新的模块需要重新编译nginx,具体的 ...
- 嵌入式linux驱动开发之给linux系统添加温度传感器模块
忙了几天,终于可以让ds18b20在自己的开发板的linux系统上跑了!虽然ds18b20不是什么新鲜玩意,但是想想知己可以给linux系统添加模块了还是有点小鸡冻呢! 虽然说现在硬件的资源非常丰富而 ...
- 动态编译添加php模块
注意:转载请注明出处:http://www.programfish.com/blog/?p=85 在很多时候我们用linux里搭建web服务器的时候会需要编译安装php套件,而在编译安装后可能又会需要 ...
- httpd添加新模块
*/ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...
- yum安装的Nginx添加第三方模块支持tcp
需求:生产有个接口是通过socket通信.nginx1.9开始支持tcp层的转发,通过stream实现的,而socket也是基于tcp通信. 实现方法:Centos7.2下yum直接安装的nginx, ...
- nginx 番外----添加第三方模块
#第三方模块需要先进行下载,然后再编译时指定文件目录 1.查看当前编译模块 root@nginx sbin]# ./nginx -V #查看当前添加模块 nginx version: nginx/ b ...
- yum安装下的nginx,如何添加模块,和添加第三方模块
需求:生产有个接口是通过socket通信.nginx1.9开始支持tcp层的转发,通过stream实现的,而socket也是基于tcp通信. 实现方法:Centos7.2下yum直接安装的nginx, ...
- 编译nginx平滑添加stream模块
1.操作背景 操作系统版本:CentOS Linux release (Core) nginx版本:1.13.4 nginx从1.9.0版本开始,新增了ngx_stream_core_module模块 ...
随机推荐
- Springboot:JSR303数据校验(五)
@Validated //开启JSR303数据校验注解 校验规则如下: [一]空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null, 无法查检长度为0的字符串 @No ...
- js上传文件前判断获取文件大小并且加以判断
描述:要求浏览器单个上传文件大小不超过10M. 解决方案: var fileSize = $("#fileId")[0].files[0].size/(1024*1024);if( ...
- synchronized 代码块怎么用
加不加 synchronized 有什么区别? synchronized 作为悲观锁,锁住了什么? 之前 2 篇文章我们已经知道 synchronized 的使用方法以及锁的内容(实例对象和Class ...
- 2019-2020-1 20199308《Linux内核原理与分析》第五周作业
<Linux内核分析> 第四章 系统调用的三层机制(上) 4.1 用户态.内核态和中断 与系统调用打交道的方式是通过库函数的方式 用户态与内核态的区分 内核态:高的执行级别下,代码可以执行 ...
- VIM 批量缩进4个空格
vim /etc/vimrc 或 vim ~/.vimrc set smartindent set shiftwidth= 按v选中多行,回车 然后shifit + >
- 防cc攻击利器之Httpgrard
一.httpgrard介绍 HttpGuard是基于openresty,以lua脚本语言开发的防cc攻击软件.而openresty是集成了高性能web服务器Nginx,以及一系列的Nginx模块,这其 ...
- UVALive 7501 Business Cycle
细心题 #include<bits/stdc++.h> using namespace std; #define rep(i,a,b) for(int i=a;i<=b;++i) # ...
- 用libevent写的海康摄像头rtsp客户端
之前一直使用live555作为RTSP的客户端,但其框架臃肿,虽然支持各种格式,但实际中并没有这些需求,关键是其注重于格式的解析,却不注重网络IO,单线程下性能也不高,重新用libevent编写rts ...
- 国际站中国区,孟买上Redis 4.0 集群版
信息摘要: 国际站中国区,孟买上线Redis 4.0 集群版适用客户: 所有用户版本/规格功能: redis 4.0 集群版产品文档: https://www.alibabacloud.com/hel ...
- Mysql 开窗函数实战
Mysql 开窗函数实战 Mysql 开窗函数在Mysql8.0+ 中可以得以使用,实在且好用. row number() over rank() over dense rank() ntile() ...