计算机网络学习笔记(2)—— IP层

Posted by 皮皮潘 on 02-16,2022

IP层

CIDR

在一开始的时候,IP是划分为A,B,C三类地址的,但是这地址划分为三类导致了很尴尬的情况:C类地址能包含的最大主机数量实在太少了,只有254个,而第二级别的B类地址能包含的最大主机数量又太多了,足足有65534,在这种情况下组网时选择C类网段还是B类网段就成为了问题,于是有了一个折中的方式叫做无类型域间选路,简称CIDR,其核心就是子网掩码,将子网掩码和 IP 地址进行 AND 计算即可得到当前对应的网段,从而可以快速判断两个IP是否在同一个网段下,这里需要注意的是子网掩码不一定是8的整数倍,在对应情况下需要做二进制展开运算

IP配置

通过ip addr add 192.168.0.2/24 dev eth1命令可以手动给网卡绑定任意IP,并通过ip link set up eth1启动对应网卡,这里需要注意的是,虽然命令本身提供了极大的自由度使得用户可以绑定任意IP,但是如果真的绑定了一个任意IP的话往往是发不出网络包的,比如旁边的机器都是10.0.0.x,用户非要配置一个192.168.0.2的IP就会导致包发不出,缘由如下:

首先参照网络包分层的核心准则:可以有下层没上层,绝对不可能有上层没下层,因此在每个包发出去之前都至少要填MAC地址,自己的MAC地址好填但是目标的MAC地址就没办法填写了,因为在发包的时候Linux首先会判断发送的地址和自身地址是否是同一个网段的,如果是一个网段的,那么它会发送ARP请求,直接获取目标的MAC地址,如果不是在同一个网段,便会发送ARP请求获取网关的MAC地址,然后填写网关的MAC地址,再把包发送给网关,让网关代为把包发送到目的地,那么在第一个情况下由于周围的机器都是10.0.0.x的而不存在192.168.0.x的机器,因此ARP请求不会获取到对应的MAC地址,在第二个情况下,由于网关要和当前网络是同一个网段的,因此也无法配置10.0.0.x的网关作为192.168.0.2的网关地址,更不用说获取到网关对应的MAC地址了

DHCP

因此在不了解IP网段的情况下,并不建议用户手动地去配置对应的IP,而应该交由局域网内部的DHCP(Dynamic Host Configuration Protocol)服务器完成IP的分配

DHCP步骤如下:

  1. 客户端发出ip请求广播,其中来源MAC中填写客户端自身的MAC地址好让DHCP服务器可以找到并回复客户端,而目标MAC中则填写FF:FF:FF:FF:FF实现广播;
  2. 多个 DHCP server 收到广播后,发出ip分配广播;
  3. 客户端接收 DHCP server 的广播(一般是第一个),发出Request 广播(包中包含客户端的 MAC 地址、接受的租约中的 IP 地址、提供此租约的 DHCP 服务器地址等);
  4. 成功的 DHCP Server 接收到 DHCP request 之后,会广播返回给客户机一个 DHCP ACK 消息包。其他所有失败的 DHCP server 接收到 DHCP request 之后,解放已分配的ip。

需要注意的是由于在DHCP的过程中,客户端并没有确定自身的IP,因此是链路层广播 + 自身的Mac的地址实现和DHCP Server之间的通信的,另外注意在步骤3中,虽然客户端已经接收了某个分配IP,但是由于还没有得到 DHCP Server 的最后确认,客户端仍然使用 0.0.0.0 为源 IP 地址、255.255.255.255 为目标地址并基于链路层进行广播,仅仅在包体中携带接受了的分配IP以及是哪个DHCP Server分配的

ping与ICMP

当遇到网络不通的时候,我们往往会使用ping命令来验证网络可达性,而ping命令正是基于ICMP协议工作的,ICMP全称是Internet Control Message Protocol,就是互联网控制报文协议

网络包在异常复杂的网络环境中传输时,常常会遇到各种各样的问题。当遇到问题的时候,就要传回消息来,报告情况,这样才可以调整传输策略,而这正是ICMP的作用,它相当于网络世界的侦察兵。ICMP 报文主要有两大类,一种是主动探查的查询报文,一种异常报告的差错报文。

ICMP报文是封装在IP包里面的,就像TCP报文也是封装在IP包里面一样,不同类型报文之间通过IP头中的协议类型字段来区分,ICMP报文的结构很简单,本身分为 ICMP 头部和数据两部分。 - ICMP 头部长度为固定 4 字节,其中 - 前 8 位(0-255)代表 ICMP 类型,但事实上经常使用的只有 “0 - ECHO REPLY请求回复”、 “3- 目的不可达”、“5 - 重定向”、“8 - ECHO REQUEST主动请求”、“11 - 超时”、“12 - 参数问题” - 9 -16 位是代码位,根据类型的不同,代码位的意义也被复用。最常用的如类型 3 对应代码 “0 - 网络不可达”、“1- 主机不可达”、“2 - 协议不可达” 等 - 后 16 位 CRC 校验码 - ICMP 数据根据类型和代码的不同有不同的内容,最常见的如 ping 报文格式,里面包含了标识符(区别请求 ping 的线程身份,如可以填请求线程的 ID)、序列号(ping 包计数,从而判断 ping 回复对应的请求) 、发出时间等信息

如果在自己的可控范围之内,当遇到网络不通的问题的时候,除了直接 ping 目标的 IP 地址之外,还应该有一个清晰的网络拓扑图。并且从理论上来讲,应该要清楚地知道一个网络包从源地址到目标地址都需要经过哪些设备,然后逐个 ping 中间的这些设备或者机器。如果可能的话,在这些关键点,通过wireshark或者 tcpdump -i eth0 icmp 命令,抓取在对应网卡(这里是etho0)的icmp包,查看包有没有到达某个点,回复的包到达了哪个点,可以更加容易推断出错的位置

另外在使用ping命令时,如果不在我们的控制范围内,很多中间设备都是禁止 ping 的,因此 ping 不通不代表网络不通,这个时候就要使用 telnet,通过其他协议来测试网络是否通

说了那么多,涉及的报文都是正常报文(类型为8、0等正常请求与回复报文),那么差错报文(类型为3、11等出错了的ICMP报文)又有什么用呢?有一个程序Traceroute就是通过使用ICMP差错报文的规则来追踪当前机器到目标主机沿途经过的路由器。

Traceroute 的参数指向某个目的 IP 地址,它会发送一个 UDP 的数据包,将 TTL 设置成 1,也就是说一旦遇到一个路由器就会返回一个 ICMP 包,也就是网络差错包,类型是时间超时(这里可以看出并不是只有发出ICMP请求才会接收到ICMP响应包,任何数据包都可能导致被动接收到ICMP响应包),接下来将TTL设置成2,如此反复直到到达目的主机,这样就拿到了所有的路由器IP,另外为了知道UDP是否达到目的主机,Tracheroute会发送UDP数据包给目的主机一个不可能的端口号,当数据报到达时,将使目的主机的UDP产生并返回一份“端口不可达”的错误ICMP报文

路由网关

Linux在发送数据包的时候,会涉及到路由过程,这个发送数据包既包括本机发送数据包,也包括途径当前机器的数据包的转发,路由的核心其实就是选择从哪个网卡出去罢了

之前说过,MAC地址仅仅只能在局域网之内(同一网段)进行定位,但凡想要跨局域网交互,那么就需要通过IP地址进行定位,并通过路由器进行路由了,因此在配置机器IP的时候,除了IP和网段的配置,还要配置对应的网关,不然对应机器只能在局域网内进行访问,而不能访问到外部的网络。

其实网关就是每一个局域网的出入口,网关/路由器做的事情就是将一个数据包从局域网一转移到局域网二,然后在局域网二内部通过MAC地址找到下一个路由器,再从局域网二转移到局域网三,如此反复最后找到目标IP所在局域网再找到对应的主机。所以一个数据包在跨网段的传输过程中,源MAC地址与目标MAC地址一直在变,而源IP地址和目标IP地址并不会变(除非使用了NAT)

很多情况下,人们把网关就叫做路由器,其实不完全准确,而另一种比喻更加恰当:路由器是一台设备,它有五个网口或者网卡,相当于有五只手,分别连着五个局域网。每只手的 IP 地址都和局域网的 IP 地址相同的网段,每只手都是它握住的那个局域网的网关,任何一个想发往其他局域网的包,都会到达其中一只手,被拿进来,拿下 MAC 头和 IP 头,看看,根据自己的路由表,选择另一只手,加上 IP 头和 MAC 头,然后扔出去。

一张路由表中会有多条路由路由规则。每一条规则至少包含这三项信息:目的网络、出口设备、下一跳网关(下一个局域网的另一个路由器的网关地址)。如果只有出口设备而没有下一条网关,则代表该出口设备对应的局域网内有目标IP

路由表的来源可分为三类,分别是:直连路由、静态路由和动态路由

直连路由

直连路由就是路由器直接连接的路由条目,只要在路由器的网卡接口上配置了对应的IP 地址与掩码,那么就能得知该网卡连接的局域网的信息了,也就会自动生成对应的直连路由

静态路由

静态路由就是通过ip route add命令手动添加的路由,其中除了可以根据目的 ip 地址配置路由外,可以配置多个路由表,从而可以根据源 IP 地址、入口设备、TOS 等选择路由表,然后再在路由表中查找路由。其中可以通过ip rule add from xxx table xxx指定路由表的筛选规则,再通过在ip route命令后面加上table xxx可以针对某个路由表添加路由规则,至于路由表本身的添加可以通过修改/etc/iproute2/re_table进行,更加具体的细节,大家可以自行谷歌

动态路由

有时候网络环境复杂并且多变,如果总是用静态路由,一旦网络结构发生变化,让网络管理员手工修改路由太复杂了,因而需要动态路由算法,使用动态路由路由器,可以根据路由协议算法生成动态路由表,随网络运行状况的变化而变化

路由协议主要有两种,一种是内部网关协议——IGP,它往往用在一个组织内部,其底层使用链路状态路由算法,找到最短的路径即可,另外一种是外部网关协议——BGP,它往往用在组织和组织之间广播路由信息,其底层使用距离矢量路由算法

Netfilter框架和iptables工具

Netfilter框架

Netfilter框架是工作在IP层的,它也可以说是协议栈的一部分,其核心在于当协议栈在IP层处理IP的时候提供多个hook点,从而可以让用户层应用通过iptables工具参与对于IP包的拦截、加工以及修改,所有的hook点如下图所示:
1674_0.png
其中矩形方框中的即为Netfilter的钩子节点,从图中可以看到,三个方向的数据包需要经过的钩子节点不完全相同:

  • 发往本地:NF_INET_PRE_ROUTING-->NF_INET_LOCAL_IN
  • 转发:NF_INET_PRE_ROUTING-->NF_INET_FORWARD-->NF_INET_POST_ROUTING
  • 本地发出:NF_INET_LOCAL_OUT-->NF_INET_POST_ROUTING
    另外不管是发往本地、转发还是本地发出都会经过ROUTE这一步,也即查阅路由表决定对应的IP是发往本地的还是发往外部的,如果是发往外部的那么应该从哪张网卡发出

iptables 工具

iptables 在用户态提供了table和chain的概念。包含的table有 filter,nat,mangle 以及 raw。而每个table下包含不同的链,如下图所示:
1676_0.png
iptables 中每个table的作用不同,以我们比较常用的 filter 表为例,其主要起到数据包过滤和拦截作用,包含 INPUT,FORWARD 和 OUTPUT 三个链,根据链的名字我们可以知道,这三个链分别被放置到 Netfilter 三个不同的钩子节点中生效。INPUT 链是在NF_INET_LOCAL_IN节点,FORWARD 链是在NF_INET_FORWARD节点,OUTPUT 链则是在NF_INET_LOCAL_OUT节点。其他table的链也类似,不同table的链上的回调方法其实最后都挂载在了同一个钩子上,table仅仅决定了哪个回调方法先执行。

一些常用指令如下:

  1. -A OUTPUT + -j DNAT 可以透明地修改程序访问的目的 ip,eg:

    iptables -t nat -A OUTPUT -d 1.1.1.1 -j DNAT --to-destination 2.2.2.2 # 在从程序输出到网卡时,将 destip 改为目标服务
    
  2. -A PREROUTING + -j DNAT 结合 -A POSTROUTING + -j MASQUEREADE可以作为网关,实现端口映射,eg:

    iptables -t nat -A PREROUTING -p tcp --dport 60002 -j DNAT --to-destination $_Y_A:$_Y_SSH_PORT # 在路由前,将 dest ip + port 改为内网服务,然后进行路由
    iptables -t nat -A POSTROUTING -p tcp -d $_Y_A --dport $_Y_SSH_PORT -j MASQUERADE # 在路由后,将 src ip 改为网关自身 ip,从而使得内网服务感知不到外界的访问
    

    这里需要注意,启用了 NAT 之后,会自动在内存生成映射表,并且在数据包返回时,将原先做得转换再变换回来,并且执行顺序会与定义的相反,比如 PREROUTING 执行的 DNAT,在收到数据包返回时会自动在 POSTROUTING 时执行 SNAT

  3. iptables -t filter -A INPUT -s 172.16.0.0/16 -p udp --dport 53 -j DROP

    该指令是在 filter 表的 INPUT 链中添加一条过滤规则,凡是收到源地址为 172.16.0.0/16,传输层协议为 UDP 并且目的端口为 53 的数据包(即 DNS 数据包),都将该数据包丢弃。在 Linux 内核中,这一个指令会在 Netfilter 网络层NF_INET_LOCAL_IN节点生成处理操作,凡是经过这个钩子节点的数据包,在前面规则都通过的情况下,都必须经过这一规则的检查,如果符合这条规则的匹配条件,则该数据包会被丢弃;如果不符合,则进行下一条规则的匹配。

参考

  1. 《趣谈网络协议》
  2. 《计算机网络:自顶向下方法》
  3. 公众号-《开发内功修炼》