码迷,mamicode.com
首页 > Web开发 > 详细

Linux系统中的防火墙的实现:iptables/netfilter

时间:2018-05-23 10:56:17      阅读:209      评论:0      收藏:0      [点我收藏+]

标签:iptables

防火墙:包括软件防火墙(基于iptables/netfilter的包过滤防火墙)和硬件防火墙,在主机或网络边缘对经由防火墙的报文以一定条件进行检测过滤的一系列组件。

Linux系统中的防火墙的实现:
利用iptables/netfilter既可以实现主机防火墙(安全服务范围仅限于当前某台主机),又可以实现网络防火墙(安全服务范围为当前局域网)。
netfilter:Linux系统内核中防火墙的框架,防火墙功能实现的主体;
iptables:为netfilter编写数据传输的匹配规则的用户空间中的应用程序工具;

iptables结构:iptables 由tables 构成,tables由chains组成,而chains由rules组成。

iptables的表(tables)与链(chains):
链(5种):
INPUT链 (处理来自外部的数据)
OUTPUT链 (处理向外发送的数据)
FORWARD链 (将数据转发本机的其他设备)
PREROUTING链(处理刚到达本机并在路由转发前的数据包;转换数据报文中目的IP地址用于DNAT)
POSTROUTING链(处理即将离开本机的数据包;转换数据报文中源IP地址用于SNAT)

表有Filter, NAT, Mangle, Raw四种内建表:

  1. filter表:用来实现过滤器,包过滤防火墙功能
    filter是iptables的默认表,有以下三种内建链:
    INPUT链 (处理来自外部的数据)
    OUTPUT链 (处理向外发送的数据)
    FORWARD链 (将数据转发本机的其他设备)
  2. NAT表:用于修改报文的源地址或目的地址、修改端口号,用来地址转换和地址伪装
    NAT表包含三种内建链:PREROUTING链、PREROUTING链、OUTPUT链
    PREROUTING链(处理刚到达本机并在路由转发前的数据包;转换数据报文中目的IP地址用于DNAT)
    POSTROUTING链(处理即将离开本机的数据包;转换数据报文中源IP地址用于SNAT)
    OUTPUT链 (处理本机产生的数据报文)
    INPUT(iptables1.4之后的版本中新增加的链,用于处理进入本机的数据报文)
  3. Mangle表:拆解报文并对报文格式进行修改,然后再重新封装报文,可以用来流量控制和对数据添加标签
    Mangle表具有5个内建链:PREROUTING、POSTROUTING、OUTPUT、INPUT、FORWARD
  4. Raw表:关闭nat表上启动的连接追踪机制
    Raw表有2个内建链:PREROUTING、OUTPUT

    注意:各个表中相同规则链上的规则生效优先级次序(从高到低):raw --> mangle --> nat --> filter

数据报文的流向:
1.入站数据——目的IP地址为防火墙主机的有效IP地址的数据报文:
PREROUTING -->路由表 (Routing table) --> INPUT
2.出站数据——源IP地址为防火墙主机的有效IP地址的数据报文:
PREROUTING --> 路由表(Routing table) --> OUTPUT --> POSTROUTING
3.转发数据——源、目的IP地址都不是防火墙主机的有效IP地址,需要经过防火墙转发的数据报文:
PREROUTING --> 路由表(Routing table) --> FORWARD --> POSTROUTING

iptables的规则(rules):
iptables:规则编写工具,非交互式命令行工具,一次只能编写一条规则并送往netfilter执行;
netfilter的规则匹配顺序是:首项匹配;
首项匹配:由上至下依次匹配每条规则,且只要有规则能够匹配数据报文,则不再继续检索其他匹配规则;

CentOS 6及以前版本:使用iptables命令编写规则;
CentOS 7:firewall-cmd命令编写规则;iptables命令依然可以使用(建议使用);
停止firewalld服务并取消其自动运行:

systemctl disable firewalld.service

        # systemctl stop firewalld.service

想要添加正确合适的规则,需要事先考虑如下几个问题:
1.数据报文的流经路径,已确定将规则添加至正确的表的正确链上;
2.确定此次规则实现的功能,以选择正确的表;
3.确定具体的匹配条件的内容,以便用于匹配感兴趣的数据;
4.确定链的默认匹配条件和执行动作;

iptables命令:规则编写工具
规则(匹配条件+处理动作):匹配规则或识别条件,根据要求定义的用来匹配进入、流出或经过本机的报文的匹配条件,并且在匹配之后指明具体的处理方法
匹配条件:基本匹配条件和扩展匹配条件
基本匹配条件:简单的IP、TCP、UDP等协议的报文首部中特定属性的匹配条件
扩展匹配条件:需要借助于扩展的功能模块(隐式和显式)进行匹配
隐式扩展:不明确的指出使用哪个具体的模块,但实际上确实在使用扩展模块来完成条件匹配
显式扩展:必须要明确指出此次使用哪个具体模块来完成条件匹配
处理动作:
基本动作(包过滤动作):ACCEPT,DROP,REJECT
扩展动作:需要借助于扩展模块才能执行的处理动作

定义规则时的注意事项:
    规则的匹配顺序是由上至下安装书写顺序进行匹配:
        1) 同一类规则,匹配范围最小的应该写在最上面;
        2) 非同一类的规则,匹配频率越高的应该写在上面;
        3) 建议尽量不修改链的默认策略为阻止所有数据,如果想要设置阻止所有数据的规则,在链的最后一条设置阻止所有数据的规则即可;
规则iptables规则的书写和优化规范:
    1.可以安全放行所有入站及出站的且状态为ESTABLISHED的连接的数据;
    2.服务于同一类功能的规则,匹配条件越严格的越应该放置在规则链的前面,条件越宽松的越放在后面;
    3.服务于不同类功能的规则,匹配报文可能性越大的越应该放置在规则链的前面,匹配可能性小的放在后面;
    4.推荐最后一条规则设置为阻止所有数据通过,而不建议直接修改默认策略;

iptables规则编写的通用格式:
    iptables [-t table] COMMAND CHAIN [-m matchname] [per-match-options] -j targetname [per-target-options]

    选项说明:
        -t table:指定选择执行哪个表的功能,可以选择的表包括:raw,mangle,nat及filter,如果省略此选项,则表示使用默认表——filter表;
        -m matchname:通常是配置显式扩展的匹配条件时,必须书写;如果省略则表示要配置基本匹配条件或隐式扩展匹配条件;
        -j targetname:指定所有匹配条件的数据包的处理动作;

        COMMAND:
            链的操作命令:
                -P, --policy chain target
                    用于定义指定链的默认策略;通常有两种动作选择,即:ACCEPT和DROP;
                -N, --new-chain chain
                    创建一条新的自定义的规则链;新建的链上的规则必须要被内建链上的规则调用才能生效;
                -X, --delete-chain [chain]
                    删除被内建链调用次数为0的自定义链;
                -E, --rename-chain old-chain new-chain
                    重命名被内建链调用次数为0的自定义链;
                -F, --flush [chain]
                    清除指定链(表中所有链)上的规则;

            规则的操作命令:
                -A, --append chain rule-specification
                    在指定的链的末尾追加一条规则;
                -D, --delete chain rule-specification
                -D, --delete chain rulenum
                    从指定的链上删除一条规则,可以指明具体规则,也可以指明规则在链上的编号;
                -I, --insert chain [rulenum] rule-specification
                    在指定的链上插入一条规则,默认是将新规则插入至链的第一条规则,也可以指定规则编号,是的插入的规则称为指定链上的第rulenum条规则;
                -R, --replace chain rulenum rule-specification
                    用命令行中的规则替换指令链上的第rulenum条规则;并不是修改规则中某个具体条件,而是完全替换整条规则;
                -L, --list [chain]
                    列表显示指定表指定链(所有链)上的所有规则;
                        可以使用的其他常用选项:
                            -v, --verbose:显示更详细格式的信息,还有-vv;
                            -n, --numeric:将规则中的所有信息都进行数字化显示;包括主机名和端口号等信息;
                            -x, --exact:精确的显示计数器的结果;
                                每个规则都有两个计数器:
                                    1.规则所匹配的报文的个数;
                                    2.规则所匹配的报文的字节总数;
                            --line-numbers:显示指定链上各个规则的编号;

            其他的命令:
                -Z, --zero [chain [rulenum]]
                    将指定链的规则计数器置0;

        常用的TARGETS:
            LOG:对于匹配的数据报文的流动情况进行日志记录,并不会影响数据报文本身的传输;
            MARK:对于匹配的数据报文进行防火墙标记的设置;
            MASQUERADE:源地址伪装,一种特殊的源IP地址转换;
            REDIRECT:目标IP地址和端口的重定向;
            REJECT:阻止数据报文传输并向数据报文的源头返回消息;
            SNAT:源IP地址转换;
            DNAT:目标IP地址转换;
            ACCEPT:对于匹配的数据报文进行放行;
            DROP:对于匹配的数据报文进行阻止;
            RETURN:在规则链之间跳转;

        匹配条件:
            默认情况下,同一条命令中的不同条件之间存在逻辑"与"的关系;
            !:对于匹配的结果取反,有除了...之外的意思;

            基本匹配条件:
                [!] -s, --source address[/mask][,...]
                    检查数据报文中的源IP地址的匹配范围;可以是单个的IP地址,也可以是子网,主网,超网等IP地址设定;0.0.0.0/0表示整个IP地址栈中所有的IP地址;如果省略该条件,意味着将匹配所有的源IP地址;
                [!] -d, --destination address[/mask][,...]
                    检查数据报文中的目标IP地址的匹配范围;可以是单个的IP地址,也可以是子网,主网,超网等IP地址设定;0.0.0.0/0表示整个IP地址栈中所有的IP地址;如果省略该条件,意味着将匹配所有的目标IP地址;
                [!] -i, --in-interface name
                    检查数据报文入站的接口是否能够被此条件所匹配;
                [!] -o, --out-interface name
                    检查数据报文出站的接口是否能够被此条件所匹配;

            扩展匹配条件:隐式扩展匹配条件和显式扩展匹配条件
                隐式扩展匹配条件:
                    [!] -p, --protocol protocol
                    [!] -p, [-m matchname] --protocol protocol
                        检查数据报文某指定的协议的封装首部中是否有符合条件的特性或字段;

                        可以在此处指定的协议包括:tcp, udp, udplite, icmp, icmpv6, esp, ah, sctp, mh, all

                        tcp协议(tcp模块):
                            [!] --source-port,--sport port[:port]
                            [!] --destination-port,--dport port[:port]
                                用于指定源端口和/或目标端口的匹配条件;每次只能指定一个端口或一组连续的端口范围,而不能指定离散端口;

                            [!] --tcp-flags mask comp
                                用于指定在TCP协议首部中各标志位的匹配条件;
                                    URG, SYN, RST, PSH, ACK, FIN, ALL, NONE
                                        mask:设定要检测的标志位的列表,各标志位之间使用","进行分隔;
                                        comp:必须被置"1"的标志位列表,剩余的在mask列表中的标志位必须置"0";
                            [!] --syn
                                相当于:--tcp-flags SYN,RST,ACK,FIN SYN

                        udp协议(udp模块):
                            [!] --source-port,--sport port[:port]
                            [!] --destination-port,--dport port[:port]
                                用于指定源端口和/或目标端口的匹配条件;每次只能指定一个端口或一组连续的端口范围,而不能指定离散端口;

                        icmp协议(icmp模块):
                            [!] --icmp-type {type[/code]|typename}
                                常用的icmp-type:
                                    echo-request,代码为8;
                                    echo-reply,代码为0;

                显式扩展匹配条件:
                    1.multiport扩展:
                        一次性的写入多个离散端口或多组连续端口,最大的上限15组端口,每一个端口范围占用两个端口;
                        可以支持的协议:tcp, udp, udplite, dccp, sctp.
                        相关选项:
                            [!] --source-ports,--sports port[,port|,port:port]...
                            [!] --destination-ports,--dports port[,port|,port:port]...
                            [!] --ports port[,port|,port:port]...

                            --dports 22,80,3306 -j ACCEPT

                            示例:
                                ~]# iptables -I FORWARD -s 192.168.100.100 -d 172.16.69.2 -p tcp -m multiport --dports 22,80,3306 -j ACCEPT
                                ~]# iptables -I FORWARD -d 192.168.100.100 -s 172.16.69.2 -p tcp -m multiport --sports 22,80,3306 -j ACCEPT

                    2.iprange扩展:
                        以连续的任意数量的IP地址访问作为匹配条件;
                        相关选项:
                            [!] --src-range from[-to]
                            [!] --dst-range from[-to]

                            -m iprange --src-range 192.168.100.1-192.168.100.100

                            示例:
                                ~]# iptables -I FORWARD -m iprange --src-range 192.168.100.1-192.168.100.100 -d 172.16.69.2 -p tcp -m multiport --dports 22,80,3306 -j ACCEPT
                                ~]# iptables -I FORWARD -m iprange --dst-range 192.168.100.1-192.168.100.100 -s 172.16.69.2 -p tcp -m multiport --sports 22,80,3306 -j ACCEPT

                    3.string扩展:
                        对数据报文中的应用层数据做字符串匹配检测;
                            相关选项:
                                --algo {bm|kmp}
                                --string "STRING"

                            示例:
                                ~]# iptables -I FORWARD -s 172.16.69.2 -m string --algo bm --string "dafa" -j REJECT

                    4.time扩展:
                        根据报文到达防火墙本机的时间与指定的时间范围进行匹配检测;
                            相关选项:
                                --datestart YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
                                --datestop YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
                                    定义唯一一个绝对的时间范围;

                                --timestart hh:mm[:ss]
                                --timestop hh:mm[:ss]
                                    定义一个周期性的时间范围;

                                [!] --monthdays day[,day...]
                                    定义每个月中各个天;取值1-31

                                [!] --weekdays day[,day...]
                                    定义每个星期中的星期几;
                                    取值:Mon, Tue, Wed, Thu, Fri, Sat, Sun, or values from 1 to 7, or Mo, Tu, etc.

                            示例:
                                ~]# iptables -I FORWARD -m time --timestart 08:00:00 --timestop 17:59:59 ! --weekdays 6,7 -o eno16777736 -j REJECT

                    5.state扩展
                        连接状态检测;基于连接追踪机制实现;
                        conntrack

                        相关选项:
                            [!] --state state

                        iptables对连接状态的定义:
                            INVALID, ESTABLISHED, NEW, RELATED or UNTRACKED;

                            INVALID:无法识别的连接状态,无效的通信状态; SYN,FIN
                            ESTABLISHED:已经建立连接的状态;连接态;
                            NEW:尚未建立连接的状态;新连接态;
                            RELATED:与其他已经建立的连接有相互关联的连接状态;关联态或衍生态;
                            UNTRACKED:未追踪的状态;

                        内核中用于保存连接追踪状态数据的位置:/proc/net/nf_conntrack

                        能够被追踪到的最大的连接数:/proc/sys/net/nf_conntrack_max
                            注意:此处记录的最大连接数的数值,建议必要时可以调整其大小到足够大;

                        为了能够尽可能的高效利用内存资源,缓存的连接追踪的状态不能无限期保存,因此设置了相应的超时时间;
                            /proc/sys/net/netfilter/nf_conntrack*timeout*

                    利用连接追踪设置FTP服务器的访问控制:
                        ~]# iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
                        ~]# iptables -A INPUT -d 172.16.69.2 -p tcp -m multiport --dports 21,22,80,3306 -m state --state NEW -j ACCEPT
                        ~]# iptables -A INPUT -j DROP

                        注意:需要装载nf_conntrack_ftp内核模块;
                            ~]# modprobe nv_conntrack_ftp

                            设置nf_conntrack_ftp模块的自动装载:
                                设置/etc/sysconfig/iptables-config
                                IPTABLES_MODULES="nf_conntrack_ftp"

                    利用连接追踪可以设置OUTPUT链上的通用规则:
                        ~]# iptables -A OUTPUT -m state --state ESTABLISHED -j ACCEPT
                        ~]# iptables -A OUTPUT -j DROP

                    6.mac扩展
                        实现mac地址匹配检测;用于PREROUTING, FORWARD or INPUT链;

                        相关选项
                            [!] --mac-source address
                                匹配源mac地址;其形式必须是:XX:XX:XX:XX:XX:XX

                    7.connlimit扩展:
                        根据每个客户端IP地址做并发连接数的匹配检测;

                        相关选项:
                            --connlimit-upto n
                                当客户端当前的并发连接数小于或等于n时,可以匹配此条件;此条件通常和ACCEPT动作配合使用;

                            --connlimit-above n
                                当客户端当前的并发连接数大于n时,可以匹配此条件;此条件通常和DROP或REJECT动作配合使用;

                    8.limit扩展:
                        基于服务器端收发数据报文的速率来进行匹配检测;

                        相关选项:
                            --limit rate[/second|/minute|/hour|/day]
                                服务器端最大单位时间内能够接收的报文速率;

                            --limit-burst number
                                初始时所能接收的数据报文的最大数量;

                        示例:
                            ~]# iptables -I INPUT 1 -p icmp --icmp-type 8 -m limit --limit 15/minute --limit-burst 8 -j ACCEPT

主机防火墙基本匹配条件配置示例:
1.允许指定的主机访问本地的SSH服务:
~]# iptables -t filter -A INPUT -s 172.16.0.1 -p tcp --dport 22 -j ACCEPT

2.默认阻止所有其他主机到本机的所有数据通信:
    方法一:~]# iptables -P INPUT DROP (不推荐,如果使用iptables -F命令后果很可怕)
    方法二:~]# iptables -A INPUT -j REJECT (推荐操作)

3.配置172.16.0.0/16网段中所有的主机可以访问本地的httpd服务:
    ~]# iptables -I INPUT -s 172.16.0.0/16 -p tcp --dport 80 -j ACCEPT

4.允许本机ping通外部主机,但不允许外部主机ping通本机:
    ~]# iptables -I INPUT 2 -d 172.16.72.2 -p icmp --icmp-type echo-reply -j ACCEPT

5.为了避免木马程序的植入运行,可以设置OUTPUT链上的防火墙规则:
    ~]# iptables -A OUTPUT -d 172.16.0.1 -p tcp --sport 22 -j ACCEPT
    ~]# iptables -A OUTPUT -j REJECT
    ~]# iptables -I OUTPUT -d 172.16.0.0/16 -p tcp --sport 80 -j ACCEPT
    ~]# iptables -I OUTPUT 2 -p icmp --icmp-type echo-request -j ACCEPT

网络防火墙基本匹配条件的配置示例:
~]# iptables -A FORWARD -j REJECT
~]# iptables -I FORWARD -s 192.168.100.100 -d 172.16.69.2 -p tcp --dport 3306 -j ACCEPT
~]# iptables -I FORWARD 2 -s 172.16.69.2 -d 192.168.100.100 -p tcp --sport 3306 -j ACCEPT
~]# iptables -I FORWARD 3 -p icmp -j ACCEPT

    注意:凡是通过FORWARD链来设置的网络防火墙的匹配规则,数据的往返过程必须同时被打开,才能保证路由功能的正常进行

自定义规则链:有一批功能类似的规则但不是用于匹配多数数据的规则,此时可以将此类规则设置于自定义链中,进而可以减少规则匹配的时间,提升匹配效率;(用到自定义链的情况)
创建自定义规则链:
~]# iptables -N udp_match //udp_match这个链必须是独一无二 的

向自定义规则链添加规则:
    ~]# iptables -A udp_match -d 172.16.72.2,192.168.100.1 -p udp -m multiport --dports 53,67,69,137,138 -j ACCEPT
    ~]# iptables -A udp_match -j RETURN
        //自定义规则链的最后一条规则,可以返回主链,进行后续规则匹配;

更改自定义规则链的名称:
    前提:要改名的自定义规则链的引用计数必须为0;
    ~]# iptables -E udp_match udp

删除自定义规则链:
    前提:要改名的自定义规则链的引用计数必须为0且链上不能有任何规则;
    ~]# iptables -X udp

在主链上引用自定义规则链:
    ~]# iptables -I INPUT -p udp -j udp   

iptables/netfilter归总:
一般书写格式:
iptables [-t table] COMMAND chain [-m matchname [per-match-options]] [-j targetname [per-target-options]]

iptables规则的保存和重载:
    CentOS 7:
        建议:为了兼容CentOS 6及以前版本,可以考虑将iptables的规则定义保存于/etc/sysconfig/iptables文件中;

        规则保存:
            ~]# (umask 077 ; iptables-save > /etc/sysconfig/iptables)

        规则恢复:
            ~]# iptables-restore < /etc/sysconfig/iptables

    CentOS 6和之前的版本:
        规则保存:
            ~]# iptables-save > /etc/sysconfig/iptables
            ~]# service iptables save

        规则恢复:
            ~]# iptables-restore < /etc/sysconfig/iptables
            ~]# service iptables restart

补充:
一、nat表:
功能:
1.NAT(Network Address Translation):网络地址转换
1) SNAT:Source-Address NAT,源地址网络地址转换
通常用于让局域网中使用私有IPv4地址的主机能够访问外部网络或互联网;一般在路由选择之后完成源地址的网络地址转换,所以在iptables中而言,SNAT类规则应配置在POSTROUTING链上;

            对于iptables来说有两种情况:
                a. 静态地址转换:一对一地址转换,一个私有地址对应一个公有地址转;
                b. 地址伪装:多对一地址转换,多个私有IP地址对应一个公有IP地址进行访问网络;

        2) DNAT:Destination-Address NAT,目标网络地址转换
            通常用于让外部网络或互联网中的主机能够访问局域网中使用私有IPv4地址的服务器上的网络服务;
            一般在路由选择之前就需要完成目标地址的网络地址转换,所以在iptables中而言,此类规则应配置在PREROUTING链上;

    2.NAPT:Network Address and Port Translation,网络地址端口转换;用来进行:网络地址转换 + 端口映射

SNAT:
    This target is only valid in the nat table, in the POSTROUTING and INPUT chains, and  user-defined  chains which are only called from those chains.
    常用选项:
        --to-source [ipaddr[-ipaddr]][:port[-port]]

        注意:在RHEL或CentOS系发行版LInux中,SNAT所指定的ipaddr必须是当前主机上配置并生效的IP地址;

        示例:
            ~]# iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -j SNAT --to-source 172.16.72.72

            ~]# iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -j MASQUERADE
                //源地址伪装技术,无需指定具体的内部全局地址,iptables自动决定本地可用的IP地址作为内部全局地址,进行源地址转换;

DNAT:
    This target is only valid in the nat table, in the PREROUTING and OUTPUT chains, and  user-defined  chains which are only called from those chains.
    常用选项:
        --to-destination [ipaddr[-ipaddr]][:port[-port]]

    示例:
        ~]# iptables -t nat -A PREROUTING -d 192.168.100.1 -p tcp --dport 80 -j DNAT --to-destination 172.16.69.2:8000

    与之功能相同的操作:
        1.在路由器上设置NAT转换的防火墙规则:
            ~]# iptables -t nat -R PREROUTING 1 -d 192.168.100.1 -j DNAT --to-destination 172.16.69.2

        2.在目标服务器上做端口重定向:需要使用REDIRECT target
            ~]# iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8000

            REDIRECT
                This target is only valid in the nat table, in the PREROUTING and OUTPUT chains, and  user-defined  chains which are only called from those chains. 

                --to-ports port[-port]

二、LOG target:
仅仅是开启内核对匹配到的数据报文做日志记录的功能,而不对数据做任何其他操作; //如果数据报文被target为LOG的规则匹配,还会按照正常的匹配顺序去匹配后续规则;

常用选项:
--log-level level :定义记录日志等级
    Level of logging, which can be (system-specific) numeric or  a  mnemonic.
    Possible  values  are  (in  decreasing  order of priority): emerg, alert, crit, error, warning, notice, info or debug.

--log-prefix prefix :日志前缀,描述日志是怎样被记录下来的(不超过29个字符)
    Prefix log messages with the specified prefix; up to 29 letters long, and useful for distinguishing messages in the logs.

示例:
~]# iptables -t filter -A FORWARD -p tcp --dport 80 -j LOG --log-level info --log-prefix "IPTAB-LOG-"        //注意:默认情况下,日志会记录在rsyslog服务所定义的默认日志文件中,/var/log/messages;

Linux系统中的防火墙的实现:iptables/netfilter

标签:iptables

原文地址:http://blog.51cto.com/chenliangdeeper/2119354

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!