前言:
最近在学习rpcx,为了更清楚搞懂rpcx的各种协议组成。对rpcx的协议做了一个深入学习。
系统: mac OS 10.14.3
golang: go1.12 darwin/amd64
调试工具:lldb
抓包工具:tcpdump
# (一)tcpdump工具使用
tcpdump是一个网络抓包工具,tcpdump支持针对网络层、协议、主机、网络或端口的过滤。
并提供and、or、not等逻辑语句来帮助你去掉无用的信息。
tcpdump格式信息如下:
参数详解
-a -- 将网络地址和广播地址转变成名字
-d -- 将匹配信息包的代码以人们能够理解的汇编格式给出
-dd -- 将匹配信息包的代码以c语言程序段的格式给出
-ddd -- 将匹配信息包的代码以十进制的形式给出
-e -- 在输出行打印出数据链路层的头部信息
-f -- 将外部的Internet地址以数字的形式打印出来
-l -- 使标准输出变为缓冲行形式
-n -- 不把网络地址转换成名字
-t -- 在输出的每一行不打印时间戳
-v -- 输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息
-vv -- 输出详细的报文信息
-c -- 在收到指定的包的数目后,tcpdump就会停止
-F -- 从指定的文件中读取表达式,忽略其它的表达式
-i -- 指定监听的网络接口
-r -- 从指定的文件中读取包(这些包一般通过-w选项产生)
-w -- 直接将包写入文件中,并不分析和打印出来
-T -- 将监听到的包直接解释为指定的类型的报文,常见的类型有rpc (远程过程调用)和snmp(简单网络管理协议)
-S -- 打印TCP 数据包的顺序号时, 使用绝对的顺序号, 而不是相对的顺序号.
-XX -- 当分析和打印时, tcpdump 会打印每个包的头部数据, 同时会以16进制和ASCII码形式打印出每个包的数据,
其中包括数据链路层的头部.这对于分析一些新协议的数据包很方便.
命令:
1)想要截获所有192.168.1.100 的主机收到的和发出的所有的数据包:
tcpdump host 192.168.1.100
2)想要截获主机192.168.1.100 和主机192.168.1.101 或192.168.1.102的通信,使用命令:(在命令行中使用 括号时,一定要转义)
tcpdump host 192.168.1.100 and (192.168.1.101 or 192.168.1.102)
3)如果想要获取主机192.168.1.100除了和主机192.168.1.101之外所有主机通信的ip包,使用命令:
tcpdump ip host 192.168.1.100 and ! 192.168.1.101
4)如果想要获取主机192.168.1.100接收或发出的telnet包,使用如下命令:
tcpdump tcp port 23 and host 192.168.1.100
5)获取回环网卡8972端口包,例如回环网卡为lo0,(unix/linux)这个可以使用ifconfig查看,使用命令如下:
tcpdump -i lo0 port 8972
6)获取回环网卡8972端口包,顺序打印并打印每个包16进制数据
tcpdump -i lo0 port 8972 -S -XX
(二)编译rpcx和运行tcpdump
2.1第一步编译tcp例子:
源码
https://github.com/rpcx-ecosystem/rpcx-examples3/tree/master/101basic
编译server
go build -o server server.go
编译client
go build -o client_async client/client_async.go
## 2.2第二步运行tcpdump:
(1)启动server、启动对应的server文件
(2)运行tcpdump
tcpdump -i lo0 port 8972 -S -XX
rpcx例子里面默认启动8972端口,由于是本地去测试,我抓的是lo0网卡,lo0网卡为回环网卡,
可以通过ifconfig查看到。
(3)执行client_async
执行client_async,tcpdump抓包信息如下:
14:17:03.977088 IP localhost.58526 > localhost.8972: Flags [S], seq 3652826897, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 407850151 ecr 0,sackOK,eol], length 0
0x0000: 0200 0000 4500 0040 0000 4000 4006 0000 ....E..@..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b711 ..........#.....
0x0020: 0000 0000 b002 ffff fe34 0000 0204 3fd8 .........4....?.
0x0030: 0103 0306 0101 080a 184f 4ca7 0000 0000 .........OL.....
0x0040: 0402 0000 ....
14:17:03.977157 IP localhost.8972 > localhost.58526: Flags [S.], seq 2001029784, ack 3652826898, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 407850151 ecr 407850151,sackOK,eol], length 0
0x0000: 0200 0000 4500 0040 0000 4000 4006 0000 ....E..@..@.@...
0x0010: 7f00 0001 7f00 0001 230c e49e 7745 4a98 ........#...wEJ.
0x0020: d9b9 b712 b012 ffff fe34 0000 0204 3fd8 .........4....?.
0x0030: 0103 0306 0101 080a 184f 4ca7 184f 4ca7 .........OL..OL.
0x0040: 0402 0000 ....
14:17:03.977177 IP localhost.58526 > localhost.8972: Flags [.], ack 2001029785, win 6379, options [nop,nop,TS val 407850151 ecr 407850151], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b712 ..........#.....
0x0020: 7745 4a99 8010 18eb fe28 0000 0101 080a wEJ......(......
0x0030: 184f 4ca7 184f 4ca7 .OL..OL.
14:17:03.977184 IP localhost.8972 > localhost.58526: Flags [.], ack 3652826898, win 6379, options [nop,nop,TS val 407850151 ecr 407850151], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 230c e49e 7745 4a99 ........#...wEJ.
0x0020: d9b9 b712 8010 18eb fe28 0000 0101 080a .........(......
0x0030: 184f 4ca7 184f 4ca7 .OL..OL.
14:17:03.977425 IP localhost.58526 > localhost.8972: Flags [P.], seq 3652826898:3652826961, ack 2001029785, win 6379, options [nop,nop,TS val 407850151 ecr 407850151], length 63
0x0000: 0200 0000 4500 0073 0000 4000 4006 0000 ....E..s..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b712 ..........#.....
0x0020: 7745 4a99 8018 18eb fe67 0000 0101 080a wEJ......g......
0x0030: 184f 4ca7 184f 4ca7 0800 0030 0000 0000 .OL..OL....0....
0x0040: 0000 0000 0000 002f 0000 0005 4172 6974 ......./....Arit
0x0050: 6800 0000 034d 756c 0000 0000 0000 0017 h....Mul........
0x0060: 82a1 41d3 0000 0000 0000 000a a142 d300 ..A..........B..
0x0070: 0000 0000 0000 14 .......
14:17:03.977440 IP localhost.8972 > localhost.58526: Flags [.], ack 3652826961, win 6378, options [nop,nop,TS val 407850151 ecr 407850151], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 230c e49e 7745 4a99 ........#...wEJ.
0x0020: d9b9 b751 8010 18ea fe28 0000 0101 080a ...Q.....(......
0x0030: 184f 4ca7 184f 4ca7 .OL..OL.
14:17:03.978268 IP localhost.8972 > localhost.58526: Flags [P.], seq 2001029785:2001029837, ack 3652826961, win 6378, options [nop,nop,TS val 407850152 ecr 407850151], length 52
0x0000: 0200 0000 4500 0068 0000 4000 4006 0000 ....E..h..@.@...
0x0010: 7f00 0001 7f00 0001 230c e49e 7745 4a99 ........#...wEJ.
0x0020: d9b9 b751 8018 18ea fe5c 0000 0101 080a ...Q.....\......
0x0030: 184f 4ca8 184f 4ca7 0800 8030 0000 0000 .OL..OL....0....
0x0040: 0000 0000 0000 0024 0000 0005 4172 6974 .......$....Arit
0x0050: 6800 0000 034d 756c 0000 0000 0000 000c h....Mul........
0x0060: 81a1 43d3 0000 0000 0000 00c8 ..C.........
14:17:03.978283 IP localhost.58526 > localhost.8972: Flags [.], ack 2001029837, win 6378, options [nop,nop,TS val 407850152 ecr 407850152], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b751 ..........#....Q
0x0020: 7745 4acd 8010 18ea fe28 0000 0101 080a wEJ......(......
0x0030: 184f 4ca8 184f 4ca8 .OL..OL.
14:17:04.978001 IP localhost.58526 > localhost.8972: Flags [F.], seq 3652826961, ack 2001029837, win 6378, options [nop,nop,TS val 407851148 ecr 407850152], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b751 ..........#....Q
0x0020: 7745 4acd 8011 18ea fe28 0000 0101 080a wEJ......(......
0x0030: 184f 508c 184f 4ca8 .OP..OL.
14:17:04.978053 IP localhost.8972 > localhost.58526: Flags [.], ack 3652826962, win 6378, options [nop,nop,TS val 407851148 ecr 407851148], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 230c e49e 7745 4acd ........#...wEJ.
0x0020: d9b9 b752 8010 18ea fe28 0000 0101 080a ...R.....(......
0x0030: 184f 508c 184f 508c .OP..OP.
14:17:04.978236 IP localhost.8972 > localhost.58526: Flags [F.], seq 2001029837, ack 3652826962, win 6378, options [nop,nop,TS val 407851149 ecr 407851148], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 230c e49e 7745 4acd ........#...wEJ.
0x0020: d9b9 b752 8011 18ea fe28 0000 0101 080a ...R.....(......
0x0030: 184f 508d 184f 508c .OP..OP.
14:17:04.978291 IP localhost.58526 > localhost.8972: Flags [.], ack 2001029838, win 6378, options [nop,nop,TS val 407851149 ecr 407851149], length 0
0x0000: 0200 0000 4500 0034 0000 4000 4006 0000 ....E..4..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b752 ..........#....R
0x0020: 7745 4ace 8010 18ea fe28 0000 0101 080a wEJ......(......
0x0030: 184f 508d 184f 508d .OP..OP.
我们发现对应抓包的信息有12个包,每个包之间16进制数据有很多都是相同的。
(三)解析TCP包
对应数据包
14:17:03.977088 IP localhost.58526 > localhost.8972: Flags [S], seq 3652826897, win 65535, options [mss 16344,nop,wscale 6,nop,nop,TS val 407850151 ecr 0,sackOK,eol], length 0
0x0000: 0200 0000 4500 0040 0000 4000 4006 0000 ....E..@..@.@...
0x0010: 7f00 0001 7f00 0001 e49e 230c d9b9 b711 ..........#.....
0x0020: 0000 0000 b002 ffff fe34 0000 0204 3fd8 .........4....?.
0x0030: 0103 0306 0101 080a 184f 4ca7 0000 0000 .........OL.....
0x0040: 0402 0000
网络传输中的字节码都是大端读取的,这个是为了兼容CPU架构。所以在编程中需要转换网络字节码,其实就是小端转成大端。
就像我们的02 00
完整解析可以看如下图:
MAC帧头定义
/*数据帧定义,头14个字节,尾4个字节*/
typedef struct _MAC_FRAME_HEADER
{
char m_cDstMacAddress[6]; //目的mac地址
char m_cSrcMacAddress[6]; //源mac地址
short m_cType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp
}__attribute__((packed))MAC_FRAME_HEADER,*PMAC_FRAME_HEADER;
typedef struct _MAC_FRAME_TAIL
{
unsigned int m_sCheckSum; //数据帧尾校验和
}__attribute__((packed))MAC_FRAME_TAIL, *PMAC_FRAME_TAIL;
结合上图我们可以看到貌似我们并没有使用到mac帧头,图中链路层只占用4字节。这是因为我们使用的是回环链路。针对于链路层有很多的形式。不同的形式,链路层占用的字节长度不同。
IP头结构的定义
/*IP头定义,共20个字节*/
typedef struct _IP_HEADER
{
char m_cVersionAndHeaderLen; //版本信息(前4位),头长度(后4位)
char m_cTypeOfService; // 服务类型8位
short m_sTotalLenOfPacket; //数据包长度
short m_sPacketID; //数据包标识
short m_sSliceinfo; //分片使用
char m_cTTL; //存活时间
char m_cTypeOfProtocol; //协议类型
short m_sCheckSum; //校验和
unsigned int m_uiSourIp; //源ip
unsigned int m_uiDestIp; //目的ip
} __attribute__((packed))IP_HEADER, *PIP_HEADER ;
tcp头结构定义
/*TCP头定义,共20个字节*/
typedef struct _TCP_HEADER
{
short m_sSourPort; // 源端口号16bit
short m_sDestPort; // 目的端口号16bit
unsigned int m_uiSequNum; // 序列号32bit
unsigned int m_uiAcknowledgeNum; // 确认号32bit
short m_sHeaderLenAndFlag; // 前4位:TCP头长度;中6位:保留;后6位:标志位
short m_sWindowSize; // 窗口大小16bit
short m_sCheckSum; // 检验和16bit
short m_surgentPointer; // 紧急数据偏移量16bit
}__attribute__((packed))TCP_HEADER, *PTCP_HEADER;
TCP头中的选项定义
/*TCP头中的选项定义
kind(8bit)+Length(8bit,整个选项的长度,包含前两部分)+内容(如果有的话)
KIND = 1表示 无操作NOP,无后面的部分
2表示 maximum segment 后面的LENGTH就是maximum segment选项的长度(以byte为单位,1+1+内容部分长度)
3表示 windows scale 后面的LENGTH就是 windows scale选项的长度(以byte为单位,1+1+内容部分长度)
4表示 SACK permitted LENGTH为2,没有内容部分
5表示这是一个SACK包 LENGTH为2,没有内容部分
8表示时间戳,LENGTH为10,含8个字节的时间戳
*/
typedef struct _TCP_OPTIONS
{
char m_ckind;
char m_cLength;
char m_cContext[32];
}__attribute__((packed))TCP_OPTIONS, *PTCP_OPTIONS;
(四)tcpdump 解惑链路层协议
图中为tcpdump源码调试过程,如果走的是回环链路, 且你们系统是mac os则回走null_if_print方法。
tcpdump中print-null.c源码:
define NULL_HDRLEN 4
这个 DLT_NULL 数据包头的长度为4字节。它包含主机字节顺序指定系列的32位整数,例如AF_INET。OpenBSD DLT_LOOP包头是相同的,除了整数按网络字节顺序排列。
DLT_NULL是 BSD回路封装,由于mac OS是BSD系类改造的。所以用的是一样的。DLT_LOOP是OpenBSD的回路封装。
回环网卡接口打印的代码如下:
void
null_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h, const u_char *p)
{
u_int length = h->len;
u_int caplen = h->caplen;
uint32_t family;
ndo->ndo_protocol = "null";
if (caplen < NULL_HDRLEN) {
ndo->ndo_ll_header_length += caplen;
nd_print_trunc(ndo);
return;
}
ndo->ndo_ll_header_length += NULL_HDRLEN;
family = GET_HE_U_4(p); //4字节表标识
if ((family & 0xFFFF0000) != 0)
family = SWAPLONG(family);
if (ndo->ndo_eflag)
null_hdr_print(ndo, family, length);
length -= NULL_HDRLEN; //tcp数据包长度
caplen -= NULL_HDRLEN; //整体包长度,包含链路层
p += NULL_HDRLEN;
switch (family) {
case BSD_AFNUM_INET: //ipv4
ip_print(ndo, p, length); //打印数据
break;
case BSD_AFNUM_INET6_BSD: //ipv6
case BSD_AFNUM_INET6_FREEBSD:
case BSD_AFNUM_INET6_DARWIN:
ip6_print(ndo, p, length); //打印数据
break;
case BSD_AFNUM_ISO:
isoclns_print(ndo, p, length);
break;
case BSD_AFNUM_APPLETALK:
atalk_print(ndo, p, length);
break;
case BSD_AFNUM_IPX:
ipx_print(ndo, p, length);
break;
default:
/* unknown AF_ value */
if (!ndo->ndo_eflag)
null_hdr_print(ndo, family, length + NULL_HDRLEN);
if (!ndo->ndo_suppress_default_print)
ND_DEFAULTPRINT(p, caplen);
}
return;
}
图中调试为caplen为网络4层协议的包大小56、length为数据包大小52。他们刚好相差4。
(五)数据包协议解析
5.1 tcp/ip协议解析
TCP/IP 三次握手
14:17:03.977088 IP localhost.58526 > localhost.8972: Flags [S], seq 3652826897, win 65535
14:17:03.977157 IP localhost.8972 > localhost.58526: Flags [S.], seq 2001029784, ack 3652826898
14:17:03.977177 IP localhost.58526 > localhost.8972: Flags [.], ack 2001029785, win 6379
14:17:03.977184 IP localhost.8972 > localhost.58526: Flags [.], ack 3652826898, win 6379
客户端往服务端发送rpc获取请求
14:17:03.977425 IP localhost.58526 > localhost.8972: Flags [P.], seq 3652826898:3652826961, ack 2001029785, win 6379
14:17:03.977440 IP localhost.8972 > localhost.58526: Flags [.], ack 3652826961, win 6378
服务端往客户端回复rpc数据
14:17:03.978268 IP localhost.8972 > localhost.58526: Flags [P.], seq 2001029785:2001029837, ack 3652826961
14:17:03.978283 IP localhost.58526 > localhost.8972: Flags [.], ack 2001029837, win 6378
TCP/IP 四次挥手
14:17:04.978001 IP localhost.58526 > localhost.8972: Flags [F.], seq 3652826961, ack 2001029837
14:17:04.978053 IP localhost.8972 > localhost.58526: Flags [.], ack 3652826962, win 6378
14:17:04.978236 IP localhost.8972 > localhost.58526: Flags [F.], seq 2001029837, ack 3652826962
14:17:04.978291 IP localhost.58526 > localhost.8972: Flags [.], ack 2001029838, win 6378
localhost.58526 客户端
localhost.8972 服务端
从我么的12次请求中可以看到分别有几个部分:
(1)TCP/IP 三次握手
(2)客户端往服务端发送rpc获取请求
(3)服务端往客户端回复rpc数据
(4)TCP/IP 四次挥手
注意:四次挥手并不是每次都是这样的顺序,在双工下。或者是fin延迟情况下。4次挥手的顺序会有所变化。
有兴趣可以去读一下TCP/IP原理。
5.2 rpc协议解析
先看看源代码:
server端:
package main
import (
"flag"
"fmt"
"github.com/smallnest/rpcx/server"
)
var (
addr = flag.String("addr", "0.0.0.0:8972", "server address")
)
type Args struct { //接收数据
A int
B int
}
type Reply struct { //回复数据
C int
}
type Arith int
//调用的方法
func (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) error {
reply.C = args.A * args.B
fmt.Printf("call: %d * %d = %d\n", args.A, args.B, reply.C)
return nil
}
func main() {
flag.Parse()
s := server.NewServer()
s.Register(new(Arith), "")
s.Serve("tcp", *addr)
}
client端:
package main
import (
"context"
"flag"
"log"
"time"
example "github.com/rpcx-ecosystem/rpcx-examples3"
"github.com/smallnest/rpcx/client"
)
var (
addr2 = flag.String("addr", "127.0.0.1:8972", "server address")
)
func main() {
flag.Parse()
d := client.NewPeer2PeerDiscovery("tcp@"+*addr2, "")
xclient := client.NewXClient("Arith", client.Failtry, client.RandomSelect, d, client.DefaultOption)
defer xclient.Close()
args := &example.Args{ //发送数据
A: 10,
B: 20,
}
reply := &example.Reply{} //返回数据
call, err := xclient.Go(context.Background(), "Mul", args, reply, nil) //调用Mul方法
if err != nil {
log.Fatalf("failed to call: %v", err)
}
time.Sleep(1e9)
replyCall := <-call.Done
if replyCall.Error != nil {
log.Fatalf("failed to call: %v", replyCall.Error)
} else {
log.Printf("%d * %d = %d", args.A, args.B, reply.C)
}
}
返回结果A(10) * B(20) = C(200)
对应rpcx消息体结构体,对应文件rpcx/protocol/message.go
type Message struct {
*Header
ServicePath string
ServiceMethod string
Metadata map[string]string //设置授权密码时用到
Payload []byte
data []byte
}
魔术数字,对应文件rpcx/protocol/message.go
const (
magicNumber byte = 0x08
)
func MagicNumber() byte {
return magicNumber
}
client端request
server端Response
数据包:
总结:
1.tcp/ip的四次挥手并不是4次的顺序都是固定的。如果是双工或者是延迟发送,则顺序就不固定。
2.mac OS的链路层占用4个字节;
3.cap包数据包大小为链路层、网络层、传输层、应用层4个部分。ip头数据包大小为网络层、传输层、应用层3个部分。
4.链路层不同的网卡链路层占用字节大小不一样,以太网链路层为MAC帧头。
参考资料:
链路层类型
https://blog.csdn.net/shenwansangz/article/details/47612505
计算机网络-数据结构-MAC帧头-IP头-TCP头-UDP头
https://www.cnblogs.com/cs-lcy/p/7462072.html
IP头,TCP头,UDP头,MAC帧头定义
https://www.cnblogs.com/li-hao/archive/2011/12/07/2279912.html
tcpdump源码
https://github.com/the-tcpdump-group/tcpdump
rpcx源码
https://github.com/smallnest/rpcx
有疑问加站长微信联系(非本文作者)