网络常识
对于网络我其实是一知半解,这篇文章基本上是我知道什么就写些什么,因此十分零碎。
协议
网络协议有好几层,这些层级环环相扣。在 OSI 概念模型中,网络协议有七层;而在事实标准 TCP/IP 协议栈中,舍弃了其中的 L5 和 L6,并套用了 OSI 的 L1 物理层,共有四层。
数据链路层由网卡实现,用于连接计算机,构成网络。以太网用于构成有线局域网,Wi-Fi 用于构成无线局域网,PPP 和 PPPoE 用于连接用户和 ISP。
网络层由操作系统内核实现,用于将网络相连,构成互联网。
传输层由程序(调用 libc)实现,一般来说有 TCP 和 UDP 两种选择。TCP 会建立连接,并且提供不错的校验纠错功能,而代价就是减小了数据位占比。而 UDP 则是一种无连接的传输层协议。现在还有一些新兴的类传输层协议,如 KCP,他们一般运行在 UDP 之上,提供比 UDP 好的数据校验,比 TCP 高的传输效率。
IP
IP 地址指示一台计算机在网络上位置,数据包通过 IP 地址在路由器间寻路。流行的 IP 协议有 IPv4 和 IPv6 两个版本。
IPv4 地址为一串 32 位的数字,标准的表示方法为每 8 位用一个 0~255 的十进制数表示,中间用 .
分开,如 123.123.123.123
。只有 32 位的 IPv4 可以分配的地址十分有限。
于是人们又提出了 IPv6。IPv6 地址为一串 128 位的数字,标准的表示方法是每 16 位用一个 0x0000~0xffff 的十六进制数表示,中间用 :
分开,如 1234:5678:9abc:def0:0000:0000:0000:0001
。并且为了简便,有两条简写的规则:
- 每段开头的零可以省略。所以上面的地址可简写为
1234:5678:9abc:def0:0:0:0:1
。 - 连续的零段可以省略为
::
,一个地址中只能这样省略一次。上面的地址可以进一步简写为1234:5678:9abc:def0::1
因特网上的 IP 地址块由 IANA 和 RIR 负责分配。RIR 是各个地区的网络资源分配组织,如亚太地区的 RIR 为 APNIC,北美地区为 RIPE。IANA 首先将地址块分配给 RIR,RIR 之下还有 LIR。个人或组织向所属的 RIR 定期缴纳会费即可成为 LIR,LIR 可以申请或替他人或其他组织代为申请 IP 地址块。
以下是 IP 协议中特殊的地址,使用它们不需要向 RIR 申请。
环回地址
环回 IP 用于代指本机,一台机器上的程序可以通过环回 IP 互相通信。
IPv4 的环回 IP 段为 127.0.0.1/8
。可见在 IPv4 中 1/256 的地址都分配给了环回地址,挺浪费的。
IPv6 中只分配了一个环回 IP,::1/128
。
私有地址
私有 IP 仅在一个局域网内使用,使用私有 IP 不需要向 RIR 申请,因为它不会传播到其他网络。
IPv4 的私有 IP 如下(RFC1918):
10.0.0.0/8
192.168.0.0/16
172.16.0.0/12
IPv4 的私有 IP 有挺多问题,比如对于 NAT 套 NAT 的情况和两个局域网合并的情况,都十分容易造成 IP 重叠导致需要大量的重新配置。因此 IPv6 中使用一种叫做 ULA 的私有地址(RFC4193)。
- 一个局域网应该在
fd00::/8
下分配一个/48
使用。 fd
后面的 40 位应当随机生成。如fdc8:541e:9e06::/48
。
链路本地地址
链路本地地址是不能被路由的地址。IPv4 的 link-local 地址段为 169.254.0.0/16
,是 system wise 的。IPv6 的 link-local 地址段为 fe80::/10
,为 link wise 的,使用时需指定网卡。
DNS
IP 地址可以帮助数据包找到目的主机,但却不具有很高的可读性。而域名正是为了解决这一点。通过 DNS,人们只需要输入域名,计算机就可以找到域名所对应的 IP 地址,从而找到对应的主机。
域名的最终管理组织为 ICANN,负责分配顶级域名(TLD)给不同组织。人们熟知的顶级域名如 com
, org
, net
, cn
等。ICANN 把这些顶级域名交给不同的组织管理。这些组织把顶级域名的子域名 —— 二级域名开放给他人注册。如 land
为 Donuts Inc. 所有,我要注册 melty.land
最终就是向这个组织提的申请。一般来说人们注册的域名都是二级域名,有了二级域名你就可以在你的二级域名上添加子域名,具体方法是设置 nameserver。
注意,没有一级域名这个概念。
DNS 系统中有四种 nameserver,分别是 root server、TLD server、authoritative server 和 recursive server。
Root nameserver 有 13 台,由 ICANN 管理,分布在全球各地。储存了各顶级域名对应的 TLD nameserver。
TLD nameserver 由 ICANN 授权的组织管理,储存了二级域名的 authoritative nameserver record。
Authoritative nameserver 由注册二级域名的个人或组织指定,可以是 registrar 提供,也可以是自建,储存了二级域名下的 A
、AAAA
、MX
、NS
、TXT
等最终的 record。
Recursive nameserver 用于加速 DNS 解析,它会缓存用户向 root server, TLD server 和 authoritative server 发送 DNS 请求。通常由 ISP 提供并通过 DHCP 层层传递到用户的路由器,也有第三方的服务,如 Google 的 dns.google (8.8.8.8)、OpenDNS(208.67.222.222, 208.67.220.220)、CloudFlare 的 1.1.1.1。在 Linux 上用户也可以通过 dnsmasq 或 systemd-resolved 自行搭建本地的 recursive nameserver(也叫 resolver)。
DNS 记录(record)是一个域名和一项结果的对应关系,有很多种类型。
A
: IPv4 记录。AAAA
: IPv6 记录。CNAME
: 别名记录,指向另一个域名,从而使对该域名的解析返回指向域名的结果。TXT
: 文本记录,可以放一些辅助信息。如 SPF 中用于验证邮件的发送者、TLS DV 提供商用于验证域名所有权等。MX
: 邮件交换记录,指向另一个域名。告诉邮件的发送方邮件该域名的邮件服务不在该域名指向的 IP 下,而应当发送至哪个域名。NS
: nameserver 记录,指向另一个 DNS 服务器的域名。告诉请求方这个域名的信息我这里没有,你应该到这个 DNS 服务器查询。上面说的 root server 指向 TLD server,TLD server 指向 authoritative nameserver 就是通过这个记录实现的。PTR
: IP 指向域名的记录,见反向 DNS
解析过程
下面简述一下访问 melty.land
时所经历的 DNS 解析过程。为了方便描述我将用 Linux 下的 dig
工具来查询 DNS,这是使用方法:
dig [<type>] <domain> [@<nameserver>] [+short]
程序调用 libc,向 recursive nameserver 发出请求
dig A melty.land @192.168.1.1
Recursive nameserver 发现自己的缓存里没有该项,于是向一台 root nameserver 发出请求
dig A melty.land @a.root-servers.net
Root nameserver 告诉 recursive nameserver 自己也没有,但找到了
melty.land
的所在的顶级域名的 TLD nameserver,于是不返回ANSWER SECTION
,而是在AUTHORITY SECTION
中返回了NS
记录;; AUTHORITY SECTION: land. 172800 IN NS v2n1.nic.land. land. 172800 IN NS v0n2.nic.land.
并且由于这些 nameserver 的域名
nic.land
本身也在land
顶级域名下,所以在ADDITIONAL SECTION
中附上了这些域名的A
和AAAA
记录(这些记录被称为“胶水记录”,是 TLD 管理组织预先在 ICANN 填写的);; ADDITIONAL SECTION: v2n1.nic.land. 172800 IN A 161.232.11.11 v2n1.nic.land. 172800 IN AAAA 2a01:8840:f5::11 v0n2.nic.land. 172800 IN A 65.22.22.11 v0n2.nic.land. 172800 IN AAAA 2a01:8840:18::11
Recursive nameserver 收到了 root server 的回答,缓存
land
顶级域名的 NS 结果,并向其中一台 TLD nameserver 发送请求dig A melty.land @v2n1.nic.land
TLD nameserver 在数据库中没找到
melty.land
的A
记录,但找到了melty.land
使用的 authoritative nameserver,于是在AUTHORITY SECTION
中返回;; AUTHORITY SECTION: melty.land. 3600 IN NS etta.ns.cloudflare.com. melty.land. 3600 IN NS fred.ns.cloudflare.com.
Recursive nameserver 收到了 TLD nameserver 的回答,缓存
melty.land
的NS
记录结果,然后向其中一台 authoritative nameserver 发送请求dig A melty.land @etta.ns.cloudflare.com
Authoritative nameserver 找到了
@.melty.land
的A
记录,发回给 recursive nameserver。;; ANSWER SECTION: melty.land. 300 IN A 233.233.233.233
Recursive nameserver 缓存该
A
记录,返回给请求的程序;; ANSWER SECTION: melty.land. 300 IN A 233.233.233.233
反向 DNS
反向 DNS 为 IP 指向域名的 DNS,使用 PTR
记录表示。常用于邮件发送验证等。它看起来是这样的:
- IPv4: 将 IP 按照点分十进制,每八位用一个 0~255 的十进制数表示,然后反过来,接上
.in-addr.arpa
查询。如123.456.789.12
则应当查询12.789.456.123.in-addr.arpa
。 - IPv6: 将 IP 以十六进制数表示,然后按十六进制位倒过来以
.
连接,接上ip6.arpa
。如1234:5678:9abc:def0::1
则应当查询1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.ip6.arpa
。
ICANN 将 arpa.
顶级域名分配给了 IANA。在 RIR IRR 数据库的 domain
对象中,可以使用 nserver
指定你的 IP 块的反向 DNS 服务器。查询此 IP 块的 IP 时 RIR 的服务器就会返回该 IP 块指向你服务器的 NS
记录。
另外 IPv4 的反向 DNS 中会出现分配的 IP 块前缀不是 8 的倍数的情况,如查询 123.456.789.128/25
下的 123.456.789.129
。这种情况无法直接指向下一级,而是会以 CNAME
记录指向 129.128/25.789.456.123.in-addr.arpa.
,然后附上 128/25.789.456.123.in-addr.arpa
NS
记录。IPv6 没有这个问题。
传输层
传输层的主要作用是复用 IP,将计算机之间的通信转化为进程之间的通信。对于 TCP,还有重传功能以确保通信可靠。
TCP 和 UDP 的头内有 16 位用于表示端口,因此各有 65536 个端口。将 IP 和端口写在一起,称为一个套接字。IPv4 中套接字标准的表示方法为 IP:port
,IPv6 中为 [IP]:port
。
0~1023 为一些标准端口,它们常常因为历史原因有一些约定俗成的用法,由 IANA 负责标准化。因此虽然可以,但不建议将它们用于别的用途(Linux 下这些端口只有具有 CAP_NET_ADMIN
的进程可以绑定)。
tcp/80
: HTTPtcp/443
: HTTPStcp/22
: SSHtcp/179
: BGPudp/53
: DNSudp/123
: NTP
TLS/SSL
TLS 和 SSL 是一个东西的两个名字。该层不在 OSI 概念模型中,硬要归类的话应该属于传输层。用于给传输层的数据加密。
SSL 需要找第三方签发证书,这是因为非对称加密本身的交换公钥过程无法保证安全性。操作系统和浏览器中会预先保存许多可信的 Certificate Authorities 的根证书。被签发方首先生成一对钥匙,将公钥发给 CA。CA 使用私钥,在验证被签发方具有对域名的所有权后给公钥签名,称作签发证书。这样通过这些 CA 签名的公钥也会被信任,可以用于加密该域名的流量。
证书根据验证所有权的方式不同,分为 DV、OV、EV。DV 只需要对域名的所有权进行验证,如 DNS 记录、网站验证等。OV 和 EV 则需要验证组织或企业的所有权。有免费的 DV 服务,如 Let's Encrypt,可以通过 ACME 接口自动续签。
HTTP
HTTP/HTTPS 是使用得最广泛的两种网络传输协议。网页即是通过 HTTP 传输。HTTP 是一种基于请求和回应的协议:客户端向服务器发出请求,服务器将内容回应给客户端。
请求方式
GET: 让服务器将一个 URI 对应的内容发给我。网页就是这么加载的。
POST: 我先给服务器发送一点信息,服务器将我发送信息后处理的结果发给我。比如一个网络知识竞赛,我把每题的答案和我的身份信息发送给服务器,然后服务器返回给我我当前的排名之类的。
对于键值对数据,POST 的主体有三种,他们的 MIME type 分别是
multipart/form-data
,application/x-www-form-url-encoded
和application/json
。application/x-www-form-url-encoded
是将键值对数据按照 query string 的方式编码,很多时候比multipart/form-data
更紧凑些。而application/json
就是 JSON 字符串了。- curl 的
-F key=value
发送的是multipart/form-data
,-d key=value
发送的是application/x-www-form-url-encoded
。 - 浏览器的 fetch API 当 body 是
FormData
时发送multipart/form-data
,当 body 是URLSearchParams
时发送application/x-www-form-url-encoded
。 - Python requests 中,
request.post()
通过可选参数data=
发送的是application/x-www-form-url-encoded
,通过json=
发送的是application/json
。
- curl 的
Headers
出现在请求和回应中,描述传输的文件类型和安全选项等。下面列出了一些通用的 headers 名,除了通用的 headers 之外也可以自行指定,比如 AWS 就在 Application Load Balancer 中使用了 X-Amzn-Trace-Id
header。
Content-Type
指定文件的类型,是 HTML(text/html
)、JSON(application/json
)、还是纯文本(text/plain
),这决定了返回的内容应该被浏览器渲染出来、以纯文本显示还是作为下载。所有 MIME 类型列表。
Access-Control-Allow-Origin
回应中出现,是否允许别的站点在浏览器上运行时引用我的内容,设为 *
即为允许。
Accept-Language
请求中出现,告诉服务器你的语言。
Referer
请求中出现,告诉服务器你从哪个链接过来的。比如在手机浏览器上从其他搜索引擎跳转到百度知道时会出现一个“返回百度”的按钮,就是通过这个实现的。
Cookies
和 Set-Cookie
见下。
Cookies
Cookies 是一列储存在浏览器中的短文本列表,可以用来储存身份信息、登录状态等。在发送请求时,浏览器会将储存的该网站的 cookies 用 Cookies
header 发出;收到服务器的回应时,浏览器会根据 Set-Cookie
header 中的子段更改储存的 cookies。
Cookies 本质上只是一对被浏览器特殊照顾的 HTTP headers 而已,除浏览器外其他环境中要指定请求中发送的 cookies 完全是可以的。比如 cURL 可以用 --cookie 'key=value'
来发送 cookie,用 --cookie-jar filename.cookies
指定接收到的 Set-Cookie
储存在那个文件中;Python 的 requests 库可以使用 requests.get('some-url.com', cookies={'key': 'value'})
来发送 cookies,还可以在一个 requests.Session
中创建请求来保存从回应中获得的 cookies。
代理
客户端的请求打包、发往代理服务器,代理服务器将请求数据解包、发往目标服务器;目标服务器收到代理服务器的请求后、将回应的数据发往代理服务器,最后代理服务器将回应的数据打包、发到客户端。
代理的用途很广,中间人攻击、抓包进行网络数据分析都是通过代理来完成的。此外,代理还可以用于绕过防火墙的过滤。
代理运行在应用层,提供应用层或传输层接口。
反向代理
反向代理和前向代理是一样的原理,区别只在于哪部分对外面的互联网是可见的。前向代理对服务器隐藏了客户端的 IP,反向代理对客户端隐藏了服务器的原始地址。
VPN
VPN 用于在公网上建立一个虚拟的内网,同时提供加密。VPN 运行于应用层,提供链路层(L2VPN)或网络层(L3VPN)的接口。