以下根据strongswan代码中的testing/tests/ikev2/net2net-fragmentation/中的测试环境,来看一下IKEv2协议的报文分片处理流程。拓扑结构如下:
拓扑图中使用到的设备包括:虚拟网关moon和sun网关。
网关配置
moon的配置文件:/etc/ipsec.conf,内容如下。注意此处fragmentation字段的值为yes,可能的取值还有:no、accept和force。其中yes表示如果对端支持分片,本端将开启分片功能。accept表示支持接收分片IKE报文,但是不会发送分片。force表示强制使用报文分片。
conn %default
keyexchange=ikev2
fragmentation=yes
conn net-net
left=PH_IP_MOON
leftcert=moonCert.pem
leftid=@moon.strongswan.org
leftsubnet=10.1.0.0/16
leftfirewall=yes
right=PH_IP_SUN
rightid=@sun.strongswan.org
rightsubnet=10.2.0.0/16
auto=add
配置文件/etc/strongswan.conf中的fragment_size指定分片的最大长度,默认为1280字节,指定为0的话,对于IPv4将使用值576。
charon {
load = random nonce aes sha1 sha2 pem pkcs1 curve25519 gmp x509 curl revocation hmac stroke kernel-netlink socket-default updown
fragment_size = 1088
}
此外,sun网关的配置与moon网关基本相同,区别在于一些主机相关选项差别。
配置消息发送
文件strongswan-5.8.1/src/starter/starter.c,使用默认的配置文件ipsec.conf(CONFIG_FILE)。
int main (int argc, char **argv)
{
starter_config_t *cfg = NULL;
starter_conn_t *conn, *conn2;
if (!config_file) {
config_file = lib->settings->get_str(lib->settings, "starter.config_file", CONFIG_FILE);
}
cfg = confread_load(config_file);
for (;;) {
/* Add stale conn and ca sections */
if (starter_charon_pid()) {
for (conn = cfg->conn_first; conn; conn = conn->next) {
if (conn->state == STATE_TO_ADD) {
if (starter_charon_pid()) {
starter_stroke_add_conn(cfg, conn);
文件/strongswan-5.8.1/src/starter/confread.c中函数conn_defaults用于设置连接的默认值,例如fragmentation默认状态为开启FRAGMENTATION_YES。如果配置文件ipsec.conf中未设置这些值,将使用默认值。
static void conn_defaults(starter_conn_t *conn)
{
conn->dpd_delay = 30; /* seconds */
conn->dpd_timeout = 150; /* seconds */
conn->replay_window = SA_REPLAY_WINDOW_DEFAULT;
conn->fragmentation = FRAGMENTATION_YES;
文件strongswan-5.8.1/src/starter/starterstroke.c中的函数starter_stroke_add_conn负责创建stroke_msg_t数据结构,并将其发送到套接口对端。stroke_msg_t结构成员fragmentation保存着是否分片的标志值。
int starter_stroke_add_conn(starter_config_t *cfg, starter_conn_t *conn)
{
stroke_msg_t *msg;
msg = create_stroke_msg(STR_ADD_CONN);
msg->add_conn.version = conn->keyexchange;
...
msg->add_conn.mobike = conn->options & SA_OPTION_MOBIKE;
msg->add_conn.force_encap = conn->options & SA_OPTION_FORCE_ENCAP;
msg->add_conn.fragmentation = conn->fragmentation;
...
return send_stroke_msg(msg);
配置信息接收
文件strongswan-5.8.1/src/libcharon/plugins/stroke/stroke_socket.c中的函数on_accept负责接收stroke配置消息。其由描述符中读取stroke_msg_t结构的数据,之后,根据其中的类型type字段进行响应的处理,对于连接添加操作STR_ADD_CONN,由函数stroke_add_conn进行处理。
static bool on_accept(private_stroke_socket_t *this, stream_t *stream)
{
stroke_msg_t *msg;
/* read length */
if (!stream->read_all(stream, &len, sizeof(len))) {
return FALSE;
}
if (len < offsetof(stroke_msg_t, buffer)) {
DBG1(DBG_CFG, "invalid stroke message length %d", len);
return FALSE;
}
/* read message (we need an additional byte to terminate the buffer) */
msg = malloc(len + 1);
msg->length = len;
if (!stream->read_all(stream, (char*)msg + sizeof(len), len - sizeof(len))) {
free(msg);
return FALSE;
}
switch (msg->type)
{
case STR_ADD_CONN:
stroke_add_conn(this, msg);
break;
同一个文件中的函数stroke_add_conn将调用stroke_config_t结构中的成员函数add来处理连接消息。
static void stroke_add_conn(private_stroke_socket_t *this, stroke_msg_t *msg)
{
this->config->add(this->config, msg);
this->attribute->add_dns(this->attribute, msg);
this->handler->add_attributes(this->handler, msg);
文件strongswan-5.8.1/src/libcharon/plugins/stroke/stroke_config.c中的函数add,调用build_ike_cfg函数创建ike_cfg_t结构。
METHOD(stroke_config_t, add, void, private_stroke_config_t *this, stroke_msg_t *msg)
{
ike_cfg_t *ike_cfg, *existing_ike;
peer_cfg_t *peer_cfg, *existing;
child_cfg_t *child_cfg;
enumerator_t *enumerator;
bool use_existing = FALSE;
ike_cfg = build_ike_cfg(this, msg);
if (!ike_cfg)
{
return;
}
同一个文件中的函数build_ike_cfg创建ike_cfg_create_t结构,并且调用ike_cfg_create函数,据此结构创建最终的ike_cfg_t结构。
static ike_cfg_t *build_ike_cfg(private_stroke_config_t *this, stroke_msg_t *msg)
{
ike_cfg_create_t ike;
ike_cfg_t *ike_cfg;
char me[256], other[256];
ike = (ike_cfg_create_t){
...
.force_encap = msg->add_conn.force_encap,
.fragmentation = msg->add_conn.fragmentation,
};
ike_cfg = ike_cfg_create(&ike);
在文件strongswan-5.8.1/src/libcharon/config/ike_cfg.c的函数ike_cfg_create中,fragmentation的值最终赋予了private_ike_cfg_t结构的成员变量fragmentation。
ike_cfg_t *ike_cfg_create(ike_cfg_create_t *data)
{
private_ike_cfg_t *this;
INIT(this,
...
.force_encap = data->force_encap,
.fragmentation = data->fragmentation,
分片大小
文件strongswan-5.8.1/src/libcharon/sa/ike_sa.c中函数ike_sa_create读取配置文件中fragment_size的数值,保存到private_ike_sa_t结构的成员变量fragment_size中,以供之后使用,这里我们设置的值为1088。如果配置文件中没有设置此字段,默认使用1280的长度值。
ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, ike_version_t version)
{
private_ike_sa_t *this;
INIT(this,
.public = {
},
.ike_sa_id = ike_sa_id->clone(ike_sa_id),
.fragment_size = lib->settings->get_int(lib->settings,
"%s.fragment_size", 1280, lib->ns),
);
文件strongswan-5.8.1/src/libcharon/sa/ikev2/task_manager_v2.c中函数generate_message用于发送IKEv2协议的报文。其调用以上的generate_message_fragmented函数。
/* Generates the given message and stores packet(s) in the given array
*/
static bool generate_message(private_task_manager_t *this, message_t *message, array_t **packets)
{
enumerator_t *fragments;
packet_t *fragment;
if (this->ike_sa->generate_message_fragmented(this->ike_sa, message, &fragments) != SUCCESS)
{
return FALSE;
}
while (fragments->enumerate(fragments, &fragment))
{
array_insert_create(packets, ARRAY_TAIL, fragment);
}
fragments->destroy(fragments);
array_compress(*packets);
文件strongswan-5.8.1/src/libcharon/sa/ike_sa.c中函数generate_message_fragmented处理报文分片。ike_cfg->fragmentation函数获取以上介绍的fragmentation配置,这里值为FRAGMENTATION_YES。对于FRAGMENTATION_FORCE,这里直接值为use_frags标志,使用分片发送。对于FRAGMENTATION_YES,需要检查是否支持分片扩展EXT_IKE_FRAGMENTATION,另外,对于IKEv1版本协议,如果支持Microsoft扩展,还需要检查是否请求了证书,如果未请求证书,不使用分片。按照注释中的说法,Windows 7和8端点只有在请求证书时,才接收分片报文(一般情况下,不带有证书的报文不会太大)。
METHOD(ike_sa_t, generate_message_fragmented, status_t, private_ike_sa_t *this, message_t *message, enumerator_t **packets)
{
enumerator_t *fragments;
packet_t *packet;
status_t status;
bool use_frags = FALSE;
bool pre_generated = FALSE;
if (this->ike_cfg)
{
switch (this->ike_cfg->fragmentation(this->ike_cfg))
{
case FRAGMENTATION_FORCE:
use_frags = TRUE;
break;
case FRAGMENTATION_YES:
use_frags = supports_extension(this, EXT_IKE_FRAGMENTATION);
if (use_frags && this->version == IKEV1 && supports_extension(this, EXT_MS_WINDOWS))
{
/* It seems Windows 7 and 8 peers only accept proprietary fragmented messages if they expect certificates. */
use_frags = message->get_payload(message, PLV1_CERTIFICATE) != NULL;
}
break;
default:
break;
}
}
对于无需分片的报文,即use_frags为空,直接使用generate_message函数执行发送。
if (!use_frags) {
status = generate_message(this, message, &packet);
if (status != SUCCESS) {
return status;
}
*packets = enumerator_create_single(packet, NULL);
return SUCCESS;
}
pre_generated = message->is_encoded(message);
this->stats[STAT_OUTBOUND] = time_monotonic(NULL);
message->set_ike_sa_id(message, this->ike_sa_id);
if (!pre_generated) {
charon->bus->message(charon->bus, message, FALSE, TRUE);
}
status = message->fragment(message, this->keymat, this->fragment_size, &fragments);
if (status == SUCCESS) {
if (!pre_generated) {
charon->bus->message(charon->bus, message, FALSE, FALSE);
}
*packets = enumerator_create_filter(fragments, filter_fragments, this, NULL);
}
return status;
分配处理
对于分片报文,由文件strongswan-5.8.1/src/libcharon/encoding/message.c中的函数fragment进行处理。我们指定的参数frag_len是整个IP报文的长度,数据长度需要减去IP头部(不计算IP选项),和UDP头部长度,另外对于使用NAT-T(4500)端口的IKE报文,还需要减去4额字节的non-ESP marker(固定值:00 00 00 00),这里没有启用NAT-T,最后数据的长度为1088-20-8=1060字节。
METHOD(message_t, fragment, status_t, private_message_t *this, keymat_t *keymat, size_t frag_len, enumerator_t **fragments)
{
encrypted_payload_t *encrypted = NULL;
generator_t *generator = NULL;
message_t *fragment;
packet_t *packet;
payload_type_t next = PL_NONE;
/* frag_len is the complete IP datagram length, account for overhead (we assume no IP options/extension headers are used) */
REDUCE_FRAG_LEN(frag_len, (src->get_family(src) == AF_INET) ? 20 : 40);
/* 8 (UDP header) */
REDUCE_FRAG_LEN(frag_len, 8);
if (dst->get_port(dst) != IKEV2_UDP_PORT && src->get_port(src) != IKEV2_UDP_PORT) {
REDUCE_FRAG_LEN(frag_len, 4); /* reduce length due to non-ESP marker */
}
以下对于已经编码的数据,可直接获取其缓存的首地址和长度值,对于IKEv2版本,可使用get_payload获取到加密载荷。否则,使用generate_message生成编码数据。随后,验证数据长度是否超出规定的分片长度,这里为以上计算的值1060,如果数据未超过分片长度,或者其为未经过加密的IKEv2协议数据,不进行分片处理。
if (is_encoded(this)) {
if (this->major_version == IKEV2_MAJOR_VERSION) {
encrypted = (encrypted_payload_t*)get_payload(this, PLV2_ENCRYPTED);
}
data = this->packet->get_data(this->packet);
len = data.len;
} else {
status = generate_message(this, keymat, &generator, &encrypted);
if (status != SUCCESS) {
DESTROY_IF(generator);
return status;
}
data = generator->get_chunk(generator, &lenpos);
len = data.len + (encrypted ? encrypted->get_length(encrypted) : 0);
}
/* check if we actually need to fragment the message and if we have an encrypted payload for IKEv2 */
if (len <= frag_len || (this->major_version == IKEV2_MAJOR_VERSION && !encrypted)) {
if (generator) {
status = finalize_message(this, keymat, generator, encrypted);
if (status != SUCCESS)
return status;
}
*fragments = enumerator_create_single(this->packet, NULL);
return SUCCESS;
}
至此,开始分片处理工作。将之前计算的分片阈值frag_len在减去28个字节,即1060-28=1032字节,此28字节为ISAKMP头部的长度。对于IKEv1版本协议,分片长度还需要减去8字节的分片载荷头部,此头部要来描述,总的分片数量,此报文的分片编号等信息。对于IKEv1,分片长度减小为1032-8=1024字节,即实际发送的数据部分的长度。
/* frag_len denoted the maximum IKE message size so far, later on it will
* denote the maximum content size of a fragment payload, therefore, account for IKE header */
REDUCE_FRAG_LEN(frag_len, 28);
if (this->major_version == IKEV1_MAJOR_VERSION)
{
if (generator) {
status = finalize_message(this, keymat, generator, encrypted);
if (status != SUCCESS) {
return status;
}
data = this->packet->get_data(this->packet);
generator = NULL;
}
/* overhead for the fragmentation payload header */
REDUCE_FRAG_LEN(frag_len, 8);
对于IKEv2协议而言,除了和IKEv1协议一样,分片长度要减去分片载荷头部长度的8个字节外,还需要减去加密算法使用的初始向量IV,以及完整性算法使用的ICV的长度,最后分片长度还需要是加密算法的块长度的整数倍。
} else {
aead_t *aead;
if (generator) {
generator->destroy(generator);
generator = generator_create();
} else {
generator = generator_create_no_dbg(); /* do not log again if it was generated previously */
}
next = encrypted->payload_interface.get_next_type((payload_t*)encrypted);
encrypted->generate_payloads(encrypted, generator);
data = generator->get_chunk(generator, &lenpos);
if (!is_encoded(this)) {
encrypted->destroy(encrypted);
}
aead = keymat->get_aead(keymat, FALSE);
/* overhead for the encrypted fragment payload */
REDUCE_FRAG_LEN(frag_len, aead->get_iv_size(aead));
REDUCE_FRAG_LEN(frag_len, aead->get_icv_size(aead));
REDUCE_FRAG_LEN(frag_len, 8); /* fragmentation payload header */
frag_len = round_down(frag_len, aead->get_block_size(aead)); /* padding and padding length */
REDUCE_FRAG_LEN(frag_len, 1);
以下根据计算而来的分配长度frag_len,来计算长度为data.len的数据需要分成几个片段,count变量表示分片数量。
count = data.len / frag_len + (data.len % frag_len ? 1 : 0);
this->fragments = array_create(0, count);
DBG1(DBG_ENC, "splitting IKE message (%zu bytes) into %hu fragments", len, count);
以下的循环使用create_fragment函数创建指定长度的分片,message的generate函数负责生产报文数据,最后将生成的报文分片插入message_t结构的fragments指定的数组中。
for (num = 1; num <= count; num++) {
len = min(data.len, frag_len);
fragment = create_fragment(this, next, num, count, chunk_create(data.ptr, len));
status = fragment->generate(fragment, keymat, &packet);
fragment->destroy(fragment);
if (status != SUCCESS) {
DBG1(DBG_ENC, "failed to generate IKE fragment");
clear_fragments(this);
DESTROY_IF(generator);
return FAILED;
}
array_insert(this->fragments, ARRAY_TAIL, packet);
data = chunk_skip(data, len);
}
*fragments = array_create_enumerator(this->fragments);
分片
文件strongswan-5.8.1/src/libcharon/encoding/message.c中的函数create_fragment负责创建一个分片,对于IKEv1协议而言,message_id总是为0。另外需要注意的是,即使是quick模式或者transaction的分片报文,IKEv1仍将使用最初阶段一的交换类型:AGGRESSIVE或者ID_PROT。
函数fragment_payload_create_from_data负责创建fragment分片载荷头部。
static message_t *create_fragment(private_message_t *this, payload_type_t next, uint16_t num, uint16_t count, chunk_t data)
{
enumerator_t *enumerator;
payload_t *fragment, *payload;
message_t *message;
peer_cfg_t *peer_cfg;
ike_sa_t *ike_sa;
message = clone_message(this);
if (this->major_version == IKEV1_MAJOR_VERSION)
{
message->set_message_id(message, 0); /* other implementations seem to just use 0 as message ID, so here we go */
/* always use the initial message type for fragments, even for quick mode or transaction messages.
*/
ike_sa = charon->bus->get_sa(charon->bus);
if (ike_sa && (peer_cfg = ike_sa->get_peer_cfg(ike_sa)) && peer_cfg->use_aggressive(peer_cfg))
{
message->set_exchange_type(message, AGGRESSIVE);
} else {
message->set_exchange_type(message, ID_PROT);
}
fragment = (payload_t*)fragment_payload_create_from_data(num, num == count, data);
对于IKEv2协议而言,使用函数encrypted_fragment_payload_create_from_data创建fragment分片载荷头部数据,对于第一个分片,其可以包括未经过加密的载荷。
} else {
fragment = (payload_t*)encrypted_fragment_payload_create_from_data( num, count, data);
if (num == 1) {
/* only in the first fragment is this set to the type of the first payload in the encrypted payload */
fragment->set_next_type(fragment, next);
/* move unencrypted payloads to the first fragment */
enumerator = this->payloads->create_enumerator(this->payloads);
while (enumerator->enumerate(enumerator, &payload))
{
if (payload->get_type(payload) != PLV2_ENCRYPTED) {
this->payloads->remove_at(this->payloads, enumerator);
message->add_payload(message, payload);
}
}
enumerator->destroy(enumerator);
}
}
message->add_payload(message, (payload_t*)fragment);
return message;
发送
文件strongswan-5.8.1/src/libcharon/sa/ikev2/task_manager_v2.c中的函数执行最终的发送操作。
static void send_packets(private_task_manager_t *this, array_t *packets, host_t *src, host_t *dst)
{
packet_t *packet, *clone;
int i;
for (i = 0; i < array_count(packets); i++)
{
array_get(packets, i, &packet);
clone = packet->clone(packet);
if (src) {
clone->set_source(clone, src->clone(src));
}
if (dst) {
clone->set_destination(clone, dst->clone(dst));
}
charon->sender->send(charon->sender, clone);
报文
以下为Non-ESP Marker字段和ISAKMP字段:
以为fragment分片载荷头部字段。
strongswan版本: 5.8.1
内核版本: 5.0
END