E2E保护是autosar标准中定义的,主要是为了保护通信安全(功能安全相关)。因此在原有的通信协议上增加了对应E2E的保护头部分。相关E2E会修改整体的通信协议的payload部分,因此如果需要支持E2E保护,那么通信双方都需要增加E2E的保护机制。
E2E保护其实主要是在原有的包中增加了CRC校验的部分来保证消息的完整性(从安全角度来说,并不能完全保证),以及增加对应的引用计数,来判断消息是否有缺失,重复等等现象。
在E2E profile4中,用户数据布局(要保护的数据的布局)不受端到端加密配置文件4的约束——只有一个要求,即要保护的数据长度是1字节的倍数。
上面所示的位编号表示位传输的顺序。 端到端加密报头字段(例如端到端加密计数器)被编码为:
1. 大端(最重要的字节在前面)-由profile规定
2. LSB第一(字节内最低有效位第一)——由TCPIP总线强制
例如,端到端加密计数器的16位按照以下顺序(数字越大,意义越大)传输:8 9 10 11 12 13 14 15 0 12 3 4 5 6 7。
通过配置整个端到端加密头的偏移量,头可以放置在受保护数据中的特定位置。
在 E2E Profile 4 中,计数器由 E2E Profile 初始化、递增、重置和检查。 计数器不被 E2E 监管的调用者操纵或使用。
[PRS_E2E_00478] 在 E2E 配置文件 4 中,在发送方,对于数据元素的第一个传输请求,计数器应初始化为 0,并应为每个后续发送请求递增 1。 当计数器达到最大值(0xFF’FF)时,它会以 0 重新开始下一次发送请求。(RS_E2E_08539)
注意:此规范以前被错误地标识为 PRS_E2EProtocol_00324。
请注意,计数器值 0xFF’FF 并未保留为特殊的无效值,而是用作普通计数器值。
唯一的Data id用于验证每个传输的与安全相关的数据元素的身份。
[PRS_E2E_00326] 在端到端加密配置文件4中,数据ID需要明确的传输,即作为传输端到端加密头的一部分。
[PRS_E2E_UC_00327] 在端到端配置文件4中,数据id应该在通信系统网络中是全局唯一的(由多个ecu组成,每个ecu发送不同的数据)。
在使用端到端监管来保护数据元素(即来自RTE的调用)的情况下,由于通信的多重性(1:1或1:N),数据元素的消费者只期望一个特定的数据元素,该数据元素由端到端监管使用数据ID检查。
在使用端到端加密监管来保护消息(例如来自COM的调用)的情况下,接收端COM只希望收到一个特定的消息,该消息由端到端加密监管使用数据ID进行检查。
引入Length字段是为了支持可变大小的长度——存储序列化数据的Data[]数组在每个周期中可能有不同的长度。 长度包括用户数据+端到端加密头(CRC + Counter + Length + DataID)。
E2E Profile 4 使用 32 位 CRC,以确保高检测率和高汉明距离。
[PRS_E2E_00329] dE2E Profile 4 应使用 SWS CRC Supervision 的 Crc_CalculateCRC32P4() 函数来计算 CRC.c(RS_E2E_08528, RS_E2E_- 08539)
注意:E2E Profile 4 使用的 CRC 与 FlexRay、CAN 和 TCP/IP 使用的 CRC 不同。 它还由不同的软件模块提供(FlexRay、CAN 和 TCP/IP 堆栈 CRC/校验和由通信控制器中的硬件支持或通信堆栈软件提供,但不由 CRC Supervision 提供)。
[PRS_E2E_00330] d在 E2E Profile 4 中,CRC 应在整个 E2E 标头(不包括 CRC 字节)和用户数据上计算。c(RS_E2E_08531)
前面提到的机制(CRC、计数器、数据 ID、长度)能够检查接收到的数据元素的有效性,当接收器独立于数据传输执行时,即当接收器没有被阻塞等待数据元素或消息时,但是 相反,如果接收器读取当前可用的数据(即检查新数据是否可用)。 然后,通过计数器,接收器可以检测到通信丢失和超时。
函数 E2E_P04Protect() 执行本节中以下九个图表指定的步骤。
[PRS_E2E_00362] d 函数 E2E_P04Protect() 应具有如图 6.28.c (RS_E2E_08539) 所示的整体行为
[PRS_E2E_00363] d E2E_P04Protect() 中的“验证保护功能的输入”步骤的行为如图 6.29.c (RS_E2E_08539)
[PRS_E2E_00376] d E2E_P04Protect()、E2E_P04Forward()和E2E_P04Check()中的“计算偏移量”步骤的行为如图6.30.c (RS_E2E_08539)所示。
[PRS_E2E_00364] d E2E_P04Protect()和E2E_P04Forward()中的步骤“Write Length”的行为如图6.31.c (RS_E2E_08539)所示。
offset 计算本地变量Uint16偏移量。 单位是[byte]
目前vsomeip主要以插件的形式增加了E2E保护的相关机制
通过代码可以看到,整个E2E保护的部分,主要加载在routing进程中,因此对于客户端来说,无需关注具体E2E的实现,只需要在使用时打开对应的开关即可。
#ifndef ANDROID
//e2e功能是否配置了
if( configuration_->is_e2e_enabled()) {
VSOMEIP_INFO << "E2E protection enabled.";
const char *its_e2e_module = getenv(VSOMEIP_ENV_E2E_PROTECTION_MODULE);
//这里没有指定对应的E2E库的话,默认使用someip的
std::string plugin_name = its_e2e_module != nullptr ? its_e2e_module : VSOMEIP_E2E_LIBRARY;
auto its_plugin = plugin_manager::get()->get_plugin(plugin_type_e::APPLICATION_PLUGIN, plugin_name);
if (its_plugin) {
VSOMEIP_INFO << "E2E module loaded.";
e2e_provider_ = std::dynamic_pointer_cast(its_plugin);
}
}
if(e2e_provider_) {
std::map> its_e2e_configuration = configuration_->get_e2e_configuration();
for (auto &identifier : its_e2e_configuration) {
//添加对应的配置信息
if(!e2e_provider_->add_configuration(identifier.second)) {
VSOMEIP_INFO << "Unknown E2E profile: " << identifier.second->profile << ", skipping ...";
}
}
}
#endif
//根据配置信息确定使用的profile
bool e2e_provider_impl::add_configuration(std::shared_ptr config)
{
if (config->profile == "CRC8" || config->profile == "P01") {
process_e2e_profile(config);
return true;
}
if (config->profile == "CRC32" || config->profile == "CSTM") {
process_e2e_profile(config);
return true;
}
if (config->profile == "P04") {
process_e2e_profile(config);
return true;
}
return false;
}
由于E2E主要时保护跨设备进行通信的,因此对于vsomeip整体的架构,本地的IPC通信就不需要E2E的保护了。
bool routing_manager_impl::send(client_t _client, const byte_t *_data,
length_t _size, instance_t _instance, bool _reliable,
client_t _bound_client,
credentials_t _credentials,
uint8_t _status_check, bool _sent_from_remote)
E2E保护部分代码实现。首先根据之前加载的配置文件中的信息判断当前的serviceid和methodid是否需要E2E保护。其次通过调用E2E的protect函数计算出E2E的头部信息,然后增加在data前面。
if (e2e_provider_) {
if ( !is_service_discovery) {
service_t its_service = VSOMEIP_BYTES_TO_WORD(
_data[VSOMEIP_SERVICE_POS_MIN], _data[VSOMEIP_SERVICE_POS_MAX]);
method_t its_method = VSOMEIP_BYTES_TO_WORD(
_data[VSOMEIP_METHOD_POS_MIN], _data[VSOMEIP_METHOD_POS_MAX]);
#ifndef ANDROID
if (e2e_provider_->is_protected({its_service, its_method})) {
// Find out where the protected area starts
size_t its_base = e2e_provider_->get_protection_base({its_service, its_method});
// Build a corresponding buffer
its_buffer.assign(_data + its_base, _data + _size);
e2e_provider_->protect({ its_service, its_method }, its_buffer, _instance);
// Prepend header
its_buffer.insert(its_buffer.begin(), _data, _data + its_base);
_data = its_buffer.data();
}
#endif
}
}
消息的验证主要是在收到消息阶段,因此也是在routing进程里面。
void routing_manager_impl::on_message(const byte_t *_data, length_t _size,
endpoint *_receiver, const boost::asio::ip::address &_destination,
client_t _bound_client, credentials_t _credentials,
const boost::asio::ip::address &_remote_address,
std::uint16_t _remote_port) {
收到消息后,进行验证
if (e2e_provider_) {
its_method = VSOMEIP_BYTES_TO_WORD(
_data[VSOMEIP_METHOD_POS_MIN],
_data[VSOMEIP_METHOD_POS_MAX]);
#ifndef ANDROID
if (e2e_provider_->is_checked({its_service, its_method})) {
auto its_base = e2e_provider_->get_protection_base({its_service, its_method});
e2e_buffer its_buffer(_data + its_base, _data + _size);
e2e_provider_->check({its_service, its_method},
its_buffer, its_instance, its_check_status);
if (its_check_status != e2e::profile_interface::generic_check_status::E2E_OK) {
VSOMEIP_INFO << "E2E protection: CRC check failed for service: "
<< std::hex << its_service << " method: " << its_method;
}
}
#endif
}
相关的配置文件例子可以直接看源码目录下:test/e2e_tests/conf下面
service的配置:
"e2e": {
"e2e_enabled": "true",
"protected": [
{
"service_id": "0x0011",
"event_id": "0x0033",
"profile": "P04",
"variant": "protector",
"crc_offset" : "64",
"data_id" : "0x2d"
}
]
}
client的配置:
"e2e": {
"e2e_enabled": "true",
"protected": [
{
"service_id": "0x0011",
"event_id": "0x0033",
"profile": "P04",
"variant": "checker",
"crc_offset" : "64",
"data_id" : "0x2d"
}
]
}
在没有加入E2E保护时的包内容如下:
client发出来的request包
server发送的response包:
加入E2E之后的包内容:
原始数据:
service回复的内容:
可以看到加上E2E保护时,根据profile4的标准长度,是会覆盖掉data的前12个字节。(这部分查了好久,以为是bug了。结果去看了对应的e2e的测试用例才发现,这里是需要开发者自己关注的,在数据前需要预留足够的空间给到E2E头的位置)。
这里还有一个问题,就是profile4用的someip head是8byte,因此在计算CRC的时候,offset必须指定大于64,否则会覆盖掉部分头信息。