IKEv2协议报文分片处理

以下根据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

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页