前言:

最近在学习rpcx,为了更清楚搞懂rpcx的各种协议组成。对rpcx的协议做了一个深入学习。

系统: mac OS 10.14.3
golang: go1.12 darwin/amd64
调试工具:lldb
抓包工具:tcpdump

# (一)tcpdump工具使用
tcpdump是一个网络抓包工具,tcpdump支持针对网络层、协议、主机、网络或端口的过滤。
并提供and、or、not等逻辑语句来帮助你去掉无用的信息。
tcpdump格式信息如下:
1.png
参数详解

 -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

2.png
rpcx例子里面默认启动8972端口,由于是本地去测试,我抓的是lo0网卡,lo0网卡为回环网卡,
可以通过ifconfig查看到。
3.png

(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

完整解析可以看如下图:
4.png

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 解惑链路层协议

5.png
图中为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;
}

6.png
图中调试为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)
   }

}

7.png
返回结果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
8.png

server端Response
9.png

数据包:
10.png

总结:

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


c_rain
23 声望8 粉丝