目录
kernel_netlink插件
插件初始化
add_sa和add_policy
kernel_netlink插件
strongswan ipsec 向内核下发SA和Policy部分可以是kernel_netlink插件的方式实现的。
其他的插件有:
Plugin Name | Description |
---|
kernel-libipsec | IPsec "kernel" interface in user-space using libipsec | kernel-netlink | IPsec/Networking kernel interface using Linux Netlink | kernel-pfkey | IPsec kernel interface using PF_KEY | kernel-wfp | IPsec backend for the Windows platform, using the Windows Filtering Platform |
libstrongswan/plugins/plugin_loader.c里面会将指定路径下的库文件当做strongswan的插件来加载,所以strongswan会把kernel_netlink编译成库文件,libcharon/Makefile文件中关键内容如下:
Makefile
am__append_108 = plugins/kernel_netlink
#am__append_109 = plugins/kernel_netlink/libstrongswan-kernel-netlink.la
/* file: libcharon/Makefile */
?Makeifle.am
if USE_KERNEL_NETLINK
SUBDIRS += plugins/kernel_netlink
if MONOLITHIC
libcharon_la_LIBADD += plugins/kernel_netlink/libstrongswan-kernel-netlink.la
endif
endif
?根据上述Makefile编译生成的libstrongswan-kernel-netlink.so文件如下:
strongswan$ grep kernel_netlink_plugin_create . -r
Binary file ./src/libcharon/plugins/kernel_netlink/.libs/libstrongswan-kernel-netlink.so matches
Binary file ./src/libcharon/plugins/kernel_netlink/.libs/kernel_netlink_plugin.o matches
./src/libcharon/plugins/kernel_netlink/kernel_netlink_plugin.c:plugin_t *kernel_netlink_plugin_create()
?libstrongswan/plugins/plugin_loader.c文件中 _load_plugins函数会遍历路径下的库文件来加载kenel-netlink插件。
METHOD(plugin_loader_t, load_plugins, bool, private_plugin_loader_t *this, char *list);
plugin_loader_t *plugin_loader_create()
{
private_plugin_loader_t *this;
INIT(this,
.public = {
.add_static_features = _add_static_features,
.load = _load_plugins,
.add_path = _add_path,
.reload = _reload,
.unload = _unload,
.create_plugin_enumerator = _create_plugin_enumerator,
.has_feature = _has_feature,
.loaded_plugins = _loaded_plugins,
.status = _status,
.destroy = _destroy,
},
.plugins = linked_list_create(),
.loaded = linked_list_create(),
.features = hashlist_create(
(hashtable_hash_t)registered_feature_hash,
(hashtable_equals_t)registered_feature_equals, 64),
.get_features = dlsym(RTLD_DEFAULT, "plugin_loader_feature_filter"),
);
if (!this->get_features)
{
this->get_features = get_features_default;
}
return &this->public;
}
/* file: libstrongswan/plugins/plugin_loader.c */
插件初始化
插件注册函数为kernel_ipsec_register();
# grep kernel_ipsec_register . -r
./plugins/kernel_wfp/kernel_wfp_plugin.c: PLUGIN_CALLBACK(kernel_ipsec_register, kernel_wfp_ipsec_create),
./plugins/load_tester/load_tester_plugin.c: PLUGIN_CALLBACK(kernel_ipsec_register, load_tester_ipsec_create),
./plugins/kernel_pfkey/kernel_pfkey_plugin.c: PLUGIN_CALLBACK(kernel_ipsec_register, kernel_pfkey_ipsec_create),
./plugins/kernel_netlink/kernel_netlink_plugin.c: PLUGIN_CALLBACK(kernel_ipsec_register, kernel_netlink_ipsec_create),
./plugins/kernel_libipsec/kernel_libipsec_plugin.c: PLUGIN_CALLBACK(kernel_ipsec_register, kernel_libipsec_ipsec_create),
./kernel/kernel_ipsec.c:bool kernel_ipsec_register(plugin_t *plugin, plugin_feature_t *feature,
./kernel/kernel_ipsec.h:bool kernel_ipsec_register(plugin_t *plugin, plugin_feature_t *feature,
?kernel_netlink插件中关键函数为kernel_netlink_plugin_create();
METHOD(plugin_t, get_features, int,
private_kernel_netlink_plugin_t *this, plugin_feature_t *features[])
{
static plugin_feature_t f[] = {
PLUGIN_CALLBACK(kernel_ipsec_register, kernel_netlink_ipsec_create),
PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"),
PLUGIN_CALLBACK(kernel_net_register, kernel_netlink_net_create),
PLUGIN_PROVIDE(CUSTOM, "kernel-net"),
};
*features = f;
return countof(f);
}
plugin_t *kernel_netlink_plugin_create()
{
private_kernel_netlink_plugin_t *this;
......
INIT(this,
.public = {
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
.reload = _reload,
.destroy = _destroy,
},
},
);
reload(this);
return &this->public.plugin;
}
/* file: libcharon/plugins/kernel_netlink/kernel_netlink_plugin.c */
?kernel_netlink_ipsec_create会对SA和Policy的管理接口初始化并初始化xfrm的用户态socket。
kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
{
private_kernel_netlink_ipsec_t *this;
bool register_for_events = TRUE;
INIT(this,
.public = {
.interface = {
.get_features = _get_features,
.get_spi = _get_spi,
.get_cpi = _get_cpi,
.add_sa = _add_sa,
.update_sa = _update_sa,
.query_sa = _query_sa,
.del_sa = _del_sa,
.flush_sas = _flush_sas,
.add_policy = _add_policy,
.query_policy = _query_policy,
.del_policy = _del_policy,
.flush_policies = _flush_policies,
.bypass_socket = _bypass_socket,
.enable_udp_decap = _enable_udp_decap,
.destroy = _destroy,
},
},
.policies = hashtable_create((hashtable_hash_t)policy_hash,
(hashtable_equals_t)policy_equals, 32),
.sas = hashtable_create((hashtable_hash_t)ipsec_sa_hash,
(hashtable_equals_t)ipsec_sa_equals, 32),
.bypass = array_create(sizeof(bypass_t), 0),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
.get_priority = dlsym(RTLD_DEFAULT,
"kernel_netlink_get_priority_custom"),
.policy_update = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.policy_update", FALSE, lib->ns),
.install_routes = lib->settings->get_bool(lib->settings,
"%s.install_routes", TRUE, lib->ns),
.proto_port_transport = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.set_proto_port_transport_sa",
FALSE, lib->ns),
);
if (streq(lib->ns, "starter"))
{ /* starter has no threads, so we do not register for kernel events */
register_for_events = FALSE;
}
this->socket_xfrm = netlink_socket_create(NETLINK_XFRM, xfrm_msg_names,
lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.parallel_xfrm", FALSE, lib->ns));
if (!this->socket_xfrm)
{
destroy(this);
return NULL;
}
setup_spd_hash_thresh(this, "ipv4", XFRMA_SPD_IPV4_HTHRESH, 32);
setup_spd_hash_thresh(this, "ipv6", XFRMA_SPD_IPV6_HTHRESH, 128);
if (register_for_events)
{
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
/* create and bind XFRM socket for ACQUIRE, EXPIRE, MIGRATE & MAPPING */
this->socket_xfrm_events = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
if (this->socket_xfrm_events <= 0)
{
DBG1(DBG_KNL, "unable to create XFRM event socket: %s (%d)",
strerror(errno), errno);
destroy(this);
return NULL;
}
addr.nl_groups = XFRMNLGRP(ACQUIRE) | XFRMNLGRP(EXPIRE) |
XFRMNLGRP(MIGRATE) | XFRMNLGRP(MAPPING);
if (bind(this->socket_xfrm_events, (struct sockaddr*)&addr, sizeof(addr)))
{
DBG1(DBG_KNL, "unable to bind XFRM event socket: %s (%d)",
strerror(errno), errno);
destroy(this);
return NULL;
}
lib->watcher->add(lib->watcher, this->socket_xfrm_events, WATCHER_READ,
(watcher_cb_t)receive_events, this);
}
netlink_find_offload_feature(lib->settings->get_str(lib->settings,
"%s.plugins.kernel-netlink.hw_offload_feature_interface",
"lo", lib->ns));
return &this->public;
}
/* file: libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c */
add_sa和add_policy
_add_sa();的实现函数为,根据SA数据,组装成NetLink消息体下发给内核
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)
{
netlink_buf_t request;
const char *alg_name;
char markstr[32] = "";
struct nlmsghdr *hdr;
struct xfrm_usersa_info *sa;
uint16_t icv_size = 64, ipcomp = data->ipcomp;
ipsec_mode_t mode = data->mode, original_mode = data->mode;
traffic_selector_t *first_src_ts, *first_dst_ts;
status_t status = FAILED;
.......
memset(&request, 0, sizeof(request));
format_mark(markstr, sizeof(markstr), id->mark);
......
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
hdr->nlmsg_type = data->update ? XFRM_MSG_UPDSA : XFRM_MSG_NEWSA;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct xfrm_usersa_info));
sa = NLMSG_DATA(hdr);
host2xfrm(id->src, &sa->saddr);
host2xfrm(id->dst, &sa->id.daddr);
sa->id.spi = id->spi;
sa->id.proto = id->proto;
sa->family = id->src->get_family(id->src);
sa->mode = mode2kernel(mode);
if (!data->copy_df)
{
sa->flags |= XFRM_STATE_NOPMTUDISC;
}
if (!data->copy_ecn)
{
sa->flags |= XFRM_STATE_NOECN;
}
if (data->inbound)
{
switch (data->copy_dscp)
{
case DSCP_COPY_YES:
case DSCP_COPY_IN_ONLY:
sa->flags |= XFRM_STATE_DECAP_DSCP;
break;
default:
break;
}
}
else
{
switch (data->copy_dscp)
{
case DSCP_COPY_IN_ONLY:
case DSCP_COPY_NO:
{
/* currently the only extra flag */
if (!add_uint32(hdr, sizeof(request), XFRMA_SA_EXTRA_FLAGS,
XFRM_SA_XFLAG_DONT_ENCAP_DSCP))
{
goto failed;
}
break;
}
default:
break;
}
}
switch (mode)
{
case MODE_TUNNEL:
sa->flags |= XFRM_STATE_AF_UNSPEC;
break;
case MODE_BEET:
case MODE_TRANSPORT:
if (original_mode == MODE_TUNNEL)
{ /* don't install selectors for switched SAs. because only one
* selector can be installed other traffic would get dropped */
break;
}
if (data->src_ts->get_first(data->src_ts,
(void**)&first_src_ts) == SUCCESS &&
data->dst_ts->get_first(data->dst_ts,
(void**)&first_dst_ts) == SUCCESS)
{
sa->sel = ts2selector(first_src_ts, first_dst_ts,
data->interface);
if (!this->proto_port_transport)
{
/* don't install proto/port on SA. This would break
* potential secondary SAs for the same address using a
* different prot/port. */
sa->sel.proto = 0;
sa->sel.dport = sa->sel.dport_mask = 0;
sa->sel.sport = sa->sel.sport_mask = 0;
}
}
break;
default:
break;
}
if (id->proto == IPPROTO_AH && sa->family == AF_INET)
{ /* use alignment to 4 bytes for IPv4 instead of the incorrect 8 byte
* alignment that's used by default but is only valid for IPv6 */
sa->flags |= XFRM_STATE_ALIGN4;
}
sa->reqid = data->reqid;
sa->lft.soft_byte_limit = XFRM_LIMIT(data->lifetime->bytes.rekey);
sa->lft.hard_byte_limit = XFRM_LIMIT(data->lifetime->bytes.life);
sa->lft.soft_packet_limit = XFRM_LIMIT(data->lifetime->packets.rekey);
sa->lft.hard_packet_limit = XFRM_LIMIT(data->lifetime->packets.life);
/* 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;
switch (data->enc_alg)
{
case ENCR_UNDEFINED:
/* no encryption */
break;
case ENCR_AES_CCM_ICV16:
case ENCR_AES_GCM_ICV16:
case ENCR_NULL_AUTH_AES_GMAC:
case ENCR_CAMELLIA_CCM_ICV16:
case ENCR_CHACHA20_POLY1305:
icv_size += 32;
/* FALL */
case ENCR_AES_CCM_ICV12:
case ENCR_AES_GCM_ICV12:
case ENCR_CAMELLIA_CCM_ICV12:
icv_size += 32;
/* FALL */
case ENCR_AES_CCM_ICV8:
case ENCR_AES_GCM_ICV8:
case ENCR_CAMELLIA_CCM_ICV8:
{
struct xfrm_algo_aead *algo;
alg_name = lookup_algorithm(ENCRYPTION_ALGORITHM, data->enc_alg);
if (alg_name == NULL)
{
DBG1(DBG_KNL, "algorithm %N not supported by kernel!",
encryption_algorithm_names, data->enc_alg);
goto failed;
}
DBG2(DBG_KNL, " using encryption algorithm %N with key size %d",
encryption_algorithm_names, data->enc_alg,
data->enc_key.len * 8);
algo = netlink_reserve(hdr, sizeof(request), XFRMA_ALG_AEAD,
sizeof(*algo) + data->enc_key.len);
if (!algo)
{
goto failed;
}
algo->alg_key_len = data->enc_key.len * 8;
algo->alg_icv_len = icv_size;
strncpy(algo->alg_name, alg_name, sizeof(algo->alg_name));
algo->alg_name[sizeof(algo->alg_name) - 1] = '\0';
memcpy(algo->alg_key, data->enc_key.ptr, data->enc_key.len);
break;
}
default:
{
struct xfrm_algo *algo;
alg_name = lookup_algorithm(ENCRYPTION_ALGORITHM, data->enc_alg);
if (alg_name == NULL)
{
DBG1(DBG_KNL, "algorithm %N not supported by kernel!",
encryption_algorithm_names, data->enc_alg);
goto failed;
}
DBG2(DBG_KNL, " using encryption algorithm %N with key size %d",
encryption_algorithm_names, data->enc_alg,
data->enc_key.len * 8);
algo = netlink_reserve(hdr, sizeof(request), XFRMA_ALG_CRYPT,
sizeof(*algo) + data->enc_key.len);
if (!algo)
{
goto failed;
}
algo->alg_key_len = data->enc_key.len * 8;
strncpy(algo->alg_name, alg_name, sizeof(algo->alg_name));
algo->alg_name[sizeof(algo->alg_name) - 1] = '\0';
memcpy(algo->alg_key, data->enc_key.ptr, data->enc_key.len);
}
}
if (data->int_alg != AUTH_UNDEFINED)
{
u_int trunc_len = 0;
alg_name = lookup_algorithm(INTEGRITY_ALGORITHM, data->int_alg);
if (alg_name == NULL)
{
DBG1(DBG_KNL, "algorithm %N not supported by kernel!",
integrity_algorithm_names, data->int_alg);
goto failed;
}
DBG2(DBG_KNL, " using integrity algorithm %N with key size %d",
integrity_algorithm_names, data->int_alg, data->int_key.len * 8);
switch (data->int_alg)
{
case AUTH_HMAC_MD5_128:
case AUTH_HMAC_SHA2_256_128:
trunc_len = 128;
break;
case AUTH_HMAC_SHA1_160:
trunc_len = 160;
break;
case AUTH_HMAC_SHA2_256_256:
trunc_len = 256;
break;
case AUTH_HMAC_SHA2_384_384:
trunc_len = 384;
break;
case AUTH_HMAC_SHA2_512_512:
trunc_len = 512;
break;
default:
break;
}
......
if (data->encap)
{
struct xfrm_encap_tmpl *tmpl;
tmpl = netlink_reserve(hdr, sizeof(request), XFRMA_ENCAP, sizeof(*tmpl));
if (!tmpl)
{
goto failed;
}
tmpl->encap_type = UDP_ENCAP_ESPINUDP;
tmpl->encap_sport = htons(id->src->get_port(id->src));
tmpl->encap_dport = htons(id->dst->get_port(id->dst));
memset(&tmpl->encap_oa, 0, sizeof (xfrm_address_t));
/* encap_oa could probably be derived from the
* traffic selectors [rfc4306, p39]. In the netlink kernel
* implementation pluto does the same as we do here but it uses
* encap_oa in the pfkey implementation.
* BUT as /usr/src/linux/net/key/af_key.c indicates the kernel ignores
* it anyway
* -> does that mean that NAT-T encap doesn't work in transport mode?
* No. The reason the kernel ignores NAT-OA is that it recomputes
* (or, rather, just ignores) the checksum. If packets pass the IPsec
* checks it marks them "checksum ok" so OA isn't needed. */
}
if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
{
goto failed;
}
if (data->tfc && id->proto == IPPROTO_ESP && mode == MODE_TUNNEL)
{ /* the kernel supports TFC padding only for tunnel mode ESP SAs */
if (!add_uint32(hdr, sizeof(request), XFRMA_TFCPAD, data->tfc))
{
goto failed;
}
}
......
status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
if (status == NOT_FOUND && data->update)
{
DBG1(DBG_KNL, "allocated SPI not found anymore, try to add SAD entry");
hdr->nlmsg_type = XFRM_MSG_NEWSA;
status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
}
......
status = SUCCESS;
failed:
memwipe(&request, sizeof(request));
return status;
}
/* file: libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c */
_add_policy的实现函数为,函数中会调用add_policy_internal();最终通过NeiLink message来向内核下发policy。
METHOD(kernel_ipsec_t, add_policy, status_t,
private_kernel_netlink_ipsec_t *this, kernel_ipsec_policy_id_t *id,
kernel_ipsec_manage_policy_t *data)
{
policy_entry_t *policy, *current;
policy_sa_t *assigned_sa, *current_sa;
enumerator_t *enumerator;
bool found = FALSE, update = TRUE;
char markstr[32] = "";
uint32_t cur_priority = 0;
int use_count;
/* create a policy */
INIT(policy,
.sel = ts2selector(id->src_ts, id->dst_ts, id->interface),
.mark = id->mark.value & id->mark.mask,
.if_id = id->if_id,
.direction = id->dir,
.reqid = data->sa->reqid,
);
format_mark(markstr, sizeof(markstr), id->mark);
/* find the policy, which matches EXACTLY */
this->mutex->lock(this->mutex);
current = this->policies->get(this->policies, policy);
if (current)
{
if (current->reqid && data->sa->reqid &&
current->reqid != data->sa->reqid)
{
DBG1(DBG_CFG, "unable to install policy %R === %R %N%s for reqid "
"%u, the same policy for reqid %u exists",
id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr,
data->sa->reqid, current->reqid);
policy_entry_destroy(this, policy);
this->mutex->unlock(this->mutex);
return INVALID_STATE;
}
/* use existing policy */
DBG2(DBG_KNL, "policy %R === %R %N%s already exists, increasing "
"refcount", id->src_ts, id->dst_ts, policy_dir_names, id->dir,
markstr);
policy_entry_destroy(this, policy);
policy = current;
found = TRUE;
policy->waiting++;
while (policy->working)
{
this->condvar->wait(this->condvar, this->mutex);
}
policy->waiting--;
policy->working = TRUE;
}
else
{ /* use the new one, if we have no such policy */
policy->used_by = linked_list_create();
this->policies->put(this->policies, policy, policy);
}
/* cache the assigned IPsec SA */
assigned_sa = policy_sa_create(this, id->dir, data->type, data->src,
data->dst, id->src_ts, id->dst_ts, id->mark,
id->if_id, data->sa);
assigned_sa->auto_priority = get_priority(policy, data->prio, id->interface);
assigned_sa->priority = this->get_priority ? this->get_priority(id, data)
: data->manual_prio;
assigned_sa->priority = assigned_sa->priority ?: assigned_sa->auto_priority;
/* insert the SA according to its priority */
enumerator = policy->used_by->create_enumerator(policy->used_by);
while (enumerator->enumerate(enumerator, (void**)¤t_sa))
{
if (current_sa->priority > assigned_sa->priority)
{
break;
}
if (current_sa->priority == assigned_sa->priority)
{
/* in case of equal manual prios order SAs by automatic priority */
if (current_sa->auto_priority > assigned_sa->auto_priority)
{
break;
}
/* prefer SAs with a reqid over those without */
if (current_sa->auto_priority == assigned_sa->auto_priority &&
(!current_sa->sa->cfg.reqid || assigned_sa->sa->cfg.reqid))
{
break;
}
}
if (update)
{
cur_priority = current_sa->priority;
update = FALSE;
}
}
policy->used_by->insert_before(policy->used_by, enumerator, assigned_sa);
enumerator->destroy(enumerator);
use_count = policy->used_by->get_count(policy->used_by);
if (!update)
{ /* we don't update the policy if the priority is lower than that of
* the currently installed one */
policy_change_done(this, policy);
DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u, "
"refcount %d]", id->src_ts, id->dst_ts, policy_dir_names,
id->dir, markstr, cur_priority, use_count);
return SUCCESS;
}
policy->reqid = assigned_sa->sa->cfg.reqid;
if (this->policy_update)
{
found = TRUE;
}
DBG2(DBG_KNL, "%s policy %R === %R %N%s [priority %u, refcount %d]",
found ? "updating" : "adding", id->src_ts, id->dst_ts,
policy_dir_names, id->dir, markstr, assigned_sa->priority, use_count);
if (add_policy_internal(this, policy, assigned_sa, found) != SUCCESS)
{
DBG1(DBG_KNL, "unable to %s policy %R === %R %N%s",
found ? "update" : "add", id->src_ts, id->dst_ts,
policy_dir_names, id->dir, markstr);
return FAILED;
}
return SUCCESS;
}
/* file: libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c */
参考资料:
[ipsec][strongswan] strongswan源码分析--(一)SA整体分析 - toong - 博客园 (cnblogs.com)https://www.cnblogs.com/hugetong/p/11143366.html
|