以下根据strongswan代码中的testing/tests/ikev2/net2net-rekey/中的测试环境,验证一下网络到网络的IKEv2协议的秘钥重协商过程。拓扑结构如下:
拓扑图中使用到的设备包括:虚拟网关moon和sun。
moon网关配置
moon网关的配置文件:/etc/ipsec.conf ,内容如下。为了进行秘钥重协商,安全关联的生存期lifetime指定为较短的10s,正常情况下不应使用如此短的时间值。margintime指定在具体生存期到期之前多久开始进行重新协商,此处为5s秒,即在距离到期(10s)前5s开始重协商。rekeyfuzz字段的值为0%表示禁用重协商时间的随机功能。
conn %default
ikelifetime=60m
lifetime=10s
margintime=5s
rekeyfuzz=0%
keyingtries=1
keyexchange=ikev2
mobike=no
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
strongswan使用以下的公式计算重协商发起时间,根据以上的参数配置,可得到rekeytime为5秒。rekeyfuzz默认为100%。
rekeytime = lifetime - (margintime + random(0, margintime * rekeyfuzz))
文件strongswan-5.8.1/src/libcharon/plugins/stroke/stroke_socket.c中的函数stroke_add_conn处理连接配置的添加,例如添加以上的net-net连接。
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);
以上add函数指针指向同目录下文件stroke_config.c文件中的add函数,其中调用子函数build_peer_cfg处理秘钥重协商相关参数。jitter_time变量的值正是依照以上rekeyfuzz配置项的值与margintime值的乘积而得到的一个范围值。此值范围为[0,margintime]。peer.rekey_time的值初始化为生存期lifetime的值减去over_time(margintime)的值。
static peer_cfg_t *build_peer_cfg(private_stroke_config_t *this, stroke_msg_t *msg, ike_cfg_t *ike_cfg)
{
peer.jitter_time = msg->add_conn.rekey.margin * msg->add_conn.rekey.fuzz / 100;
peer.over_time = msg->add_conn.rekey.margin;
if (msg->add_conn.rekey.reauth)
{
peer.reauth_time = msg->add_conn.rekey.ike_lifetime - peer.over_time;
} else {
peer.rekey_time = msg->add_conn.rekey.ike_lifetime - peer.over_time;
}
在文件libcharon/config/peer_cfg.c中的函数get_rekey_time用于获取秘钥重协商的时间,如下所示,为以上计算的rekey_time时间,加上[0,margintime]范围内的随机时间。另外,对于IKE连接的重新认证时间,margintime和rekeyfuzz配置参数同样有效,参见get_reauth_time函数。
METHOD(peer_cfg_t, get_rekey_time, uint32_t, private_peer_cfg_t *this, bool jitter)
{
if (this->rekey_time == 0)
{
return 0;
}
if (this->jitter_time == 0 || !jitter)
{
return this->rekey_time;
}
return this->rekey_time - (random() % this->jitter_time);
moon网关的配置文件:/etc/strongswan.conf,内容如下。字段delete_rekeyed_delay值为2,意味着在秘钥重协商完成之后,延时2秒钟删除CHILD_SA子关联的inbound方向结构,以便能够处理延迟接收到的报文。此值配置为0,将等待CHILD_SA超时执行删除。
charon {
load = random nonce aes sha1 sha2 pem pkcs1 curve25519 gmp x509 curl revocation hmac stroke kernel-netlink socket-default updown
# remove rekeyed inbound SA a bit quicker for the test scenario
delete_rekeyed_delay = 2
syslog {
daemon {
knl = 2
}
}
}
在文件:strongswan-5.8.1/src/libcharon/sa/ikev2/tasks/child_delete.c中,函数destroy_and_reestablish负责删除CHILD_SA子关联。首先获得配置的delete_rekeyed_delay字段的值,如果未配置,使用默认值DELETE_REKEYED_DELAY(5秒)。再者对于秘钥重协商的SA,移除其outbond方向SA。最后,如果当前时间加上delay延时时间小于SA的生存期,延迟delay之后调用函数delete_child_sa_job_create_id删除inbound方向SA。
static status_t destroy_and_reestablish(private_child_delete_t *this)
{
delay = lib->settings->get_int(lib->settings, "%s.delete_rekeyed_delay",
DELETE_REKEYED_DELAY, lib->ns);
enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&entry))
{
child_sa = entry->child_sa;
child_sa->set_state(child_sa, CHILD_DELETED);
/* signal child down event if we weren't rekeying */
protocol = child_sa->get_protocol(child_sa);
if (!entry->rekeyed) { ... }
else
{
install_outbound(this, protocol, child_sa->get_rekey_spi(child_sa));
/* for rekeyed CHILD_SAs we uninstall the outbound SA but don't
* immediately destroy it, by default, so we can process delayed packets */
child_sa->remove_outbound(child_sa);
expire = child_sa->get_lifetime(child_sa, TRUE);
if (delay && (!expire || ((now + delay) < expire)))
{
lib->scheduler->schedule_job(lib->scheduler,
(job_t*)delete_child_sa_job_create_id(
child_sa->get_unique_id(child_sa)), delay);
continue;
}
sun网关配置
sun的配置文件:/etc/ipsec.conf,内容如下。无需要注意的特殊配置项。
conn %default
ikelifetime=60m
keylife=20m
rekeymargin=3m
keyingtries=1
keyexchange=ikev2
mobike=no
conn net-net
left=PH_IP_SUN
leftcert=sunCert.pem
leftid=@sun.strongswan.org
leftsubnet=10.2.0.0/16
leftfirewall=yes
right=PH_IP_MOON
rightid=@moon.strongswan.org
rightsubnet=10.1.0.0/16
auto=add
连接建立
操作流程如下,在sun和moon两个网关上启动strongswan进程,之后在moon网关上发起net-net连接的建立。
sun::ipsec start
moon::ipsec start
sun::expect-connection net-net
moon::expect-connection net-net
moon::ipsec up net-net
内核SA过期
moon网关的内核的SA如下所示,可见在192.168.0.2到192.168.0.1反向的SA的软性超时时长为5秒钟,硬性时长为10秒钟,由配置一致。
src 192.168.0.1 dst 192.168.0.2
proto esp spi 0xc14f1c0a(3243187210) reqid 1(0x00000001) mode tunnel
lifetime config:
expire add: soft 0(sec), hard 10(sec)
expire use: soft 0(sec), hard 0(sec)
lifetime current:
168(bytes), 2(packets)
add 2019-12-09 09:44:21 use 2019-12-09 09:44:22
stats:
replay-window 0 replay 0 failed 0
src 192.168.0.2 dst 192.168.0.1
proto esp spi 0xcb131d54(3407027540) reqid 1(0x00000001) mode tunnel
lifetime config:
expire add: soft 5(sec), hard 10(sec)
expire use: soft 0(sec), hard 0(sec)
lifetime current:
168(bytes), 2(packets)
add 2019-12-09 09:44:21 use 2019-12-09 09:44:22
内核中结构体类型变量netlink_mgr初始化了notify函数调用xfrm_send_state_notify用于向应用层发送netlink消息通知,如下所示。
static struct xfrm_mgr netlink_mgr = {
.notify = xfrm_send_state_notify,
.acquire = xfrm_send_acquire,
.compile_policy = xfrm_compile_policy,
.notify_policy = xfrm_send_policy_notify,
.report = xfrm_send_report,
.migrate = xfrm_send_migrate,
.new_mapping = xfrm_send_mapping,
.is_alive = xfrm_is_alive,
};
函数xfrm_send_state_notify内容如下,对于SA过期消息XFRM_MSG_EXPIRE,由函数xfrm_exp_state_notify处理。
static int xfrm_send_state_notify(struct xfrm_state *x, const struct km_event *c)
{
switch (c->event) {
case XFRM_MSG_EXPIRE:
return xfrm_exp_state_notify(x, c);
在函数xfrm_exp_state_notify中,创建netlink消息报文skb,由子函数build_expire填充内容,最后,xfrm_nlmsg_multicast将消息多播发送到组XFRMNLGRP_EXPIRE。
static int xfrm_exp_state_notify(struct xfrm_state *x, const struct km_event *c)
{
struct net *net = xs_net(x);
struct sk_buff *skb;
skb = nlmsg_new(xfrm_expire_msgsize(), GFP_ATOMIC);
if (skb == NULL)
return -ENOMEM;
if (build_expire(skb, x, c) < 0) {
kfree_skb(skb);
return -EMSGSIZE;
}
return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_EXPIRE);
目前发送到应用层的消息中包括portid,以及表明时硬性过期还是软性过期的hard值,SA的标记mark和xfrm虚拟接口索引if_id值。
static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
{
struct xfrm_user_expire *ue;
struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, c->portid, 0, XFRM_MSG_EXPIRE, sizeof(*ue), 0);
ue = nlmsg_data(nlh);
copy_to_user_state(x, &ue->state);
ue->hard = (c->data.hard != 0) ? 1 : 0;
/* clear the padding bytes */
memset(&ue->hard + 1, 0, sizeof(*ue) - offsetofend(typeof(*ue), hard));
err = xfrm_mark_put(skb, &x->mark);
err = xfrm_if_id_put(skb, x->if_id);
内核函数km_state_expired将调用以上netlink_mgr结构中的notify指针,事件类型为XFRM_MSG_EXPIRE。
void km_state_notify(struct xfrm_state *x, const struct km_event *c)
{
struct xfrm_mgr *km;
rcu_read_lock();
list_for_each_entry_rcu(km, &xfrm_km_list, list)
if (km->notify)
km->notify(x, c);
rcu_read_unlock();
}
void km_state_expired(struct xfrm_state *x, int hard, u32 portid)
{
struct km_event c;
c.data.hard = hard;
c.portid = portid;
c.event = XFRM_MSG_EXPIRE;
km_state_notify(x, &c);
内核SA定时器
内核在分配SA结构xfrm_state时,初始化mtimer定时器,处理函数为xfrm_timer_handler。
struct xfrm_state *xfrm_state_alloc(struct net *net)
{
struct xfrm_state *x;
x = kmem_cache_alloc(xfrm_state_cache, GFP_ATOMIC | __GFP_ZERO);
if (x) {
tasklet_hrtimer_init(&x->mtimer, xfrm_timer_handler,
CLOCK_BOOTTIME, HRTIMER_MODE_ABS);
x->curlft.add_time = ktime_get_real_seconds();
随后在xfrm_state_add(__xfrm_state_insert)函数或者xfrm_state_update函数中,将启动此定时器。
static void __xfrm_state_insert(struct xfrm_state *x)
{
tasklet_hrtimer_start(&x->mtimer, ktime_set(1, 0), HRTIMER_MODE_REL);
以下的超时处理函数xfrm_timer_handler,如果为软性过期,将调用km_state_expired发送SA过期消息。
static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me)
{
struct tasklet_hrtimer *thr = container_of(me, struct tasklet_hrtimer, timer);
struct xfrm_state *x = container_of(thr, struct xfrm_state, mtimer);
time64_t now = ktime_get_real_seconds();
if (x->lft.soft_add_expires_seconds) {
long tmo = x->lft.soft_add_expires_seconds + x->curlft.add_time - now;
if (tmo <= 0) {
warn = 1;
x->xflags &= ~XFRM_SOFT_EXPIRE;
} else if (tmo < next) {
next = tmo;
x->xflags |= XFRM_SOFT_EXPIRE;
x->saved_tmo = tmo;
}
}
x->km.dying = warn;
if (warn)
km_state_expired(x, 0, 0);
成员变量soft_add_expires_seconds的值在xfrm_state的构建函数xfrm_state_construct中,由子函数copy_from_user_state从应用层的数据结构xfrm_usersa_info->lft中拷贝而来。x->curlft.add_time的值在xfrm_state结构的分配函数xfrm_state_alloc中配赋予当前的值。
static void copy_from_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
{
memcpy(&x->id, &p->id, sizeof(x->id));
memcpy(&x->sel, &p->sel, sizeof(x->sel));
memcpy(&x->lft, &p->lft, sizeof(x->lft));
在strongswan文件strongswan-5.8.1/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c中,函数add_sa初始化了xfrm_lifetime_cfg结构相关的成员变量。其中soft_add_expires_seconds初始化为rekey的时长,而hard_add_expires_seconds初始化为lifttime的时长。
METHOD(kernel_ipsec_t, add_sa, status_t, private_kernel_netlink_ipsec_t *this, kernel_ipsec_sa_id_t *id, kernel_ipsec_add_sa_t *data)
{
/* we use lifetimes since added, not since used */
sa->lft.soft_add_expires_seconds = data->lifetime->time.rekey;
sa->lft.hard_add_expires_seconds = data->lifetime->time.life;
sa->lft.soft_use_expires_seconds = 0;
sa->lft.hard_use_expires_seconds = 0;
秘钥重协商
文件strongswan-5.8.1/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c中的函数process_expire接收处理内核的安全关联到期消息XFRM_MSG_EXPIRE。其调用内核接口的expire函数指针进行处理。
/** Process a XFRM_MSG_EXPIRE from kernel */
static void process_expire(private_kernel_netlink_ipsec_t *this, struct nlmsghdr *hdr)
{
struct xfrm_user_expire *expire;
expire = NLMSG_DATA(hdr);
protocol = expire->state.id.proto;
spi = expire->state.id.spi;
DBG2(DBG_KNL, "received a XFRM_MSG_EXPIRE");
if (protocol == IPPROTO_ESP || protocol == IPPROTO_AH)
{
dst = xfrm2host(expire->state.family, &expire->state.id.daddr, 0);
if (dst)
{
charon->kernel->expire(charon->kernel, protocol, spi, dst, expire->hard != 0);
dst->destroy(dst);
如下为内核接口结构kernel_interface_t的expire函数,其中调用注册在内核接口上的所有listener结构的expire成员函数。
METHOD(kernel_interface_t, expire, void, private_kernel_interface_t *this, uint8_t protocol, uint32_t spi, host_t *dst, bool hard)
{
kernel_listener_t *listener;
enumerator_t *enumerator;
this->mutex->lock(this->mutex);
enumerator = this->listeners->create_enumerator(this->listeners);
while (enumerator->enumerate(enumerator, &listener))
{
if (listener->expire && !listener->expire(listener, protocol, spi, dst, hard)) {
this->listeners->remove_at(this->listeners, enumerator);
}
文件strongswan-5.8.1/src/libcharon/kernel/kernel_handler.c中函数kernel_handler_create注册了listener结构,用于处理安全关联的到期事件,其中的expire函数如下,由函数rekey_child_sa_job_create处理SA的秘钥重协商。
METHOD(kernel_listener_t, expire, bool, private_kernel_handler_t *this, uint8_t protocol, uint32_t spi, host_t *dst, bool hard)
{
protocol_id_t proto = proto_ip2ike(protocol);
DBG1(DBG_KNL, "creating %s job for CHILD_SA %N/0x%08x/%H", hard ? "delete" : "rekey", protocol_id_names, proto, ntohl(spi), dst);
if (hard) {
lib->processor->queue_job(lib->processor,
(job_t*)delete_child_sa_job_create(proto, spi, dst, hard));
} else {
lib->processor->queue_job(lib->processor,
(job_t*)rekey_child_sa_job_create(proto, spi, dst));
}
文件strongswan-5.8.1/src/libcharon/processing/jobs/rekey_child_sa_job.c中函数execute为rekey_child_sa_job的执行函数。首先根据protocol,spi和dst三个参数获取到对应的ike_sa结构;接下来,执行ike_sa结构的rekey_child_sa函数。
METHOD(job_t, execute, job_requeue_t, private_rekey_child_sa_job_t *this)
{
ike_sa_t *ike_sa;
ike_sa = charon->child_sa_manager->checkout(charon->child_sa_manager, this->protocol, this->spi, this->dst, NULL);
if (ike_sa == NULL)
{
DBG1(DBG_JOB, "CHILD_SA %N/0x%08x/%H not found for rekey", protocol_id_names, this->protocol, htonl(this->spi), this->dst);
}
else
{
if (ike_sa->get_state(ike_sa) != IKE_PASSIVE)
{
ike_sa->rekey_child_sa(ike_sa, this->protocol, this->spi);
}
charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
}
以下为文件strongswan-5.8.1/src/libcharon/sa/ike_sa.c中的函数rekey_child_sa,其调用task_manager_t结构的两个指针函数:queue_child_rekey和initiate。
METHOD(ike_sa_t, rekey_child_sa, status_t, private_ike_sa_t *this, protocol_id_t protocol, uint32_t spi)
{
if (this->state == IKE_PASSIVE)
{
return INVALID_STATE;
}
this->task_manager->queue_child_rekey(this->task_manager, protocol, spi);
return this->task_manager->initiate(this->task_manager);
文件strongswan-5.8.1/src/libcharon/sa/ikev2/task_manager_v2.c中函数queue_child_rekey如下。其新增了一个任务task,此任务由函数child_rekey_create创建。
METHOD(task_manager_t, queue_child_rekey, void, private_task_manager_t *this, protocol_id_t protocol, uint32_t spi)
{
queue_task(this, (task_t*)child_rekey_create(this->ike_sa, protocol, spi));
以上任务的实现位于文件strongswan-5.8.1/src/libcharon/sa/ikev2/tasks/child_rekey.c中,在函数child_rekey_create中依据protocol的值为连接的initiator或者responder初始化任务的build和process函数指针。对于此处的moon网关,由于是连接的发起方,使用build_i和process_i函数。
child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, uint32_t spi)
{
private_child_rekey_t *this;
INIT(this,
.public = {
.task = {
.get_type = _get_type,
.migrate = _migrate,
.destroy = _destroy,
},
},
.ike_sa = ike_sa,
.protocol = protocol,
.spi = spi,
);
if (protocol != PROTO_NONE)
{
this->public.task.build = _build_i;
this->public.task.process = _process_i;
this->initiator = TRUE;
this->child_create = NULL;
看一下在同一个文件中的函数build_i,首先需要获取到需要重协商秘钥的子SA,如果inbound没有找到,再次由outbound方向进行查找,但是由于使用inbound方向进行rekey操作,在oubound方向找到SA之后,进一步查找inbound方向的spi。其次,如果没有获得到SA结构,意味值其已经不存在了;或者,此SA结构的状态不是CHILD_INSTALLED;再或者,此rekey任务已经有了child_create_t结构,即已经开启过重协商任务,但是其SA的状态又不等于CHILD_REKEYING,表明出现了错误,以上任一情况都结束函数,不在进行后续处理。
METHOD(task_t, build_i, status_t, private_child_rekey_t *this, message_t *message)
{
notify_payload_t *notify;
uint32_t reqid;
child_cfg_t *config;
this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, this->spi, TRUE);
if (!this->child_sa)
{ /* check if it is an outbound CHILD_SA */
this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, this->spi, FALSE);
if (this->child_sa) {
/* we work only with the inbound SPI */
this->spi = this->child_sa->get_spi(this->child_sa, TRUE);
}
}
if (!this->child_sa ||
(!this->child_create && this->child_sa->get_state(this->child_sa) != CHILD_INSTALLED) ||
(this->child_create && this->child_sa->get_state(this->child_sa) != CHILD_REKEYING))
{
/* CHILD_SA is gone or in the wrong state, unable to rekey */
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
config = this->child_sa->get_config(this->child_sa);
以下为正常的流程,在rekey任务结构中创建一个child_create_t结构,用于完成大部分重协商工作,此处继续使用上一次协商的DH组参数。以及继续使用reqid、mark和if_id值。之后,由刚刚创建的child_create_t结构中的任务函数build来完成rekey工作,其位于文件strongswan-5.8.1/src/libcharon/sa/ikev2/tasks/child_create.c中。另外,将在CREATE_CHILD_SA消息中增加REKEY_SA类型的通知(PLV2_NOTIFY)载荷。函数最后,将此SA的状态更新为CHILD_REKEYING。
/* our CHILD_CREATE task does the hard work for us */
if (!this->child_create) {
proposal_t *proposal;
uint16_t dh_group;
this->child_create = child_create_create(this->ike_sa, config->get_ref(config), TRUE, NULL, NULL);
proposal = this->child_sa->get_proposal(this->child_sa);
if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &dh_group, NULL))
{ /* reuse the DH group negotiated previously */
this->child_create->use_dh_group(this->child_create, dh_group);
}
}
reqid = this->child_sa->get_reqid(this->child_sa);
this->child_create->use_reqid(this->child_create, reqid);
this->child_create->use_marks(this->child_create, this->child_sa->get_mark(this->child_sa, TRUE).value,
this->child_sa->get_mark(this->child_sa, FALSE).value);
this->child_create->use_if_ids(this->child_create, this->child_sa->get_if_id(this->child_sa, TRUE),
this->child_sa->get_if_id(this->child_sa, FALSE));
if (this->child_create->task.build(&this->child_create->task, message) != NEED_MORE) {
schedule_delayed_rekey(this);
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
if (message->get_exchange_type(message) == CREATE_CHILD_SA) {
/* don't add the notify if the CHILD_CREATE task changed the exchange */
notify = notify_payload_create_from_protocol_and_type(PLV2_NOTIFY, this->protocol, REKEY_SA);
notify->set_spi(notify, this->spi);
message->add_payload(message, (payload_t*)notify);
}
this->child_sa->set_state(this->child_sa, CHILD_REKEYING);
同一个文件中的函数process_i处理rekey过程中的报文交互,可见其主要由child_create_t结构中的task任务处理rekey消息,最后,由child_delete_create负责删除之前的CHILD_SA。
METHOD(task_t, process_i, status_t, private_child_rekey_t *this, message_t *message)
{
if (this->child_create->task.process(&this->child_create->task, message) == NEED_MORE) {
/* bad DH group while rekeying, retry, or failure requiring deletion */
return NEED_MORE;
}
/* rekeying done, delete the obsolete CHILD_SA using a subtask */
this->child_delete = child_delete_create(this->ike_sa, protocol, spi, FALSE);
this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_delete;
this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_delete;
函数build_i_delete负责创建INFORMATIONAL类型的删除消息,通知对端删除指定的SA。
static status_t build_i_delete(private_child_rekey_t *this, message_t *message)
{
/* update exchange type to INFORMATIONAL for the delete */
message->set_exchange_type(message, INFORMATIONAL);
return this->child_delete->task.build(&this->child_delete->task, message);
文件strongswan-5.8.1/src/libcharon/sa/ikev2/tasks/child_delete.c中的函数build_i负责创建CHILD_SA的DELETE消息。
METHOD(task_t, build_i, status_t, private_child_delete_t *this, message_t *message)
{
INIT(entry,
.child_sa = child_sa,
.rekeyed = child_sa->get_state(child_sa) == CHILD_REKEYED,
);
this->child_sas->insert_last(this->child_sas, entry);
log_children(this);
build_payloads(this, message);
在接收到对端的响应之后,调用函数destroy_and_reestablish删除SA。在此函数中根据配置的delete_rekeyed_delay的值,执行一定的延迟操作。
METHOD(task_t, process_i, status_t, private_child_delete_t *this, message_t *message)
{
process_payloads(this, message);
DBG1(DBG_IKE, "CHILD_SA closed");
return destroy_and_reestablish(this);
报文流程
以下为报文交互流程:
其中MID等于2的两条CREATE_CHILD_SA消息为rekey消息,其中设置了REKEY_SA通知载荷:
最后MID等于3的两条消息,用于删除之前的SA连接。
strongswan版本: 5.8.1
内核版本: 5.0
END
来源:CSDN
作者:redwingz
链接:https://blog.csdn.net/sinat_20184565/article/details/103465418