热门标签 | HotTags
当前位置:  开发笔记 > 程序员 > 正文

Cloudstack平台安全研究

面向Cloudstack4.2SecurityGroupXenServer6.x,对于KVM原理一样,需要和Agent通信,调用security_group.py。安全组是AWS里的一种技术,用于隔离多租户虚拟机,Cloudstack也支持安全组,3.0.x版本只支持BasicZone的安全组,4.1以后引入Advan

面向Cloudstack4.2 Security Group XenServer 6.x ,对于KVM原理一样,需要和Agent通信,调用security_group.py。

安全组是AWS里的一种技术,用于隔离多租户虚拟机,Cloudstack也支持安全组,3.0.x版本只支持Basic Zone的安全组,4.1以后引入Advance Shared Zone的安全组,目前安全组支持的Hypervisor为KVM 和Xen系列,对于VMware可以使用PVLAN也可以使用vShield实现,需要独立开发。

首先要创建带安全组的Shared网络服务,创建Zone,创建VM时,选择新创建的安全组或默认的。如果不选择安全组,会使用默认的安全组。默认情况下,安全组会将VM的Deny All Ingress ,Allow ALL Egress. 

对于Xenserver 如果要使用安全组,需要将网络从OVS改为Bridge。在xenserver机器执行

#cat /etc/xensource/network.conf
openvswitch

执行改为bridge或者手动改为bridge,并重启主机。

# xe-switch-network-backend bridge

另外需要确认以下这些值,如下表示OK。

#sysctl -a|grep bridge
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-arptables = 1

如果不是,修改

/etc/sysctl.conf
#sysctl -p

CitrixResourceBase.java 是负责和XenServer交互的底层资源类。在VM启动时,如果启用安全组,将建立默认规则(Ingress Allow,Egress Deny).

方法为 execute(StartCommand cmd),里面代码片段:

if (_canBridgeFirewall) {
    String result = null;
    if (vmSpec.getType() != VirtualMachine.Type.User) {
        NicTO[] nics = vmSpec.getNics();
        boolean secGrpEnabled = false;
        for (NicTO nic : nics) {
            if (nic.isSecurityGroupEnabled()
                    || (nic.getIsolationUri() != null && nic.getIsolationUri().getScheme()
                            .equalsIgnoreCase(IsolationType.Ec2.toString()))) {
                secGrpEnabled = true;
                break;
            }
        }
        if (secGrpEnabled) {
            result = callHostPlugin(conn, "vmops", "default_network_rules_systemvm", "vmName", vmName);
            if (result == null || result.isEmpty() || !Boolean.parseBoolean(result)) {
                s_logger.warn("Failed to program default network rules for " + vmName);
            } else {
                s_logger.info("Programmed default network rules for " + vmName);
            }
        }
    } else {
        // For user vm, program the rules for each nic if the isolation
        // uri scheme is ec2
        NicTO[] nics = vmSpec.getNics();
        for (NicTO nic : nics) {
            if (nic.isSecurityGroupEnabled() || nic.getIsolationUri() != null
                    && nic.getIsolationUri().getScheme().equalsIgnoreCase(IsolationType.Ec2.toString())) {
                List nicSecIps = nic.getNicSecIps();
                String secIpsStr;
                StringBuilder sb = new StringBuilder();
                if (nicSecIps != null) {
                    for (String ip : nicSecIps) {
                        sb.append(ip).append(":");
                    }
                    secIpsStr = sb.toString();
                } else {
                    secIpsStr = "0:";
                }
                result = callHostPlugin(conn, "vmops", "default_network_rules", "vmName", vmName, "vmIP",
                        nic.getIp(), "vmMAC", nic.getMac(), "vmID", Long.toString(vmSpec.getId()), "secIps",
                        secIpsStr);
                if (result == null || result.isEmpty() || !Boolean.parseBoolean(result)) {
                    s_logger.warn("Failed to program default network rules for " + vmName + " on nic with ip:"
                            + nic.getIp() + " mac:" + nic.getMac());
                } else {
                    s_logger.info("Programmed default network rules for " + vmName + " on nic with ip:"
                            + nic.getIp() + " mac:" + nic.getMac());
                }
            }
        }
    }
}

该方法将判断主机网络是否为bridge,如果不是就不会建立安全组规则。将遍历VM所有网卡,判断是否启用安全组隔离类型为EC2,即安全组隔离。

对于System VM(vrouter,ssvm,cpvm),要单独处理,如果是System VM,会调用主机plugin vmops里的方法default_network_rules_systemvm 参数为VM名字,如果是User VM,会调用default_network_rules,参数为 vmName, vmIP ,vmMAC,,  secIps,secIps表示网卡的第二个IP地址,cloudstack支持一个网卡多个IP地址。

主机Plugin vmops 位于cloudstack 代码树中cloudstack/scripts/vm/hypervisor/xenserver,在Xenserver主机/etc/xapi.d/plugins/

Cloudstack对于Xenserver和KVM的安全组主要使用Iptables,ebtable ,ipset,arptables实现。

System VM 
def default_network_rules_systemvm(session, args):
    ......
    vmchain = chain_name(vm_name)
   .......
    delete_rules_for_vm_in_bridge_firewall_chain(vm_name)
    #建立iptables chain
    try:
        util.pread2(['iptables', '-N', vmchain])
    except:
        util.pread2(['iptables', '-F', vmchain])
    #允许所有出流量
    allow_egress_traffic(session)
    #遍历vif
    for vif in vifs:
        try:
            util.pread2(['iptables', '-A', 'BRIDGE-FIREWALL', '-m', 'physdev', '--physdev-is-bridged', '--physdev-out', vif, '-j', vmchain])
            util.pread2(['iptables', '-I', 'BRIDGE-FIREWALL', '2', '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', vif, '-j', vmchain])
            util.pread2(['iptables', '-I', vmchain, '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', vif, '-j', 'RETURN'])
        except:
            util.SMlog("Failed to program default rules")
            return 'false'
    util.pread2(['iptables', '-A', vmchain, '-j', 'ACCEPT'])
    return 'true'
 SSVM Chain规则
Chain s-6-VM (8 references)
pkts bytes target     prot opt in     out source    destination
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif18.2 --physdev-is-bridged
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif18.0 --physdev-is-bridged
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif18.1 --physdev-is-bridged
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif18.3 --physdev-is-bridged
0     0 ACCEPT     all  --  *  * 0.0.0.0/0 0.0.0.0/0
 CPVM  Chain规则
Chain v-2-VM (6 references)
pkts bytes target     prot opt in     out source   destination
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif17.2 --physdev-is-bridged
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif17.0 --physdev-is-bridged
0     0 RETURN     all  --  *  * 0.0.0.0/0     0.0.0.0/0   PHYSDEV match --physdev-in vif17.1 --physdev-is-bridged
0     0 ACCEPT     all  --  *  * 0.0.0.0/0     0.0.0.0/0
 VRouter Chain规则
Chain r-4-VM (4 references)
pkts bytes target     prot opt in  out source   destination
0     0 RETURN     all  --  *  * 0.0.0.0/0      0.0.0.0/0   PHYSDEV match --physdev-in vif19.0 --physdev-is-bridged
0     0 RETURN     all  --  *  * 0.0.0.0/0      0.0.0.0/0   PHYSDEV match --physdev-in vif19.1 --physdev-is-bridged
0     0 ACCEPT     all  --  *  * 0.0.0.0/0      0.0.0.0/0
User VM 
def default_network_rules(session, args):
    vm_name = args.pop('vmName')
    vm_ip = args.pop('vmIP')
    vm_id = args.pop('vmID')#
    vm_mac = args.pop('vmMAC')
    sec_ips = args.pop("secIps")
    action = "-A"
    try:
        vm = session.xenapi.VM.get_by_name_label(vm_name)
        if len(vm) != 1:
             util.SMlog("### Failed to get record for vm  " + vm_name)
             return 'false'
        vm_rec = session.xenapi.VM.get_record(vm[0])
        domid = vm_rec.get('domid')
    except:
        util.SMlog("### Failed to get domid for vm " + vm_name)
        return 'false'
    if domid == '-1':
        util.SMlog("### Failed to get domid for vm (-1):  " + vm_name)
        return 'false'
    vif = "vif" + domid + ".0"
    tap = "tap" + domid + ".0"
    vifs = [vif]
    try:
        util.pread2(['ifconfig', tap])
        vifs.append(tap)
    except:
        pass
    delete_rules_for_vm_in_bridge_firewall_chain(vm_name)
    vmchain =  chain_name(vm_name)
    vmchain_egress =  egress_chain_name(vm_name) #EGRESS
    vmchain_default = chain_name_def(vm_name) #INGRESS
    destroy_ebtables_rules(vmchain)
#建立三个chain: i-x-y-VM ,i-x-y-VM-eg,i-x-y-VM-def
    try:
        util.pread2(['iptables', '-N', vmchain])
    except:
        util.pread2(['iptables', '-F', vmchain])
    try:
        util.pread2(['iptables', '-N', vmchain_egress])
    except:
        util.pread2(['iptables', '-F', vmchain_egress])
    try:
        util.pread2(['iptables', '-N', vmchain_default])
    except:
        util.pread2(['iptables', '-F', vmchain_default])
    vmipset = vm_name
    #create ipset and add vm ips to that ip set
    if create_ipset_forvm(vmipset) == False:
       util.SMlog(" failed to create ipset for rule " + str(tokens))
       return 'false'
    #add primary nic ip to ipset
    if add_to_ipset(vmipset, [vm_ip], action ) == False:
       util.SMlog(" failed to add vm " + vm_ip + " ip to set ")
       return 'false'
    #add secodnary nic ips to ipset
    secIpSet = "1"
    ips = sec_ips.split(':')
    ips.pop()
    if ips[0] == "0":
        secIpSet = "0";
    if secIpSet == "1":
        util.SMlog("Adding ipset for secondary ips")
        add_to_ipset(vmipset, ips, action)
        if write_secip_log_for_vm(vm_name, sec_ips, vm_id) == False:
            util.SMlog("Failed to log default network rules, ignoring")
    keyword = '--' + get_ipset_keyword()
    try:
        for v in vifs:
            util.pread2(['iptables', '-A', 'BRIDGE-FIREWALL', '-m', 'physdev', '--physdev-is-bridged', '--physdev-out', v, '-j', vmchain_default])
            util.pread2(['iptables', '-I', 'BRIDGE-FIREWALL', '2', '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '-j', vmchain_default])
        #don't let vm spoof its ip address
        for v in vifs:
            #允许VM的DNS请求
            util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '-m', 'set', keyword, vmipset, 'src', '-p', 'udp', '--dport', '53', '-j', 'RETURN'])
            util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '-m', 'set', '!', keyword, vmipset, 'src', '-j', 'DROP'])
            util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-out', v, '-m', 'set', '!', keyword, vmipset, 'dst', '-j', 'DROP'])
            util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-in', v, '-m', 'set', keyword, vmipset, 'src', '-j', vmchain_egress])
            util.pread2(['iptables', '-A', vmchain_default, '-m', 'physdev', '--physdev-is-bridged', '--physdev-out', v,  '-j', vmchain])
    except:
        util.SMlog("Failed to program default rules for vm " + vm_name)
        return 'false'
    default_arp_antispoof(vmchain, vifs, vm_ip, vm_mac)
    #add default arp rules for secondary ips;
    if secIpSet == "1":
        util.SMlog("Adding arp rules for sec ip")
        arp_rules_vmip(vmchain, vifs, ips, vm_mac, action)
    default_ebtables_antispoof_rules(vmchain, vifs, vm_ip, vm_mac)
    if write_rule_log__vm(vm_name, vm_id, vm_ip, domid, '_initial_', '-1', vm_mac) == False:
        util.SMlog("Failed to log default network rules, ignoring")
    util.SMlog("Programmed default rules for vm " + vm_name)
    return 'true'
用户VM chain规则
Chain i-2-7-VM (1 references)
target     prot opt source               destination
DROP       all  --  0.0.0.0/0            0.0.0.0/0
Chain i-2-7-VM-eg (1 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
Chain i-2-7-def (2 references)
target     prot opt source     destination
RETURN     udp  --  0.0.0.0/0  0.0.0.0/0    PHYSDEV match --physdev-in vif21.0 --physdev-is-bridged set i-2-7-VM src udp dpt:53
DROP       all  --  0.0.0.0/0  0.0.0.0/0    PHYSDEV match --physdev-in vif21.0 --physdev-is-bridged !set i-2-7-VM src
DROP       all  --  0.0.0.0/0  0.0.0.0/0    PHYSDEV match --physdev-out vif21.0 --physdev-is-bridged !set i-2-7-VM dst
i-2-7-VM-eg  all  --  0.0.0.0/0 0.0.0.0/0 PHYSDEV match --physdev-in vif21.0 --physdev-is-bridged set i-2-7-VM src
i-2-7-VM   all  --  0.0.0.0/0   0.0.0.0/0   PHYSDEV match --physdev-out vif21.0 --physdev-is-bridged

对于User VM,会创建ebtables和ipset,arptable。并且允许DHCP 67/68端口,否则用户VM将无法自动获得IP。

ebtables
Bridge chain: FORWARD, entries: 5, policy: ACCEPT
-j DEFAULT_EBTABLES
-i vif21.0 -j i-2-7-VM #入接口
-o vif21.0 -j i-2-7-VM  #出接口
Bridge chain: OUTPUT, entries: 0, policy: ACCEPT
Bridge chain: DEFAULT_EBTABLES, entries: 12, policy: ACCEPT
#DHCP的广播目的地址是255.255.255.255,源地址是0.0.0.0
-p IPv4 --ip-dst 255.255.255.255 --ip-proto udp --ip-dport 67 -j ACCEPT
-p IPv4 --ip-dst 255.255.255.255 --ip-proto udp --ip-dport 68 -j ACCEPT
-p ARP --arp-op Request -j ACCEPT
-p ARP --arp-op Reply -j ACCEPT
-p IPv4 -d Broadcast -j DROP
-p IPv4 -d Multicast -j DROP
-p IPv4 --ip-dst 255.255.255.255 -j DROP
-p IPv4 --ip-dst 224.0.0.0/4 -j DROP
-p IPv4 -j RETURN
-p IPv6 -j DROP
-p 802_1Q -j DROP
-j DROP
Bridge chain: i-2-7-VM, entries: 2, policy: ACCEPT
# fake dhcp and snooping of dhcp requests drop
-p IPv4 -i vif21.0 --ip-proto udp --ip-dport 68 -j DROP
-p IPv4 -o vif21.0 --ip-proto udp --ip-dport 67 -j DROP
IPSet

ipset是以VM名字命名的,membes是VM的IP地址。

Name: i-2-7-VM
Type: iphash
References: 4
Header: hashsize: 1024 probes: 8 resize: 50
Members:
192.168.2xx.xxx

对于target 为RETURN 的会交给上级chain 处理。

Chain  (1 references)
target     prot opt source        destination
BRIDGE-DEFAULT-FIREWALL  all  --  0.0.0.0/0 0.0.0.0/0
i-2-7-def  all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif21.0 --physdev-is-bridged
r-4-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif19.0 --physdev-is-bridged
r-4-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif19.1 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif18.2 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif18.0 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif18.1 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif18.3 --physdev-is-bridged
v-2-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif17.2 --physdev-is-bridged
v-2-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif17.0 --physdev-is-bridged
v-2-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-in vif17.1 --physdev-is-bridged
v-2-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif17.1 --physdev-is-bridged
v-2-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif17.0 --physdev-is-bridged
v-2-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif17.2 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif18.3 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif18.1 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif18.0 --physdev-is-bridged
s-6-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif18.2 --physdev-is-bridged
r-4-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif19.1 --physdev-is-bridged
r-4-VM     all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif19.0 --physdev-is-bridged
i-2-7-def  all  --  0.0.0.0/0     0.0.0.0/0     PHYSDEV match --physdev-out vif21.0 --physdev-is-bridged

默认DHCP的67,68端口允许。

Chain FORWARD (policy ACCEPT)
target     prot opt source           destination
BRIDGE-FIREWALL  all  --  0.0.0.0/0  0.0.0.0/0 PHYSDEV match --physdev-is-bridged
ACCEPT     all  --  0.0.0.0/0        0.0.0.0/0    PHYSDEV match --physdev-out eth1 --physdev-is-bridged
ACCEPT     all  --  0.0.0.0/0        0.0.0.0/0    PHYSDEV match --physdev-out eth0 --physdev-is-bridged
DROP       all  --  0.0.0.0/0        0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Chain BRIDGE-DEFAULT-FIREWALL (1 references)
target     prot opt source         destination

推荐阅读
  • 本文介绍如何在现有网络中部署基于Linux系统的透明防火墙(网桥模式),以实现灵活的时间段控制、流量限制等功能。通过详细的步骤和配置说明,确保内部网络的安全性和稳定性。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 本文深入探讨了计算机网络的基础概念和关键协议,帮助初学者掌握网络编程的必备知识。从网络结构到分层模型,再到传输层协议和IP地址分类,文章全面覆盖了网络编程的核心内容。 ... [详细]
  • 深入解析TCP/IP五层协议
    本文详细介绍了TCP/IP五层协议模型,包括物理层、数据链路层、网络层、传输层和应用层。每层的功能及其相互关系将被逐一解释,帮助读者理解互联网通信的原理。此外,还特别讨论了UDP和TCP协议的特点以及三次握手、四次挥手的过程。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 本文深入探讨了MAC地址与IP地址绑定策略在网络安全中的应用及其潜在风险,同时提供了针对该策略的破解方法和相应的防御措施。 ... [详细]
  • NFS(Network File System)即网络文件系统,是一种分布式文件系统协议,主要用于Unix和类Unix系统之间的文件共享。本文详细介绍NFS的配置文件/etc/exports和相关服务配置,帮助读者理解如何在Linux环境中配置NFS客户端。 ... [详细]
  • 解析EasyCVR平台国标GB28181协议下的TCP与UDP模式
    在使用EasyCVR视频融合平台过程中,用户常遇到关于端口设置的问题,尤其是TCP和UDP模式的区别。本文将详细介绍这两种模式在GB28181协议下的具体应用及差异。 ... [详细]
  • 数据管理权威指南:《DAMA-DMBOK2 数据管理知识体系》
    本书提供了全面的数据管理职能、术语和最佳实践方法的标准行业解释,构建了数据管理的总体框架,为数据管理的发展奠定了坚实的理论基础。适合各类数据管理专业人士和相关领域的从业人员。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 新冠肺炎疫情期间,各大银行积极利用手机银行平台,满足客户在金融与生活多方面的需求。线上服务不仅激活了防疫相关的民生场景,还推动了银行通过互联网思维进行获客、引流与经营。本文探讨了银行在找房、买菜、打卡、教育等领域的创新举措。 ... [详细]
  • 三菱PLC SLMP协议报文详解
    本文详细解析了三菱PLC中使用的SLMP协议报文结构,包括其工作原理、通信流程及报文格式,旨在帮助工程师和技术人员更好地理解和运用这一协议。 ... [详细]
  • 使用C# .NET构建UDP点对点聊天应用
    本文详细介绍如何利用C# .NET框架开发一个基于UDP协议的点对点聊天程序,包括客户端与服务器之间的连接建立、数据传输等核心功能。 ... [详细]
  • 本文将详细探讨 Linux 系统中的 netstat 命令,该命令用于查看网络状态和连接情况。通过了解 IP 地址和端口的基本概念,我们将更好地理解如何利用 netstat 命令来监控和管理网络服务。 ... [详细]
  • TCP/IP基础知识详解
    本文详细介绍了TCP/IP协议的基本概念,包括网络层次模型、TCP三次握手过程、四次挥手过程以及TCP与UDP的比较。通过这些内容,读者可以更好地理解TCP/IP协议的工作原理。 ... [详细]
author-avatar
最低调的鹌鹑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有