目录

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_NETLINKSUBDIRS += plugins/kernel_netlink
if MONOLITHIClibcharon_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**)&current_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

strongswan ipsec 向内核下发SA和Policy部分相关推荐

  1. IPSEC点到多点(SA+NAT穿越)策略模板方式成功配置

    IPSEC点到多点(SA+NAT穿越)策略模板方式成功配置 USG5500A 与USG5500C.USG5500D建立IPSEC VPN Tunnel,其中USG5500C穿越USG5500B NAT ...

  2. Strongswan — IPSec 的 Linux 软件实现

    目录 文章目录 目录 Strongswan 安装配置 net2net-psk 网络拓扑 base configuration moon Site ipsec.conf ipsec.secrets st ...

  3. 一张图认识IPSec,区分IKE SA(ISAKMP SA)和IPSec SA

    IPSec工作在网络层,一般用于两个子网之间的通信. IPSec主要分为两个环节,主要信息可在我的思维导图中看到,十分清晰. 第一环节使用IKE(Internet Key Exchange)完成身份鉴 ...

  4. IKEv2协议中的EAP-TLS认证处理流程

    以下根据strongswan代码中的testing/tests/ikev2/rw-eap-tls-only/中的测试环境,验证一下IKEv2协议的EAP-TLS认证过程.拓扑结构如下: 拓扑图中使用到 ...

  5. 基于Strongswan的IPSec部署

    1. 简介 ​ IPSec全称Internet Protocol Security,是通过对IP协议的分组进行加密和认证来保护IP协议的网络传输协议集,IPSec的主要用途之一就是建立虚拟专用网络. ...

  6. 使用StrongSwan配置IPSec

    使用StrongSwan对IPSec进行研究,是一种很好的理解IPSec的实践.然而StrongSwan在使用的过程中实在是有太多的坑,网上的教程也多有不完整的地方,几乎没有能彻彻底底说明白每一步的, ...

  7. strongSwan:ipsec.conf – IPsec 的配置和连接

    配置文件描述 可选的ipsec.conf文件指定了strongSwan IPsec子系统的大多数配置和控制信息. 主要的例外是身份验证的机密:见ipsec.secrets(5).其内容不是安全敏感的. ...

  8. strongswan之ipsec.conf配置手册

    ipsec.conf是strongSwan的关键配置,文件指定了strongSwan IPsec子系统的大部分配置和控制信息.主要的例外是身份验证的密钥,配置保存在ipsec.secrets文件中. ...

  9. ## 使用strongswan和xl2tpd配置l2tp over ipsec和Xauth

    使用strongswan和xl2tpd配置l2tp over ipsec和Xauth yum install -y strongswan xl2tpd 编辑 /etc/strongswan/ipsec ...

最新文章

  1. 修复使用codeXmlDocument/code加载含有DOCTYPE的Xml时,加载后增加“[]”字符的错误...
  2. 接口隔离原则_设计模式六大原则
  3. Spring vs Seam
  4. java 怎么比较两个日期_如何在Java中比较两个日期?
  5. mvc ajax给control传值问题
  6. Java 数组排序及元素查找
  7. 管理数据库计算机网络,计算机网络与数据库管理系统.pdf
  8. Highcharts:小案例,自定义图片下载路径,中文乱码的解决办法(不足之处,求指点)。...
  9. 对vue饿了么项目重构之后的一些理解
  10. VS2019创建COM组件
  11. 正/负相比例放大中压摆率对电路带宽的影响分析
  12. 百度地图 LBS API 使用
  13. 军犬舆情热点:最高检明确正当防卫标准;ofo戴威称勇敢活下去
  14. 作为面试官,如何甄别应聘者的包装程度?
  15. 小程序(原生) 跳转页面的几种方法
  16. 《极限逃亡》12.3正式上线链游玩家|末日围城、文明重启
  17. STM32+ESP8266+MQTT连接阿里云服务器(四、STM32连接阿里云平台)
  18. 计算机实践学什么作用,大学计算机基础:计算机操作实践
  19. 对ActiveX控件进行注册
  20. 4.java基础-static

热门文章

  1. 2012中国新白领十项标准”——工作三五年的你满足了吗?!
  2. 读书笔记:《德鲁克管理思想精要》- 8 汇总
  3. Ubuntu之Anaconda3安装
  4. 【二维数组与稀疏数组的相互转化】
  5. 基于Wemos D1的智能感应开盖垃圾桶
  6. 梦想是一个天真的词,实现梦想是一个残酷的词。
  7. CCNA-思科认证网络支持工程师及薪金调查
  8. vue消息推送【个推】
  9. [Servlet 1] JSP基础知识
  10. python的html模块,python模块之HTMLParser