-- 基于状态的iptables 如果按照tcp/ip来划分连接状态,有11种之多(课后可以自己去读一下相关知识) 但iptables里只有4种状态;ESTABLISHED、NEW、RELATED及INVALID 这两个分类是两个不相干的定义。例如在TCP/IP标准描述下UDP及ICMP数据包是没有连接状态的,但在state模块的描述下,任何数据包都有连接状态。 1、ESTABLISHED (1)与TCP数据包的关系:首先在防火墙主机上执行SSH Client,并且对网络上的SSH服务器提出服务请求,而这时送出的第一个数据包就是服务请求的数据包,如果这个数据包能够成功的穿越防火墙,那么接下来SSH Server与SSH Client之间的所有SSH数据包的状态都会是ESTABLISHED。 (2)与UDP数据包的关系:假设我们在防火墙主机上用firefox应用程序来浏览网页(通过域名方式),而浏览网页的动作需要DNS服务器的帮助才能完成,因此firefox会送出一个UDP数据包给DNS Server,以请求名称解析服务,如果这个数据包能够成功的穿越防火墙,那么接下来DNS Server与firefox之间的所有数据包的状态都会是ESTABLISHED。 (3)与ICMP数据包的关系:假设我们在防火墙主机ping指令来检测网络上的其他主机时,ping指令所送出的第一个ICMP数据包如果能够成功的穿越防火墙,那么接下来刚才ping的那个主机与防火墙主机之间的所有ICMP数据包的状态都会是ESTABLISHED。 由以上的解释可知,只要第一个数据包能够成功的穿越防火墙,那么之后的所有数据包(包含反向的所有数据包)状态都会是ESTABLISHED。 2、NEW 首先我们知道,NEW与协议无关,其所指的是每一条连接中的第一个数据包,假如我们使用SSH client连接SSH server时,这条连接中的第一个数据包的状态就是NEW。 3、RELATED RELATED状态的数据包是指被动产生的数据包。而且这个连接是不属于现在任何连接的。RELATED状态的数据包与协议无关,只要回应回来的数据包是因为本机送出一个数据包导致另一个连接的产生,而这一条新连接上的所有数据包都是属于RELATED状态的数据包。 4、INVALID INVALID状态是指状态不明的数据包,也就是不属于以上三种状态的封包。凡是属于INVALID状态的数据包都视为恶意的数据包,因此所有INVALID状态的数据包都应丢弃掉,匹配INVALID状态的数据包的方法如下: iptables -A INPUT -p all -m state INVALID -j DROP 我们应将INVALID状态的数据包放在第一条。 | --------- |---》 client | server 《------------- | ----- | client访问server过去 第一个数据包(new状态),如果拒绝,那么后续包都会被拒绝(因为后面来的都会是第一个,都为new状态) 第一个数据包如果允许过去,那么后续包的状态为established server返回给client 返回的所有包都为established 例1: 有下面两台机 10.1.1.35 10.1.1.36 10.1.1.35是可以ssh访问10.1.1.36,也可以elinks访问10.1.1.36 1,在10.1.1.36上 iptables -P INPUT DROP iptables -P OUTPUT DROP 这里就把双链都关掉,10.1.1.35任何访问都过不来了 2, 按以前的做法 在10.1.1.36上允许别人ssh进来 iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT 在10.1.1.36上允许别人elinks进来 iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT 或者把上面四条合下面两条 iptables -A INPUT -p tcp -m multiport --dport 22,80 -j ACCEPT iptables -A OUTPUT -p tcp -m multiport --sport 22,80 -j ACCEPT 把上面的两条再换成 iptables -A INPUT -p tcp -m multiport --dport 22,80 -j ACCEPT iptables -A OUTPUT -p tcp -m state --state established -j ACCEPT (后面一句可以翻译成tcp协议的连接只要你进得来,你就出得去) (无论他是用哪个随机端口访问进来的;因为只要能进来,那么后续的包都属于ESTABLISHED状态了) 例2: --有些服务器,可能希望你ping不通他,但是他可以ping通你 一种: 修改proc的内核参数,禁ping 二种:使用状态iptables 有下面两台机 10.1.1.35 10.1.1.36 在10.1.1.36上实现10.1.1.36能ping通所有人.但所有人不能ping通10.1.1.36 | ---------》| client | server 10.1.1.35 | 10.1.1.36 <-------| <-------- new established INPUT 拒绝 允许 OUTPUT 允许 允许 1,在10.1.1.36上 iptables -P INPUT DROP iptables -P OUTPUT DROP 这里就把双链都关掉,10.1.1.35任何访问都过不来了 2,在10.1.1.36上 iptables -A INPUT -p icmp -m state --state ESTABLISHED -j ACCEPT iptables -A OUTPUT -p icmp -m state --state NEW,ESTABLISHED -j ACCEPT --重点是INPUT那条不能允许NEW状态的; --注意第二步的第二条(也就是output这条),如果只写了NEW状态,那么10.1.1.36ping所有人,都只能通第一个包;加上ESTABLISHED状态,所有包都能通 例3: 1,在双链默认策略为drop的情况下,只有10.1.1.35可以ssh访问我 2, 在上面的基础上加上需求:实现我想访问别人的服务(任何服务),都可以成功; 2,继续加需求:但别人想访问我的服务,只允许可以访问我的80端口 3,继续加需求:允许我ping任何人,但只允许10.1.1.35这一个IP能ping通我 # iptables -P INPUT DROP # iptables -P OUTPUT DROP # iptables -A INPUT -p tcp --dport 22 -s 10.1.1.35 -j ACCEPT # iptables -A OUTPUT -m state --state new,established,related -j ACCEPT # iptables -A INPUT -m state --state established,related -j ACCEPT # iptables -A INPUT -p tcp --dport 80 -j ACCEPT # iptables -A INPUT -p icmp -s 10.1.1.35 -j ACCEPT ====================================================================== 对mangle表的mark的讨论 MARK用来给包设置特殊的标记。iproute2能识别这些标记,并根据不同的标记(或没有标记)决定不同的路由。用这些标记我们可以做带宽限制和基于请求的分类。 用处一: 在lvs集群里,可以对进入的数据包打mark标记,然后针对标记再来进行分发 使用到iptables 的 set-mark和ipvsadm -f参数 --下面这段就是网上的一个例子,学到LVS后,可以对比这个做出来 ========================================== 例子:LVS的 NAT 模式配置使用-f 选项 Load Balancer: eth0: 192.168.1.110/32 (负载服务器对外VIP) eth1: 192.168.3.1/24 Realserver1: 192.168.3.2 (集群服务器) 掩码: 255.255.255.0 网关: 192.168.3.1 Realserver2: 192.168.3.3 (集群服务器) 掩码: 255.255.255.0 网关: 192.168.3.1 实验目的:在负载服务器上,配置LVS(NAT模式),做远程登陆服务器的集群。 实现最终用户访问负载服务器的对外VIP,负载服务器,将请求分发到集群下的服务器, 由集群服务器轮流提供ssh和telnet服务。 在Load Balancer上设置: #echo "1" > /proc/sys/net/ipv4/ip_forward #确保转发功能已打开 # iptables -t mangle -A PREROUTING -p tcp -d 192.168.1.110/32 --dport 22 -j MARK --set-mark 2 # iptables -t mangle -A PREROUTING -p tcp -d 192.168.1.110/32 --dport 23 -j MARK --set-mark 2 # iptables -t mangle -L PREROUTING #查看上述两条规则是否加入到mangle表的PREROUTING链中 # ipvsadm -A -f 2 -s rr #添加一个LVS服务,数据类型为fwmark为2的数据包,使用轮询策略 # ipvsadm -a -f 2 -r 192.168.3.2 -m -w 1 #添加真实服务器192.168.3.2到LVS(NAT模式) #数据类型为fwmark为2的服务中 # ipvsadm -a -f 2 -r 192.168.3.3 -m -w 1 #添加真实服务器192.168.3.3到LVS(NAT模式) #数据类型为fwmark为2的服务中 lvs 80(http)+443(https) web1 web2 用处二: 可以和策略路由结合使用,进行路由的标记分发 参考: http://bbs.chinaunix.net/thread-2228370-1-1.html http://bbs.chinaunix.net/thread-2251893-1-1.html 用处三: 单服务器多个网卡同网段,想进行从哪个网卡来的数据就从哪个网卡出去 --一般来说,单服务器多网卡不要使用同一个网段的IP 用一个虚拟机两个host-only网卡,同网段 [root@localhost ~]# ifconfig |grep eth -A 1 eth0 Link encap:Ethernet HWaddr 00:0C:29:EE:A0:7F inet addr:1.1.1.129 Bcast:1.1.1.255 Mask:255.255.255.0 -- eth1 Link encap:Ethernet HWaddr 00:0C:29:EE:A0:89 inet addr:1.1.1.128 Bcast:1.1.1.255 Mask:255.255.255.0 用真实机ping这两个网卡,然后在真实机上查看 [root@li ~]# ip neigh |grep ^1.1.1. 1.1.1.129 dev vmnet1 lladdr 00:0c:29:ee:a0:7f REACHABLE 1.1.1.128 dev vmnet1 lladdr 00:0c:29:ee:a0:7f REACHABLE --结果是两个网卡都是对应eth0(1.1.1.129)的MAC,这就是有问题的情况 那么这样的话,就会出现问题,因为无论访问129还是128都只会访问这台虚拟机的eth0网卡 默认,linux所有的网卡都会回应arp(address resolve protocol),如果我在这台虚拟机上把eth0下面的内核参数改一下,表示eth0没有这个IP的话,就不回应arp echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore --这样修改后,如果本网卡没有这个IP,则不回应arp 改完后,用真实机去ping 1.1.1.128还是可以ping通,是因为真实机的arp列表里还保留了128的MAC地址对应 再在真实机上做一条命令arp -d 1.1.1.128删除128对应的MAC地址列表,再次ping 1.1.1.128就不通了 [root@li ~]# ip neigh |grep ^1.1.1. 1.1.1.129 dev vmnet1 lladdr 00:0c:29:ee:a0:7f REACHABLE 1.1.1.128 dev vmnet1 FAILED 原因是128的网卡一切正常,所以正常情况应该是可以找到128,并且会得到128的MAC地址,但是linux默认就是让你只去找第一张网卡(这其实就是说明了两双卡同网段是有问题的).但第一张网卡的129配置内核参数不回应非自己IP的ARP,就造成了真实机现在ping不通128的情况 扩展: ARP协议(地址解析协议) 做IP地址到MAC地址的转换 比如:本机做一个ping 10.1.1.15的操作,那么会发现ARP广播包"谁是10.1.1.15,请把MAC地址告诉我", 那么拥有10.1.1.15这个IP的人就会回应,并把它的MAC地址给本机。 然后本机就可以跟它进行通迅,在IP数据包里并会带上本机和它的MAC地址。 并且本机会把10.1.1.15的MAC保留并缓存到本机的MAC地址列表里(可以使用ip neigh或arp -a等命令查看本机MAC地址缓存列表)。 但这个信息不会永久保存,linux默认保存15分钟,在保存的这个时间段里本机再和10.1.1.15通迅,不用再进行ARP广播。 但15分钟后,列表里没有它的缓存,本机又要和它通迅,则重新走ARP广播的过程了。 ======================================================================== tcpdump tcpdump - dump traffic on a network 参数的分类: 协议 tcp udp arp icmp 数据内容 端口,Ip : src port 80 --源 dst port 22 --目标 tcpdump tcp dst port 80 -n tcpdump -i eth1 tcp dst port 80 -n --小写i参数指定哪个网卡 tcpdump tcp dst port 80 -n -w tcpdump.txt --把dump出来的信息保存到tcpdump.txt文件 tcpdump -r tcpdump.txt --然后需要使用tcpdump -r去读取 关系参数 : ! and or tcpdump tcp dst port 80 -n and src 10.2.2.4 tcpdump tcp dst port 80 -n and ! src 10.2.2.4 tcpdump tcp dst port 80 -n and host 10.2.2.4 -vv <---把数据包的详细内容都记录下来 -w <---把数据保存到某个文件 ====================================================================== 练习: 捕捉所有访问本机ftp服务器的连接,并且数据包是来自10.2.2.4 tcpdump tcp dst port 21 and src 10.2.2.4 -n 捕捉所有来自10.2.2.1的arp协议的数据包,并且捕捉的显示要求输出以太网数据帧的信息 -e tcpdump arp and src 10.2.2.1 -n -e 捕捉所有本机web服务回应10.2.2.4的数据包 tcpdump tcp src port 80 and dst 10.2.2.4 图形的抓包工具:wireshark yum install wireshark* ============================================================ linux 高级路由 策略路由 lartc(linux advanced routing and traffic control) http://www.lartc.org # rpm -qa |grep iproute --iproute2工具包软件 iproute-2.6.32-31.el6.x86_64 ip命令就属于iproute2软件包 ip addr ip neigh ip rule ip route ip tunnel ============================================================= # ip rule list 0: from all lookup local 32766: from all lookup main 32767: from all lookup default # cat /etc/iproute2/rt_tables --上面的local对应数字255,在rhel5.7里ip rule list看到的不是local,而是255;这只是对应的两种名称而已 # # reserved values # 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep # ip route list/show table local/255 # ip route list/show table main/254 # ip route list/show table default/253 ===================================================================== 基本家用网络拓扑图 线 | modem | | 路由器 | |---------| 交换机 交换机 | |------| 电脑1 电脑2 ============================================================== 应用实例1: 猫1 ----------》快线路 | | 内网用户 ----linux路由 | | 猫2 ---------》慢线路 linux路由器有两条上网线路,一个快,一个慢 有这样的需求:内网用户需要给钱共享上网,有人给钱多,需要快线路,有人给钱少,需要快线路,这样的话,我们就可以使用策略路由了 模拟的话使用下面的图: 10.1.1.36 ----------》VM2(bridge) | 1.1.1.128 | 10.1.1.35 eth0 VM1(hostonly1)----linux路由1.1.1.1 vmnet1 公网 | 2.2.2.1 vmnet2 | ---------》VM3 (hostonly2) 2.2.2.128 上图架构中: 1.把VM1网关指向1.1.1.1 2,把linux路由器的网关指向10.1.1.36 3.linux路由打开ip_forward 先测试:在VM1上ping一个外网IP(如 ping 8.8.8.8),这个时候只能在VM2上抓到相关的包,表示数据包从VM2出去 下面就是在linux路由上进行操作来实现: 操作命令: echo 200 t1 >> /etc/iproute2/rt_tables ip rule add from 1.1.1.128 table t1 ip route add default via 2.2.2.128 dev vmnet2 table t1 ip route flush cache --如果加错了规则,想删掉,就使用ip rule del table t1 操作完后,测试 1,在内网ping 8.8.8.8 2,在两个模拟外网路由器的机器上抓包 # tcpdump -i eth0 -p icmp 3,结果这次只能在VM3上抓到包,OK 应用实例2: 10.1.1.36 线路一 ----------》VM2(bridge) | 1.1.1.128 | 10.1.1.35 eth0 VM1(hostonly)----linux路由1.1.1.1 vmnet1 公网 | 2.2.2.1 vmnet2 | ---------》VM3 (hostonly2) 2.2.2.128 线路二 要实现不同类型的包走不同的线路:如80的访问走一条线,其它的走另外一条线路 实现不同类型的包的策略路由,就要借助于iptables的mangle链的set mark功能 1,在linux路由器上使用策略路由实现 # iptables -t mangle -A PREROUTING -i vmnet1 -p tcp --dport 80 -j MARK --set-mark 1 --把从内网进来要出去的80的包打标记为1 # echo 100 http.out >> /etc/iproute2/rt_tables --建一张叫http.out的表,表编号100 # ip rule add fwmark 1 table http.out pref 20000 --指定打了标记为1的所有包都走http.out这张路由表,并指定优先级为20000(这里优先级可以不指,因为只有这一条策略) # ip route add default via 2.2.2.128 dev vmnet2 table http.out --指定http.out表从vmnet8出去找2.2.2.128 # ip route flush cache --刷新路由缓存 2,测试 测试一:在内网1.1.1.128客户端上 elinks 8.8.8.8 在模拟两个线路的机器上都执行下面的命令 tcpdump -i eth0 tcp port 80 --只有线路二上能抓到包,OK 测试二:在内网1.1.1.128客户端上 ping 8.8.8.8 在模拟两个线路的机器上都执行下面的命令 tcpdump -i eth0 -p icmp --只有线路一上能抓到包,OK --从上面就可以看到出去的80端口的包和其它的包走的路线不一致 ========================================================== 问题: 要求:写出这个电信用户访问到双线web服务器时的IP变化过程(只写源IP,目标IP,和做SNAT还是DNAT等) 你觉得有没有问题? 192.168.1.100 192.168.2.100 电信用户 网通用户 | | 192.168.1.1 | | 192.168.2.1 电信用户家里路由器 网通用户家里路由器 51.1.2.3 | | 61.1.2.3 | | | | 71.1.2.3 | | 81.1.2.3 公司电信路由器 公司网通路由器 10.1.1.1 | | 172.16.2.1 | | | | 10.1.1.100 eth0 双线web服务器 eth1 172.16.2.100 电信用户和网通用户通过智能DNS分别去访问电信或网通线路 SIP:192.168.1.100 DIP:71.1.2.3 到达电信用户家用路由器手动SNAT SIP:51.1.2.3 DIP:71.1.2.3 到达公司电信路由器手动DNAT SIP:51.1.2.3 DIP:10.1.1.100 到达web服务器返回(网关指向10.1.1.1) SIP:10.1.1.100 DIP:51.1.2.3 返回到公司电信路由器自动SNAT SIP:71.1.2.3 DIP: 51.1.2.3 到达电信用户家用路由器自动DNAT SIP:71.1.2.3 DIP:192.168.1.100 问题是: 电信的用户回去时,把双线web服务器网关要指向10.1.1.1 网通的用户回去时,把双线web服务器网关要指向172.16.2.1 那么按前面所学的知识,同一个路由表只能有一个可用网关,如果可用网关为电信路由器内网IP;那么实现的是电信进来的包从电信回,网通进来的包也从电信回.问题出现了 如果在双线web服务器上不加网关,用加路由的方式来做的话, 那么 route add -net 51的网段 netmask x.x.x.x dev eth0 route add -net 61的网段 netmask x.x.x.x dev eth1 同理还得加网通的,但是这也有一个问题,实际情况电信和网通的用户网段太多了, 你不可能全加上去 应用实例3: 实际情况下的图示,如果做实现得需要七台虚拟机 192.168.1.100 192.168.2.100 电信用户 网通用户 | | 192.168.1.1 | | 192.168.2.1 电信用户家里路由器 网通用户家里路由器 51.1.2.3 | | 61.1.2.3 |www.abc.com | | | 71.1.2.3 | | 81.1.2.3 公司电信路由器 公司网通路由器 10.1.1.1 | | 172.16.2.1 | | | | 10.1.1.100 eth0 双线web服务器 eth1 172.16.2.100 精简一点可以使用下面的四台虚拟来做,并且要注意宿主机(真实机)不能在这里扮演角色,因为宿主机和任何虚拟机都是可以直接通的 下图中,电信客户端和网通客户端就没有使用去模拟路由器NAT,直接用一台虚拟机用两个网卡来模拟两个角色 172.16.2.51 bridge 客户端 hostonly2 91.1.1.129 | | | | | | 172.16.2.41 bridge hostonly2 91.1.1.128 电信路由器1 网通路由器2 2.2.2.230 hostonly1 vmnet8 71.1.1.131 | | | | 2.2.2.236 hostonly1 双线服务器 vmnet8 71.1.1.132 SIP:172.16.2.51 DIP:172.16.2.41 DNAT SIP:172.16.2.51 DIP:2.2.2.236 回来 SIP:2.2.2.236 DIP:172.16.2.51 SNAT sip:172.16.2.41 dip:172.16.2.51 进来 电信客户端 SIP:192.168.1.100 DIP:172.16.2.41 被客户端家里的路由器SNAT SIP:172.16.2.51 DIP:172.16.2.41 被服务器端的电信路由器DNAT SIP:172.16.2.51 DIP:2.2.2.236 我们下面所做的就是回去时,由两个不同ISP运营商进来的包,从两个不同的网关回去 回去: SIP:2.2.2.236 DIP:172.16.2.51 被服务器端的电信路由器SNAT SIP:172.16.2.41 DIP:172.16.2.51 被电信客户端家里的路由器DNAT SIP:172.16.2.41 DIP:192.168.1.100 上图中: 客户端用虚拟机(linux或xp都可以,但不能使用真实机,因为真实机可以直接访问到上图中的服务器) eth0 172.16.2.51 bridge eth1 91.1.1.129 hostonly2 电信路由器1用虚拟机 eth0 172.16.2.41 bridge eth1 2.2.2.230 hostonly1 网通路由器2用虚拟机 eth0 91.1.1.128 hostonly2 eth1 71.1.1.131 nat 服务器用虚拟机 eth0 2.2.2.236 hostonly1 eth1 71.1.1.132 nat --假设上图里的客户端为内网用户,服务器为公网服务器; --因为是模拟环境,所以这里两个路由器打开ip_forward,但上面四台都不用指网关 --我们实现的是当客户端用电信线路访问,也只能从电信线路返回;网通线路访问,也只能从网通线路返回 先在电信路由器和网通路由器上做好SNAT和DNAT,这样就不用把电信客户端的外网IP的网关指向电信路由器的外网IP了 (因为实际的公网IP,不会把网关指向你的,这个在讲iptables的SNAT就讨论过) 电信路由器上写两条(还要打开ip_forward) --也可以只写DNAT,SNAT那条不写它包会自动回来SNAT的 iptables -t nat -A PREROUTING -i eth0 -j DNAT --to-destination 2.2.2.236 iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 172.16.2.41 网通路由器上写两条(还要打开ip_forward) --也可以只写DNAT,SNAT那条不写它包会自动回来SNAT的 iptables -t nat -A PREROUTING -i eth0 -j DNAT --to-destination 71.1.1.132 iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 91.1.1.128 --上面四条当中的eth0自己换成对应的网卡名;电信路由器上的网卡名应该都为172.16.2.41的网卡名;网通路由器上的网卡名应该都为91.1.1.128的网卡名 做好上面四条和打开路由器的ip_forward后,并且默认上图所有的机器里现在都没有网关 在双线web服务器这台启动apache,随便做一个主页测试 验证: 1,在客户端elinks 172.16.2.41,得不到结果 在客户端elinks 91.1.1.128,得不到结果 2,上面两个线路都得不到结果,是因为能到web服务器,但回不来 所以把web服务器网关指向2.2.2.230的话,则再测试,只有elinks 172.16.2.41能得到结果 把web服务器网关指向71.1.1.131的话,则再测试,只有elinks 91.1.1.128能得到结果 3,也就是说,现在无法实现电信线路和网通线路都能访问成功;所以我们要借助于策略路由 1,在双线web服务器上使用策略路由实现 # ip rule 0: from all lookup local 32766: from all lookup main 32767: from all lookup default # echo 100 dianxin >> /etc/iproute2/rt_tables # echo 200 wangtong >> /etc/iproute2/rt_tables --下面这四条是告诉dianxin路由表找2.2.2.230出去;告诉wangtong路由表找71.1.1.131出去;eth0就是2.2.2.236的网卡;eth1就是71.1.1.132的网关 # ip route add 2.2.2.0 dev eth0 src 2.2.2.236 table dianxin # ip route add default via 2.2.2.230 table dianxin # ip route add 71.1.1.0 dev eth1 src 71.1.1.132 table wangtong # ip route add default via 71.1.1.131 table wangtong --下面这两条是加规则,指定从2.2.2.236回去的包找dianxin表;从71.1.1.132出去的包找wangtong表 # ip rule add from 2.2.2.236 table dianxin # ip rule add from 71.1.1.132 table wangtong # ip rule 0: from all lookup local 32764: from 71.1.1.132 lookup wangtong 32765: from 2.2.2.236 lookup dianxin 32766: from all lookup main 32767: from all lookup default 测试: 再在客户端 elinks 172.16.2.41,能访问到web elinks 91.1.1.128,也能访问到web 说明电信线路的包只走电信线路,网通线路的包只走网通线路 如果要深入测试的话,可以在客户端elinks电信线路的172.16.2.41时,去双线web服务器上双线路网卡都去tcpdump,会发现只有电信的网卡有包;网通的没有 反之,亦然 ==================================