OpenStack IPSec VPNaaS ( by quqi99 )

作者:张华  发表于:2013-08-03
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明

http://blog.csdn.net/quqi99 )

今天想review一下社区IPSec VNPaaS Driver的代码,所以系统看了一下这背后的知识。笔记如下:

什么是VPN

VPN可以通过在L2或L3层建立一条逻辑链路让广域网上多个内网能够相互访问(我的理解,VPN除了让广域网的内网互通,还需要不同的用户tenant之间可用相同的内网地址,技术都是相通的,这样实际上它也就类似于Linux上的命名空间了在路由器上为每个用户有单独的路由)。有基于租用专用物理线路实现的(如帧中继和ATM,因为是专用线路,贵啊,但QoS好,对语音等对延迟敏感的数据流至关重要,可运行上层的IP,IPX, AppleTalk,IP多播多种协议);也有基于以太网的虚连接的实现方式(不是专用线路,灵活,数据通道不安全要结合如IPSec来保证).分类如下:

  • ATM和帧中继都是L2层VPN

  • GRE,L2TP,MPLS和IPSec属于L3层VPN技术

  • GRE VPN,建VPN很方便,但没有安全机制,需结合IPSec来支持身份验证、完整性、访问控制和机密性。

  • SSL VPN,这种VPN的最大好处在于,仅需要一个单独的TCP或UDP端口便可以轻易穿越大多数防火墙进行数据传送,OpenVPN是在Linux系统上最好的实现。
    nova-network使用的cloudpipe,就是ssl
    vpn,可在虚机上安装openvpn,这样在广域网内网的这个虚机,所以它实际上是host-to-host的。而OpenStack现在基于
    openswan实现的IPSec VPNaaS是net-to-net的,但IPSec也是支持host-to-net的

  • MPLS VPN,当使用GRE这些遂道技术来实现VPN时,因为是点至点的,当新添一个站点,需修改每个站点的VPN配置。MPLS是在传数据之前就先用L3层的路由机制将L2层的标签在途径的每个路由器上都事先算好了,并且这种标签是自动配置的,所以添加一个新站点很方便。

IPSec

先回忆一下加密算法,对称算法是IPSecVPN使用较多的加密算法,它使用Internet密钥交换IKE协议来分发密钥(pluto是openswan用的一个IKE实现,charon是strongswan用的一个IKE实现):

  • 对称算法,如3DES,AES,公钥加密,私钥解密,公钥是公开的,公钥和私钥在数学上是相关的可以从一种推导到另一种,所以如何通过安全通信渠道来传输私钥是一个大课题。

  • 不对称算法,公钥和私钥数学上不相关,无法从一种推导到另一种,所以解决了对称算法如何安全传输私钥的问题。但不对称算法加密速度慢啊。所以一般采用对称算法来加密数据,用不对称算法再来解决对称算法的密钥的分发问题。

  • 数字签名,加密一般是用公钥加密然后用私钥解密的,如果用私钥来加密的话将创建一个数字签名。数字签名不可逆,如MD5、SHA.

IPSec是用来保护数据报文的,所以它有两种IPSec协议:

  • 一是保护数据报头的,叫AH(验证报头),它使用消息摘要算法生成一个散列值,包括不变的报头字段(如源IP,目标IP),不同于ESP,不提供机密性,即不加密数据本身,所以不是很有用。计算报头摘要时要排除一些可变字段(如服务类型ToS,标记,分段偏移、存活时间TTL和报头校验和)。关于IPSec与NAT,NAT要发挥作用,它可能要修改IP源地址、目标地址、源端口、目标端口、IP和TCP报头的校验和、TCP序列号和确认号以及有效负载中的IP地址。因为,使用AH时,NAT不可行。

  • 二是保护数据本身的,叫ESP(封装安全有效负载),提供机密性、数据完整性、数据来源验证和反重放功能。对于IPSec传输模式,使用ESP时NAT依然不可行。但在遂道模式下,只要NAT执行1:1的地址转换,不将多个内网地址转换一个外部地址并使用端口来区分它们,NAT和ESP便可以共存。一个最简单的办法就是在IPSec之间执行NAT,但这并非总是可能的,所以有了NAT跨越技术(NAT Traversal, NAT-T),它由三部分组成,首先判断远程peer是否支持NAT跨越,其次是检测peer之间的路径上是否有NAT;最后是决定如何使用UDP封闭来处理NAT。

同时,IPSec有两种重要的使用模式:

  • IPSec传输模式:传输过程中IPSec源端点不会修改IP报中的目标IP地址

  • IPSec遂道模式:传输过程中IPSec源端点会修改IP报中的目标IP地址

  • 仅用IPSec就可以实现VPN了,但途径的每一个路由器都得配置复杂的IPSec配置,非常麻烦。并且IPSec只是加解密IP数据流,无法处理多播或广播IP数据流,所以多播无法通IPSec遂道;另外,很多路由协议(如EIGRP,OSPF, RIPv2)使用了多播,因此不能在IPSecpeer之间来配置动态路由。结合GRE可以克服这些缺点。

  • 结合GRE和IPSec提供VPN功能时,既可以采用遂道模式,又可以采用传输模式。

ESP协议有如下重要字段,它提供机密性、数据完整性、数据来源验证和反重放功能:

  • 安全参数索引(SPI),是一个32位的数,功能类似于索引号,与IP报头中的目标地址一起用于在安全关联数据库(SADB)中查找安全关联(SA安全关联SA有用于控制流的IKE(ISAKMP) Sa和用于数据流的IPSecSA两种

  • 序列号:唯一且单调递增的数字,它和滑动窗口配置使用提供全安中的反重放功能

  • 有效负载:被加密保护的数据放在这个字段里,提供安全中的数据机密性功能

  • 填充字节:用于增加ESP报头的位数,填充的位数取决于使用的加密算法。

  • 难证数据:验证摘要用于提供安全中的数据完整性功能

上面说了安全参数索引SPI和目标IP一起用于在安全关联数据库SADB中查找安全关联SA,SA分用于控制流的IKE(ISAKMP)Sa(基于UDP)和用于数据流的IPSec Sa两种。IPSec模块有两个数据库:

  • 安全策略数据库(SPD它定义rule,字段有:目标IP、源IP、名称、数据敏感性等级、传输层协议、源和目标端口

  • 安全关联数据库(SADB),定义action,字段有:序列号、序列号溢出(大于这个数了就必须删除该SA,并和IPSecPeer重新商量一个SA)、反重放窗口、SA寿命、模式(是传输模式还是遂道模式)、AH验证算法、ESP验证算法、ESP加密算法、路径MTU。

再看一下VPN所谓的高级特性。技术都是相通的,以前研发中间件应用服务器的基础在这里经常能引起联想,换汤不换药,名字叫法不同而已。IPSec控制面协议(IKE)是基于UDP无联结的,IPSec peer之间的IKE和安全关联SA在寿命到期之间一直存在,但某一个peer出故障的话,另一个peer不知道仍然会发数据浪费CPU周期,所以可以配置IKE存活消息(在中间件里就叫心跳检查)crypto isakmp keep alive 60 3。

但这个存活消息也只能检查peer是否出故障,万一是peer背后的子网还可达呢?IKE存活机制将无能为力,所以又出现一个失效对待体检测(DPD)技术,它认为两个peer之间有数据流的话则无需发送存活消息检测对等体的存活性,因为IPSec数据流隐式证明了peer是可用的。这样推迟DPD交换(需要发送数据流时才进行),DPD避免了发送多途的消息,避免了强迫IPSec重新创建密钥。

空闲超时是IPSec的另一个特性。对于空闲的peer,长期不活动就先删除SA节约内存,要用的时候再重新创建SA。(crypto ipsec security-association idel-time 120)

反向路由注入RRI,前往远端peer必须存在路由,这个路由可以静态设置,也可以通过推导得出。特别对于远程接入型的vpn,由于客户端的ip是动态变的,那么服务端就很难通过静态设置,那么可以通过反向路由注入到服务端。(reverse-routeremote-perr 9.1.1.33)

RRI和HSRP,HSRP用于在路由器之间进行故障切换(failover),在中间件里叫HA,通过一个VIP地址作为一些冗余设备的共用IP。有状态故障切换是通过SADB传输和同步实现的,这就是中间件领域里的Session复制啊。按铁道部发言人的说法,不管你懂不懂,反正我想到这里不看后面的也懂了。

IPSec在Linux下的实现OpenSwan

IPSec在Linux上的实现Frees/wan项目如今已经分裂为openswan和strongswan。openswan基于自身的XAUTH扩展,可以作为Cisco等各家VPN集成产品的客户端。ipsec可以让我们在不改变外部防火墙规则的情况下,在内核级别对什么能通过遂道作出安全的处理实现NET-TO-NET以及host-to-net的配置。尽管已经对NAT-Travel的支持做了不少改进,但还是不能较好的工作于一些NAT网关之后。

下面参考了《使用Openswan构建企业级VPN》一文,但修复了一些因软件过时产生的问题。http://mos1989.blog.51cto.com/4226977/1109450

拓扑:一个site-to-site的vpn,两个局域网(172.16.5.0/24与172.16.6.0/24)通过两个网关(路由器)相连(网关两块网卡,网关vpn1的IP是:172.16.5.11,与192.168.0.101,网关vpn2的IP是:172.16.6.11与192.168.0.102),一台测试用机192.168.0.103。这里使用192.168.0.0/24网段模拟公网。

1, 安装:sudoyum install make gcc gmp-devel bison flex lsofopenswan

2, 启动:sudo service ipsec start

3, sudo vi /etc/sysctl.conf,然后sudo sysctl -p使之生效

net.ipv4.ip_forward =1

net.ipv4.conf.default.accept_redirects=0 #让NETKEY(即netlink)不要发假的ICMP转发

net.ipv4.conf.default.send_redirects=0

4, 检查系统是否满足要求:

sudo ipsec verify

5, 配置加密key,默认会调用/dev/random随机数设备,一旦系统中断的IRQS的随机数不够用,将会产生大量的等待时间,所以将random替换成urandom

sudo mv/dev/{random,random.bak}

sudo ln -s /dev/urandom/dev/random

sudo certutil -N -d/etc/ipsec.d

sudo ipsec newhostkey--output /etc/ipsec.secrets --bits 1024 --verbose --configdir/etc/pki/nssdb/

在fedora上有两个nss数据库文件,/etc/ipsec.d与/etc/pki/nssdb,前一个好像坏了(所以目前的OpenStack IPSec VPNaaS只能在ubuntu上运行,我评审代码时提到过这个问题,但作者说VPNaaS现在只是实验性的,它下一阶段将完善对不同平台的支持,我就不好再说什么了),所以用用--configdir指定指用/etc/pki/nssdb。

6, 配置,在/etc/ipsec.conf文件上追加下列内容

conn %default

type=tunnel

keyingtries=0

disablearrivalcheck=no

authby=secret

esp=3des-sha1

ike=3des-sha1-modp1024

keylife=8h

keyexchange=ike

left=IPA

pfs=no

conn v1-2 #给vpn1和vpn2之间的链接命个名字,行首不要有空格,否则报错

left=192.168.0.101 #vpn1的IP

leftsubnet=172.16.5.0/24 #vpn1的eth1的网段,此网段的主机需要将网关指向eth1

leftid=@left #给vpn1命个名字,多个配置段时,切勿重复

leftnexthop=%defaultroute #指定网关作为下一条默认路由

leftrsasigkey=<sudo ipsec showhostkey --left>

right=192.168.0.102 # vpn2的IP

rightsubnet=172.16.6.0/24 #vpn2的eth1的网段,此网段的主机需要将网关指向eth1

rightid=@right #给vpn2命个名字,多个配置段时,切勿重复

rightnexthop=%defaultroute #指定网关作为下一条默认路由

rightrsasigkey=< sudo ipsec showhostkey --right>

auto=start #服务启动时自动生效,可配置auto=add,服务器启动后,使用#ipsec auto --up v1-2启动

7, 调试手段,在两个网关上重启服务(sudoservice ipsec restart),这时候应该用sudo service ipsec status命令看到遂道已建成。

在vpn1上使用tcpdump抓包查看从左边vpn子网发到子边vpn子网的ping包。

Tcpdump -i eth0 -n host 192.168.0.101 and192.168.0.102

OpenStack IPSec VPNaaS

打好了上面的基础之后,我们来看一下OpenStack VPNaaS。

IPSec VPN (Agent Impl) Specs

https://blueprints.launchpad.net/quantum/+spec/ipsec-vpn-reference

Related BPs

https://blueprints.launchpad.net/quantum/+spec/vpnaas-python-apis

Spec

https://wiki.openstack.org/wiki/Quantum/VPNaaS

Installation manual
https://wiki.openstack.org/wiki/Quantum/VPNaaS/HowToInstall

https://docs.google.com/document/d/1Jphcvnn7PKxqFEFFZQ1_PYkEx5J4aO5J5Q74R_PwgV8/edit#

模块架构如下:

逻辑架构,它即router上安装openswan来为内部的子网提供vpn服务:

从local_cidr到peer_cidr的ipsec流量不应该做snat规则:

POSTROUTING-s <local_cidr> -d <peer_cidr>-m policy --dirout --polipsec-j ACCEPT

部署架构如下,显然,这个实现采用IPSec自身的遂道实现,未使用GRE

用两个节点来模拟测试VPN,注意:下面的外网网段用了一致的172.24.4.0/24,只是网关不一样,原因是目前multi-host功能还没有提交到社区可以想象:

  • 一个OpenStack云下不同子网通过VPN通信,遂道两端只需要用管理网段IP即可,网关会是一样的,那没什么。

  • 不同数据中心的OpenStack的子网通过VPN通信,遂道两端直接用浮动IP即可,因为别忘了strongwan本来就是运行在l3-agent节点上的。至此,理论全想通顺了

 (10.1.0.0/24 - DevStack East)
|
| 10.1.0.1
[Quantum Router]
| 172.24.4.226
|
| 172.24.4.225
[Internet GW]
|
|
[Internet GW]
| 172.24.4.232
|
| 172.24.4.233
[Quantum Router]
| 10.2.0.1
|
(10.2.0.0/24 DevStack West)
在East上的配置:

neutron vpn-ikepolicy-create ikepolicy1

neutron vpn-ipsecpolicy-create ipsecpolicy1

neutron vpn-service-create --name myvpn --description "My vpn service" --subnet-idmysubnet --router-id router1

neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-idmyvpn --ikepolicy-id ikepolicy1 --ipsecpolicy-id ipsecpolicy1 --peer-address 172.24.4.233 --peer-id 172.24.4.233  --peer-cidr 10.2.0.0/24 --psk secret

在West上的配置:

neutron vpn-ikepolicy-create ikepolicy1

neutron vpn-ipsecpolicy-create ipsecpolicy1

neutron vpn-service-create --name myvpn --description "My vpn service"--subnet-id mysubnet --router-id router1

neutron ipsec-site-connection-create --name vpnconnection1 --vpnservice-idmyvpn --ikepolicy-id ikepolicy1 --ipsecpolicy-id ipsecpolicy1 --peer-address 172.24.4.226 --peer-id 172.24.4.226 --peer-cidr10.1.0.0/24 --psk secret

最终它会生成下列的strongwan的配置:

{%for ipsec_site_connection in vpnservice.ipsec_site_connections

%}conn{{ipsec_site_connection.id}}

#NOTE: a default route is required for %defaultrouteto work...

left={{vpnservice.external_ip}}

leftid={{vpnservice.external_ip}}

auto={{ipsec_site_connection.initiator}}

#NOTE:REQUIRED

#[subnet]

leftsubnet={{vpnservice.subnet.cidr}}

#leftsubnet=networkA/netmaskA,networkB/netmaskB (IKEv2 only)

leftnexthop=%defaultroute

######################

#ipsec_site_connections

######################

#[peer_address]

right={{ipsec_site_connection.peer_address}}

#[peer_id]

rightid={{ipsec_site_connection.peer_id}}

#[peer_cidrs]

rightsubnet={{ipsec_site_connection['peer_cidrs']|join(',')}}

#rightsubnet=networkA/netmaskA,networkB/netmaskB (IKEv2 only)

rightnexthop=%defaultroute

#[mtu]

#Note It looks like not supported in thestrongswandriver

#ignore it now

#[dpd_action]

dpdaction={{ipsec_site_connection.dpd_action}}

#[dpd_interval]

dpddelay={{ipsec_site_connection.dpd_interval}}s

#[dpd_timeout]

dpdtimeout={{ipsec_site_connection.dpd_timeout}}s

#[auth_mode]

authby={{ipsec_site_connection.auth_mode}}

######################

#IKEPolicyparams

######################

#[ike_version]

keyexchange=ike{{ipsec_site_connection.ikepolicy.ike_version}}

#[encryption_algorithm]-[auth_algorithm]-[pfs]

ike={{ipsec_site_connection.ikepolicy.encryption_algorithm}}-{{ipsec_site_connection.ikepolicy.auth_algorithm}}-{{ipsec_site_connection.ikepolicy.pfs}}!

#[phase1_negotiation_mode]

#NOTEUNSUPPORTED before 5.0.0(http://wiki.strongswan.org/projects/strongswan/wiki/FAQ)

#ignore it now

#[lifetime_value]

ikelifetime={{ipsec_site_connection.ikepolicy.lifetime_value}}

#NOTE: it looks lifetime_units=kilobytes can't be enforced (could beseconds, hours, days...)

##########################

#IPsecPolicysparams

##########################

#[transform_protocol]

auth={{ipsec_site_connection.ipsecpolicy.transform_protocol}}

#[encryption_algorithm]-[auth_algorithm]-[pfs]

esp={{ipsec_site_connection.ipsecpolicy.encryption_algorithm}}-{{ipsec_site_connection.ipsecpolicy.auth_algorithm}}-{{ipsec_site_connection.ipsecpolicy.pfs}}!

#NOTE: different DH group for PFS inespparam is IKEv2 only

#[encapsulation_mode]

type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}}

#[lifetime_value]

lifetime={{ipsec_site_connection.ipsecpolicy.lifetime_value}}s

#lifebytes=100000 iflifetime_units=kilobytes (IKEv2 only)

{%endfor%}

对应的数据结构如下,而在AWS中IKE和IPSecPolicies都是预定义的。

现在到了直接研究一下代码的时候了,https://review.openstack.org/#/c/33148/

首先它使用Jinja2做为模板引擎,采用strongswan作为IPSec实现,同时引入了一个包装命令:neutron-netns-wrapper,原因是strongman的一些配置路径都是硬编码的,如pid,socket文件等,而现在要求在每一个namespace下启动一个strongman进程(这个和我之前想的在每一个命名空间下运行一个quagga类似),它的底层实现原理是mount --bind命令,如:mount –bind /tmp/hosts /etc/hosts命令,对/tmp/hosts文件进行读写,就相当于对/etc/hosts在读写,但/etc/hosts不会真被写。用完之后执行umount/etc/hosts断开绑定。

ip netns exec test_ns neutron-netns-wrapper -d--cmd "ipsec,start" \
--mount_paths/etc:/some_dir/etc,/var/run:/some_dir/var/run \
--rootwrap_config/etc/neutron/rootwrap.conf

VPN Plguin定义操作数据库,定义了数据表结构及增删改查的方法:IPsecPeerCidr,IpsecPolicy, IKEPolicy, IpsecSiteConnection, VPNService。

VPNDriverPlugin操作数据库的同时也会调用具体的Driver,还会涉及到RPC调用:

update_status_on_host(self,context, host, active_services),它会通过本机的hostname查找到l3-agent,然后再找出它上面的所有VPNService,然后通过传来的active_services参数决定是否将某些VPNService记录从数据库层面设置成Active或Error状态。

defget_driver_by_vpnservice(self,vpnservice)

对于具体的ServiceDriver做两件事:调用plugin中的数据库操作,且调用vpn agent的RPC操作(IPsecVpnAgentApi).

是$neutron/neutron/services/vpn/serivce_drivers/ipsec.py中的IPsecVPNDriver(它会实现$neutron/neutron/services/vpn/serivce_drivers/__init__.py定义了一些接口)。

Plugin自带提供的回调函数IPsecVpnDriverCallBack,

vpnagent的RPC操作(IPsecVpnAgentApi)有方法:defvpnservice_updated(self,context, router_id),

在具体实现类strongswan.py中有一个周期性的方法sync,它找到和它关联的所有vnpservices

本文转自http://blog.csdn.net/quqi99/article/details/9734251,

另外,http://openstack-huawei.github.io/VPNaaS/ 提供了一个不错的使用步骤

[转] OpenStack IPSec VPNaaS的更多相关文章

  1. Neutron 理解(10):虚拟专用网(VPN)虚拟化 [How Neutron implements VPN Virtualization]

    学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...

  2. OpenStack 企业私有云的若干需求(4):混合云支持 (Hybrid Cloud Support)

    本系列会介绍OpenStack 企业私有云的几个需求: 自动扩展(Auto-scaling)支持 多租户和租户隔离 (multi-tenancy and tenancy isolation) 混合云( ...

  3. [转] OpenStack Kilo 更新日志

    OpenStack 2015.1.0 (Kilo)更新日志 原文: https://wiki.openstack.org/wiki/ReleaseNotes/Kilo/zh-hans 目录  [隐藏] ...

  4. Openstack实践(1)部署使用实例及neutron网络

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者.原文超链接 ,博主地址:http://www.cnblogs.com/SuperXJ/ 如何快速部署使用openstack,使用kolla吧,o ...

  5. RedHat 和 Mirantis OpenStack 产品的版本和功能汇总和对比(持续更新)

    Mirantis 和 Red Hat 作为 OpenStack 商业化产品领域的两大领军企业,在行业内有重要的地位.因此,研究其产品版本发布周期和所支持的功能,对制定 OpenStack 产品的版本和 ...

  6. OpenStack调研:OpenStack是什么、版本演变、组件关系(Havana)、同类产品及个人感想

    一点调研资料,比较浅,只是觉得部分内容比较有用,记在这里: 首先,关于云计算,要理解什么是SAAS.PAAS.IAAS,这里不述:关于虚拟化,需要知道什么是Hypervisor,这里也不述: Open ...

  7. Neutron 理解 (9): OpenStack 是如何实现 Neutron 网络 和 Nova虚机 防火墙的 [How Nova Implements Security Group and How Neutron Implements Virtual Firewall]

    学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...

  8. 理解 OpenStack 高可用(HA)(3):Neutron 分布式虚拟路由(Neutron Distributed Virtual Routing)

    本系列会分析OpenStack 的高可用性(HA)概念和解决方案: (1)OpenStack 高可用方案概述 (2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议) (3)N ...

  9. 理解 OpenStack 高可用(HA)(1):OpenStack 高可用和灾备方案 [OpenStack HA and DR]

    本系列会分析OpenStack 的高可用性(HA)概念和解决方案: (1)OpenStack 高可用方案概述 (2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议) (3)N ...

随机推荐

  1. Educational Codeforces Round 41 (Rated for Div. 2)

    这场没打又亏疯了!!! A - Tetris : 类似俄罗斯方块,模拟一下就好啦. #include<bits/stdc++.h> #define fi first #define se ...

  2. 【Java】 归并排序的非递归实现

    归并排序可以采用递归方法(见:归并排序),但递归方法会消耗深度位O(longn)的栈空间,使用归并排序时,应该尽量使用非递归方法.本文实现了java版的非递归归并排序. 更多:数据结构与算法合集 思路 ...

  3. MySQL查询语句执行过程及性能优化-查询过程及优化方法(JOIN/ORDER BY)

    在上一篇文章MySQL查询语句执行过程及性能优化-基本概念和EXPLAIN语句简介中介绍了EXPLAIN语句,并举了一个慢查询例子:

  4. 洛谷P2894 [USACO08FEB]酒店Hotel [线段树]

    题目传送门 酒店 题目描述 The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and ...

  5. JdbcTemplate使用小结

    org.springframework.jdbc.core.JdbcTemplate.query(String sql, Object[] args, RowMapper<StaffUnionV ...

  6. php模板引擎之blade

    一.简介模板引擎 模板引擎是将网站的页面设计和PHP应用程序几乎完全分离的一种解决方案,它能让前端工程师专注页面搭建,让后台工程师专注功能实现,以便实现逻辑分离,让每个人发挥所长.模板引擎技术的核心是 ...

  7. Winform给TextBox设置默认值(获取焦点后默认值消失)

    主要是通过TextBox的获取焦点Enter和失去焦点Leave两个事件来实现的, 思路如下: 1.设置一个字符串常量,作为TextBox的默认值: 2.在界面的构造方法中将默认值赋值给TextBox ...

  8. Bzoj4818:生成函数 快速幂

    转来的题面:首先这题显然补集转化,就是用全部方案减去不含任何质数的方案.然后怎么做呢?考虑m比较小,我们能大力把<=m的质数全都筛出来.发现n很大,要么倍增要么快速幂......发现p相当小,所 ...

  9. STM32 TIMER REGISTER

  10. AT91 USB Composite Driver Implementation

    AT91 USB Composite Driver Implementation 1. Introduction The USB Composite Device is a general way t ...