はじめに
gRPC の Go の QuickStart チュートリアルをやった時の備忘 - untitled をやってみた流れで gRPC の通信を WireShark でキャプチャしてみました。
環境
以下を使います。 セットアップの方法については省略します。
tcpdump でのキャプチャ
TCP ダンプでキャプチャを開始します。
% sudo tcpdump -A -n -i lo -W /tmp/grpc-capture.dump
gRPCサーバーを起動します。
~/path/to/grpc-go/examples/helloworld/greeter_server ((v1.63.0)) % go run main.go 2024/08/29 02:12:36 server listening at [::]:50051
gRPCクライアントを起動します。
~/path/to/grpc-go/examples/helloworld/greeter_client ((v1.63.0)) % go run main.go 2024/08/29 02:15:57 Greeting: Hello world
Wireshark で読み込み
以下のようになっていました。

開始(Frame1-3)は 3ウェイハンドシェイクで始まり、FIN/ACK -> FIN/ACK -> ACK でクローズ(Frame18-20)しています。
Frame4-11 でMagic, SETTINGS, WINDOW_UPDATE, PING というフレームが送信されています。これらはHTTP2のものと思います。 間の Frame12,15 が gRPC のリクエスト、レスポンスのようです。
いくつかわかりやすいヘッダーを見てみます。 Content-Type が「application/grpc」で始まっていない場合、gRPC サーバーは HTTP ステータス 415 (サポートされていないメディア タイプ) で応答する必要があるとのことで、ここでは application/grpc になっていました。
User-Agent は必須ではないようですが、user-agent: grpc-go/1.63.0 となっていました。
Frame 12: 216 bytes on wire (1728 bits), 216 bytes captured (1728 bits)
Encapsulation type: Ethernet (1)
Arrival Time: Aug 29, 2024 11:15:57.925436000 JST
UTC Arrival Time: Aug 29, 2024 02:15:57.925436000 UTC
Epoch Arrival Time: 1724897757.925436000
[Time shift for this packet: 0.000000000 seconds]
[Time delta from previous captured frame: 0.000108000 seconds]
[Time delta from previous displayed frame: 0.000108000 seconds]
[Time since reference or first frame: 0.000396000 seconds]
Frame Number: 12
Frame Length: 216 bytes (1728 bits)
Capture Length: 216 bytes (1728 bits)
[Frame is marked: False]
[Frame is ignored: False]
[Protocols in frame: eth:ethertype:ipv6:tcp:http2:grpc:protobuf]
[Coloring Rule Name: HTTP]
[Coloring Rule String: http || tcp.port == 80 || http2]
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Destination: 00:00:00_00:00:00 (00:00:00:00:00:00)
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
Source: 00:00:00_00:00:00 (00:00:00:00:00:00)
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
Type: IPv6 (0x86dd)
[Stream index: 0]
Internet Protocol Version 6, Src: ::1, Dst: ::1
0110 .... = Version: 6
.... 0000 0000 .... .... .... .... .... = Traffic Class: 0x00 (DSCP: CS0, ECN: Not-ECT)
.... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint: Default (0)
.... .... ..00 .... .... .... .... .... = Explicit Congestion Notification: Not ECN-Capable Transport (0)
.... 0001 1000 1010 0001 0110 = Flow Label: 0x18a16
Payload Length: 162
Next Header: TCP (6)
Hop Limit: 64
Source Address: ::1
[Address Space: Reserved by IETF]
[Special-Purpose Allocation: Loopback Address]
[Source: False]
[Destination: False]
[Forwardable: False]
[Globally Reachable: False]
[Reserved-by-Protocol: True]
Destination Address: ::1
[Address Space: Reserved by IETF]
[Special-Purpose Allocation: Loopback Address]
[Source: False]
[Destination: False]
[Forwardable: False]
[Globally Reachable: False]
[Reserved-by-Protocol: True]
[Stream index: 0]
Transmission Control Protocol, Src Port: 55646, Dst Port: 50051, Seq: 34, Ack: 25, Len: 130
Source Port: 55646
Destination Port: 50051
[Stream index: 0]
[Stream Packet Number: 12]
[Conversation completeness: Complete, WITH_DATA (31)]
..0. .... = RST: Absent
...1 .... = FIN: Present
.... 1... = Data: Present
.... .1.. = ACK: Present
.... ..1. = SYN-ACK: Present
.... ...1 = SYN: Present
[Completeness Flags: ·FDASS]
[TCP Segment Len: 130]
Sequence Number: 34 (relative sequence number)
Sequence Number (raw): 3678708405
[Next Sequence Number: 164 (relative sequence number)]
Acknowledgment Number: 25 (relative ack number)
Acknowledgment number (raw): 3438549865
1000 .... = Header Length: 32 bytes (8)
Flags: 0x018 (PSH, ACK)
000. .... .... = Reserved: Not set
...0 .... .... = Accurate ECN: Not set
.... 0... .... = Congestion Window Reduced: Not set
.... .0.. .... = ECN-Echo: Not set
.... ..0. .... = Urgent: Not set
.... ...1 .... = Acknowledgment: Set
.... .... 1... = Push: Set
.... .... .0.. = Reset: Not set
.... .... ..0. = Syn: Not set
.... .... ...0 = Fin: Not set
[TCP Flags: ·······AP···]
Window: 512
[Calculated window size: 65536]
[Window size scaling factor: 128]
Checksum: 0x00aa [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
TCP Option - No-Operation (NOP)
Kind: No-Operation (1)
TCP Option - No-Operation (NOP)
Kind: No-Operation (1)
TCP Option - Timestamps: TSval 848634857, TSecr 848634857
Kind: Time Stamp Option (8)
Length: 10
Timestamp value: 848634857
Timestamp echo reply: 848634857
[Timestamps]
[Time since first frame in this TCP stream: 0.000396000 seconds]
[Time since previous frame in this TCP stream: 0.000108000 seconds]
[SEQ/ACK analysis]
[iRTT: 0.000029000 seconds]
[Bytes in flight: 130]
[Bytes sent since last PSH flag: 130]
TCP payload (130 bytes)
[PDU Size: 9]
[PDU Size: 100]
[PDU Size: 21]
HyperText Transfer Protocol 2
Stream: SETTINGS, Stream ID: 0, Length 0
Length: 0
Type: SETTINGS (4)
Flags: 0x01, ACK
0000 000. = Unused: 0x00
.... ...1 = ACK: True
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 91, POST /helloworld.Greeter/SayHello
Length: 91
Type: HEADERS (1)
Flags: 0x04, End Headers
00.0 ..0. = Unused: 0x00
..0. .... = Priority: False
.... 0... = Padded: False
.... .1.. = End Headers: True
.... ...0 = End Stream: False
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 838645956272d141fc1eca245f15852a4b631b87eb1968a0ff418ba0e41d139d09b8d800d87f5f8b1d75d0620d263d4c4d65647a8a9acac8b4c7602bb8cae040027465864d833505b11f40899acac8b24d494f6a7f857df7c4d82d
[Header Length: 225]
[Header Count: 8]
Header: :method: POST
Name Length: 7
Name: :method
Value Length: 4
Value: POST
:method: POST
[Unescaped: POST]
Representation: Indexed Header Field
Index: 3
Header: :scheme: http
Name Length: 7
Name: :scheme
Value Length: 4
Value: http
:scheme: http
[Unescaped: http]
Representation: Indexed Header Field
Index: 6
Header: :path: /helloworld.Greeter/SayHello
Name Length: 5
Name: :path
Value Length: 28
Value: /helloworld.Greeter/SayHello
:path: /helloworld.Greeter/SayHello
[Unescaped: /helloworld.Greeter/SayHello]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 5
Header: :authority: localhost:50051
Name Length: 10
Name: :authority
Value Length: 15
Value: localhost:50051
:authority: localhost:50051
[Unescaped: localhost:50051]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 1
Header: content-type: application/grpc
Name Length: 12
Name: content-type
Value Length: 16
Value: application/grpc
content-type: application/grpc
[Unescaped: application/grpc]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 31
Header: user-agent: grpc-go/1.63.0
Name Length: 10
Name: user-agent
Value Length: 14
Value: grpc-go/1.63.0
user-agent: grpc-go/1.63.0
[Unescaped: grpc-go/1.63.0]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 58
Header: te: trailers
Name Length: 2
Name: te
Value Length: 8
Value: trailers
[Unescaped: trailers]
Representation: Literal Header Field with Incremental Indexing - New Name
Header: grpc-timeout: 999250u
Name Length: 12
Name: grpc-timeout
Value Length: 7
Value: 999250u
[Unescaped: 999250u]
Representation: Literal Header Field with Incremental Indexing - New Name
[Full request URI: http://localhost:50051/helloworld.Greeter/SayHello]
[Response in frame: 15]
HyperText Transfer Protocol 2
Stream: DATA, Stream ID: 1, Length 12
Length: 12
Type: DATA (0)
Flags: 0x01, End Stream
0000 .00. = Unused: 0x00
.... 0... = Padded: False
.... ...1 = End Stream: True
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
DATA payload (12 bytes)
[Connection window size (before): 65535]
[Connection window size (after): 65523]
[Stream window size (before): 65535]
[Stream window size (after): 65523]
GRPC Message: /helloworld.Greeter/SayHello, Request
0... .... = Frame Type: Data (0)
.... ...0 = Compressed Flag: Not Compressed (0)
Message Length: 7
Message Data: 7 bytes
Protocol Buffers: /helloworld.Greeter/SayHello,request
Message: <UNKNOWN>
Field(1):
[Field Name: <UNKNOWN>]
.000 1... = Field Number: 1
.... .010 = Wire Type: Length-delimited (2)
Value Length: 5
Value: 776f726c64
Frame 15: 169 bytes on wire (1352 bits), 169 bytes captured (1352 bits)
Encapsulation type: Ethernet (1)
Arrival Time: Aug 29, 2024 11:15:57.925722000 JST
UTC Arrival Time: Aug 29, 2024 02:15:57.925722000 UTC
Epoch Arrival Time: 1724897757.925722000
[Time shift for this packet: 0.000000000 seconds]
[Time delta from previous captured frame: 0.000122000 seconds]
[Time delta from previous displayed frame: 0.000122000 seconds]
[Time since reference or first frame: 0.000682000 seconds]
Frame Number: 15
Frame Length: 169 bytes (1352 bits)
Capture Length: 169 bytes (1352 bits)
[Frame is marked: False]
[Frame is ignored: False]
[Protocols in frame: eth:ethertype:ipv6:tcp:http2:grpc:protobuf]
[Coloring Rule Name: HTTP]
[Coloring Rule String: http || tcp.port == 80 || http2]
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Destination: 00:00:00_00:00:00 (00:00:00:00:00:00)
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
Source: 00:00:00_00:00:00 (00:00:00:00:00:00)
.... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit: Individual address (unicast)
Type: IPv6 (0x86dd)
[Stream index: 0]
Internet Protocol Version 6, Src: ::1, Dst: ::1
0110 .... = Version: 6
.... 0000 0000 .... .... .... .... .... = Traffic Class: 0x00 (DSCP: CS0, ECN: Not-ECT)
.... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint: Default (0)
.... .... ..00 .... .... .... .... .... = Explicit Congestion Notification: Not ECN-Capable Transport (0)
.... 0101 0111 1001 1101 0100 = Flow Label: 0x579d4
Payload Length: 115
Next Header: TCP (6)
Hop Limit: 64
Source Address: ::1
[Address Space: Reserved by IETF]
[Special-Purpose Allocation: Loopback Address]
[Source: False]
[Destination: False]
[Forwardable: False]
[Globally Reachable: False]
[Reserved-by-Protocol: True]
Destination Address: ::1
[Address Space: Reserved by IETF]
[Special-Purpose Allocation: Loopback Address]
[Source: False]
[Destination: False]
[Forwardable: False]
[Globally Reachable: False]
[Reserved-by-Protocol: True]
[Stream index: 0]
Transmission Control Protocol, Src Port: 50051, Dst Port: 55646, Seq: 55, Ack: 181, Len: 83
Source Port: 50051
Destination Port: 55646
[Stream index: 0]
[Stream Packet Number: 15]
[Conversation completeness: Complete, WITH_DATA (31)]
..0. .... = RST: Absent
...1 .... = FIN: Present
.... 1... = Data: Present
.... .1.. = ACK: Present
.... ..1. = SYN-ACK: Present
.... ...1 = SYN: Present
[Completeness Flags: ·FDASS]
[TCP Segment Len: 83]
Sequence Number: 55 (relative sequence number)
Sequence Number (raw): 3438549895
[Next Sequence Number: 138 (relative sequence number)]
Acknowledgment Number: 181 (relative ack number)
Acknowledgment number (raw): 3678708552
1000 .... = Header Length: 32 bytes (8)
Flags: 0x018 (PSH, ACK)
000. .... .... = Reserved: Not set
...0 .... .... = Accurate ECN: Not set
.... 0... .... = Congestion Window Reduced: Not set
.... .0.. .... = ECN-Echo: Not set
.... ..0. .... = Urgent: Not set
.... ...1 .... = Acknowledgment: Set
.... .... 1... = Push: Set
.... .... .0.. = Reset: Not set
.... .... ..0. = Syn: Not set
.... .... ...0 = Fin: Not set
[TCP Flags: ·······AP···]
Window: 512
[Calculated window size: 65536]
[Window size scaling factor: 128]
Checksum: 0x007b [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
TCP Option - No-Operation (NOP)
Kind: No-Operation (1)
TCP Option - No-Operation (NOP)
Kind: No-Operation (1)
TCP Option - Timestamps: TSval 848634858, TSecr 848634858
Kind: Time Stamp Option (8)
Length: 10
Timestamp value: 848634858
Timestamp echo reply: 848634858
[Timestamps]
[Time since first frame in this TCP stream: 0.000682000 seconds]
[Time since previous frame in this TCP stream: 0.000122000 seconds]
[SEQ/ACK analysis]
[This is an ACK to the segment in frame: 14]
[The RTT to ACK the segment was: 0.000122000 seconds]
[iRTT: 0.000029000 seconds]
[Bytes in flight: 83]
[Bytes sent since last PSH flag: 83]
TCP payload (83 bytes)
[PDU Size: 23]
[PDU Size: 27]
[PDU Size: 33]
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 14, 200 OK
Length: 14
Type: HEADERS (1)
Flags: 0x04, End Headers
00.0 ..0. = Unused: 0x00
..0. .... = Priority: False
.... 0... = Padded: False
.... .1.. = End Headers: True
.... ...0 = End Stream: False
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 885f8b1d75d0620d263d4c4d6564
[Header Length: 54]
[Header Count: 2]
Header: :status: 200 OK
Name Length: 7
Name: :status
Value Length: 3
Value: 200
:status: 200
[Unescaped: 200]
Representation: Indexed Header Field
Index: 8
Header: content-type: application/grpc
Name Length: 12
Name: content-type
Value Length: 16
Value: application/grpc
content-type: application/grpc
[Unescaped: application/grpc]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 31
[Time since request: 0.000286000 seconds]
[Request in frame: 12]
HyperText Transfer Protocol 2
Stream: DATA, Stream ID: 1, Length 18
Length: 18
Type: DATA (0)
Flags: 0x00
0000 .00. = Unused: 0x00
.... 0... = Padded: False
.... ...0 = End Stream: False
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
DATA payload (18 bytes)
[Connection window size (before): 65535]
[Connection window size (after): 65517]
[Stream window size (before): 65535]
[Stream window size (after): 65517]
GRPC Message: /helloworld.Greeter/SayHello, Response
0... .... = Frame Type: Data (0)
.... ...0 = Compressed Flag: Not Compressed (0)
Message Length: 13
Message Data: 13 bytes
Protocol Buffers: /helloworld.Greeter/SayHello,response
Message: <UNKNOWN>
Field(1):
[Field Name: <UNKNOWN>]
.000 1... = Field Number: 1
.... .010 = Wire Type: Length-delimited (2)
Value Length: 11
Value: 48656c6c6f20776f726c64
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 24
Length: 24
Type: HEADERS (1)
Flags: 0x05, End Headers, End Stream
00.0 ..0. = Unused: 0x00
..0. .... = Priority: False
.... 0... = Padded: False
.... .1.. = End Headers: True
.... ...1 = End Stream: True
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 40889acac8b21234da8f013040899acac8b5254207317f00
[Header Length: 40]
[Header Count: 2]
Header: grpc-status: 0
Name Length: 11
Name: grpc-status
Value Length: 1
Value: 0
[Unescaped: 0]
Representation: Literal Header Field with Incremental Indexing - New Name
Header: grpc-message:
Name Length: 12
Name: grpc-message
Value Length: 0
Value:
[Unescaped: ]
Representation: Literal Header Field with Incremental Indexing - New Name
[Time since request: 0.000286000 seconds]
[Request in frame: 12]
ステータスコードは HTTP と gRPC で2つあります。 今回のケースでは HTTP のステータスコードは 200 となっていて、gRPC のステータスコードは 0 となっていました。
Header: :status: 200 OK
Name Length: 7
Name: :status
Value Length: 3
Value: 200
:status: 200
[Unescaped: 200]
Representation: Indexed Header Field
Index: 8
Header: content-type: application/grpc
Name Length: 12
Name: content-type
Value Length: 16
Value: application/grpc
content-type: application/grpc
[Unescaped: application/grpc]
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 31
:
:
:
Header: grpc-status: 0
Name Length: 11
Name: grpc-status
Value Length: 1
Value: 0
[Unescaped: 0]
Representation: Literal Header Field with Incremental Indexing - New Name
Header: grpc-message:
Name Length: 12
Name: grpc-message
Value Length: 0
Value:
[Unescaped: ]
Representation: Literal Header Field with Incremental Indexing - New Name
[Time since request: 0.000286000 seconds]
[Request in frame: 12]
ただ、コードを見ると gRPC のステータスコードは何が返されることになっても HTTP のステータスコードは 200 が返るようになっているように見えます。
まとめ
gRPC の通信を tcpdump で取得して Wireshark で覗いてみました。 HTTP2 の元で動く gRPC の通信の流れがなんとなくつかめました。