网络协议基础 -- 东哥

IP协议

IP协议是网络层协议,提供将数据从一台电脑传递到另一台电脑的能力。但是不校验传递过去的数据是否正确。互联网上的每个节点都必须有一个独立的IP地址。我们现在使用的IP地址都是IPv4标准的。是一个32bit的数字组成。

IP协议头:

image

这里只重点说明一下几个字段:

  • 版本: 4
  • 标识,标志,片偏移:这是IP分片的时候使用的。
  • 生存时间: TTL,表示这个IP数据包可以在网络中生存多长时间。
  • 协议:IP负载里面的数据格式。比如:TCP,UDP之类的。

IP协议头中不包含端口信息,端口是TCP或UDP层次的事情。

其实所有的网络协议都是类似的结构:协议头+负载的方式,然后反复循环组成了数据流。

TCP/UDP 协议

TCP和UDP都属于传输层协议。提供可靠和不可靠数据传输的功能。TCP协议是基于连接的协议,在传输数据之前会首先在传输双方建立通道。只有通道建立之后,才会开始传输数据。UDP数据传输不需要提前建立通道。发送数据的一方只管发送数据,并不关心对方是否收到数据。

相对来讲,在网络状况相对较好的情况下。UDP协议传输数据的效率要比TCP协议要高一些。所以现在有一种解决方案是在UDP协议搭载TCP数据来提高传输效率。就是在UDP协议上添加少量的TCP的特性来保证数据到达的顺序和正确性。

TCP/UDP层只关心端口,所以在TCP头和UDP头中是找不到IP地址字段的。IP地址是IP层关心的东西。

TCP协议头:

image

UDP协议头:

image

完整的以太网帧结构:

image

TCP连接建立的三次握手过程和关闭的四次握手过程:

image

libpcap 编程库

libpcap是一个辅助抓取网卡数据包的库。方便编写数据包分析程序。使用libpcap可以方便地实现一个类似tcpdump的程序。很多编程语言也实现了对libpcap的封装。

使用libpcap进行编程

DNS 协议

DNS协议提供从域名到IP地址的转换服务。使用UDP数据包进行传输的协议,就是基于UDP协议的。新的一些DNS服务器也开始支持基于TCP协议的DNS服务。DNS服务通常使用53端口。

DNS协议主要包含:查询,响应和更新消息。我们这里主要看看查询和响应的报文格式。

DNS消息格式:

Header	    消息头部
Question	DNS请求
Answer	    回答请求的资源记录(Resource Record(s))
Authority	指向域的资源记录
Additional	其他资源记录

DNS消息头格式:

image

图片地址:https://images0.cnblogs.com/blog/384029/201304/02194631-b2a0d6c247a547c9955b91dd1b5ebe29.x-png

Message ID: 消息ID
QR: 0表示请求,1表示响应
QDCOUNT:请求部分条目数 往往是1
ANCOUNT: 响应部分资源记录数

DNS请求内容格式:

image

图片地址:https://images0.cnblogs.com/blog/384029/201304/02203442-4a6d8a568f584f6092d87f32a041dbe3.x-png

QNAME:请求的域名
QTYPE:请求的类型,0x0001  A记录, 0x0002  NS记录, 0x0005  CNAME记录

DNS应答内容格式:

image

图片地址:https://images0.cnblogs.com/blog/384029/201304/02205855-a75a3b78ceea48b380c206f9efe280a8.x-png

DNS应答内容一般都比请求内容大,有的时候甚至会大很多。这就是DNS放大攻击的原来。通过伪造源IP地址,发送少量请求,就可以让受害机器收到大量的应答内容。

HTTP 协议

HTTP协议是一个文本协议。协议的主要内容都是通过文本的方式直观地查看。使用非常广泛。

HTTP协议请求头:

<request-line>
<headers>
<blank line>
[<request-body>]

一个例子:

GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive

比较重要的字段:

  1. 请求方法
  2. 请求地址(url)
  3. 目标主机(host)
  4. 用户身份标识(cookie)
  5. 客户端浏览器标识(User-Agent)
  6. 代理标识(X-Forwarded-For)
  7. 上一个请求地址(referer)

HTTP协议响应头:

<status-line>
<headers>
<blank line>
[<response-body>]

一个例子:

HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122

<html>
<head>
<title>Wrox Homepage</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>

比较重要的字段: 状态:表示成功或失败或错误。 响应类型:html,二进制格式内容,json格式内容等 响应长度:响应长度有多种表示方法。(content-length最常用)

因为协议本身就是文本格式,所以我们其实可以使用telnet就可以方便地完成http协议的请求。(很简单的演示)

Curl 和 Libcurl

这是一个实现了多种协议(HTTP, HTTPS, FTP等)的命令行工具。libcurl是curl攻击的C语言库(现在各种语言应该都有对这个库的封装)。使用libcurl可以方便地在自己的程序中集成类似的功能。这个工具和库现在在很多场合使用非常广泛。当一个应用程序需要通过HTTP协议从外部获取信息或者进行交互的时候,不再需要自己实现HTTP请求和应答处理部分的代码,而是通过集成libcurl就可以方便地实现和http服务器直接的交互。

现在我们的游戏客户端和服务端中都有使用libcurl来完成一些http协议的请求。比如:请求微信登录认证接口,请求IP策略等。

使用libcurl进行开发的示例代码地址:https://curl.haxx.se/libcurl/c/example.html

PhantomJS

这是另一个非常强大的HTTP协议工具。其实这个工具更多突出的是Javascript引擎特性。使用phantomjs可以在命令行打开一个网页,同时可以通过javascript来操作这个网页的内容。可以说phantomjs是一个可以在命令行通过javascript来交互的浏览器。

更多的HTTP工具

  1. Fiddler

msgpack协议

官网地址

在官方网站上可以看到它已经有了多种语言的稳定实现。可以在多种语言中实现跨语言,跨平台的信息交互。是一种高效的二进制序列化格式。非常类似json格式,但是比json格式更高效。(当然可读性不如json)

实现跨语言,跨平台的数据传输最重要的就是定义一种语言和平台无关的数据表示方法(数据类型)和数据传输格式,然后在各种语言中实现语言,平台相关数据类型和自己定义的类型之间的转换。然后按照定义好的格式,组织这些数据类型。这就是序列化的过程。从定义的数据类型和格式到语言相关的数据类型和格式的转化叫做反序列化的过程。

在排查一些可能由于网络数据没有正确传递导致的问题时,分析数据包可以排查到导致问题的直接原因。

msgpack内部定义的数据类型和格式

一起看一下福建项目登录数据包msgpack格式分析。(fj_login.pcap)

请求包中的数字类型,字符串类型。响应包中的gate端口。

protobuff 协议

官网地址

ProtoBuff干脆自己定义了一个网络协议描述语言。非常简单的就可以描述网络通信过程中使用的通信格式。同时,使用他提供的编译器,可以将网络协议描述文件直接转化为对应语言的代码。现在官方支持的语言有:C++ C# GO JAVA PYTHON。现在官方还不支持lua,但是有一些第三方提供了lua语言的支持。

使用protobuf进行网络通信的时候,网络上传输的都是二进制内容。不会像msgpack一样传递类似变量名的内容。通信双方完全通过协议的约定来序列化和反序列化内容。这样使得根据网络传递内容破解通信协议的难度加大,更加安全。但是出现网络传输错误的时候,也更难排查原因。

套接字

定义一个套接字的时候,一般需要指定三个参数。域(domain),类型(type)和协议(protocol)。

我们在定义套接字的时候一般指定的域是:AF_INET,这是指的因特网,底层协议就是IP协议。

套接字类型是在一个域内套接字的类型。比如在英特网的域(AF_INET域)内有流套接字和数据报套接字。流套接字提供的就是有序,可靠,双向的字节流。数据包套接字就是每个包就是一个单独的网络消息。数据包长度有限制,传输过程中,可能乱序,重复或丢失。

一般确定了域和类型之后,套接字支持的协议也就确定了。一般这个字段填0.一般情况下一个域内的一个类型的套接字只支持一种协议。

一个socket创建的代码示例:

int skd = socket(AF_INET, SOCK_STREAM, 0);

AF_INET域下的套接字类型有:SOCK_STREAM, SOCK_DGREAM, SOCK_SEQPACKET, SOCK_RAW。

前两个套接字分布就对应于TCP协议和UDP协议。这个大家在进行socket编程的时候接触的比较多了。

SOCK_SEQPACKET 这个类型的套接字也是提供可靠的数据传输,不过他发送和接受的是固定长度的数据包。只有在接收到完整的数据包之后,才能进行读取。

我们主要了解一下最后一个套接字:原始套接字。

先看一个创建原始套接字的例子:

int skd = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);

有的地方有把AF_INET写作PF_INET的。其实这两个值是一样的,只是一般情况下指定协议的时候使用PF_***,指定地址的时候使用AF_***

原始套接字创建的目的之一是为了方便在用户空间实现自定义的传输层协议。使用IP包来将自定义的数据包从一个主机运送到另一个主机,而这些IP数据包运送的内容是自己定义的。如何接收和处理这些数据也是我们自己定义的。所以,原始套接字允许我们自己填充IP数据包内承载的内容。如果通过setsocketopt设置了原始套接子的IP_HDRINCL选项,则数据包负载中必须包含一个有效的IP头。就是允许我们自己定义自己的IP包头。

这样的原始套接字经常在网络安全类的开发中使用到。比如我们通过原始套接字构造一个独特的IP数据包,包含定制的TCP数据来发送畸形的数据来触发一些漏洞。或者检验一些防护设备的拦截规则。

再看一个packet套接字:

int packet_socket = socket(AF_PACKET, int socket_type, int protocol);

packet套接字允许发送和接收链路层数据包。就是可以自定义MAC地址的数据包。socket_type可以是SOCK_RAW(带有链路层头)和SOCK_DGRAM(没有链路层头)。当protocol设置为htons(ETH_P_ALL)时,所有协议的数据包都会被这个socket接收。

一般只有超级用户,或者专门赋予了创建原始套接字权限的用户才有创建原始套接的权限。

websocket

WebSocket有WebTCP之称,是HTML5的一个新特性。为了让web应用更好,更高效地支持持续数据传输和实时数据传输。在有websockt之前,要想实时地从服务器获取提醒类数据,必须周期性地轮询去服务器检查是否有新的数据可以获取。这样的性能很底下,而且会给服务器造成很大的压力。

轮询和websocket在实时获取数据时的性能差距:

image

随着数据量的增长和压力的增大,websocket表现除了出色的性能。

缺点是:websocket还不是一个成熟的协议,还在不断的变化中。而且IE浏览器对websocket支持并不好。

WebSocket协议的大概原理是这样的。首先建立TCP连接,然后会发送一个HTTP协议的请求,这个请求的含义就是升级协议为websocket。服务器然后回应一个升级成功的消息,后面客户端和服务端就开始使用websocket协议进行通信了。不再会有http协议的消息。

下面的内容已经过时了,但是大家也可以做个参考。

draft-hixie-thewebsocketprotocol-76 草案中websocket的握手协议:

客户端到服务端: 
GET /demo HTTP/1.1 
Host: example.com 
Connection: Upgrade 
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 
Upgrade: WebSocket 
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5 
Origin: http://example.com 
[8-byte security key] 
 
服务端到客户端:
HTTP/1.1 101 WebSocket Protocol Handshake 
Upgrade: WebSocket 
Connection: Upgrade 
WebSocket-Origin: http://example.com 
WebSocket-Location: ws://example.com/demo 
[16-byte hash response]
  1. 逐个字符读取 Sec-WebSocket-Key1头信息中的值,将数值型字符连接到一起放到一个临时字符串里,同时统计所有空格的数量;
  2. 将在第 1 步里生成的数字字符串转换成一个整型数字,然后除以第1步里统计出来的空格数量,将得到的浮点数转换成整数型;
  3. 将第 2 步里生成的整型值转换为符合网络传输的网络字节数组;
  4. 对 Sec-WebSocket-Key2 头信息同样进行第 1 到第 3 步的操作,得到另外一个网络字节数组;
  5. 将 [8-byte security key] 和在第 3,第 4 步里生成的网络字节数组合并成一个 16 字节的数组;
  6. 对第 5 步生成的字节数组使用 MD5 算法生成一个哈希值,这个哈希值就作为安全密钥返回给客户端,以表明服务器端获取了客户端的请求,同意创建 WebSocket 连接

参考资料

  1. 《TCP/IP协议详解》卷一
  2. DNS协议参考
原文地址:https://www.cnblogs.com/zhengchunyuan/p/7421568.html