> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Capture and Analyze gRPC Packets Using Wireshark
- URL: https://huizhou92.com/how-to-capture-and-analyze-grpc-packets-2/
- Published: 2026-09-06T11:40:58.000Z
- Updated: 2026-09-06T11:40:58.000Z
- Description: How to Capture and Analyze gRPC Packets Using Wireshark. Introduce Wireshark is a popular tool for capturing network packets. It can not only capture packe。
- Author: huizhou92
- Tags: tech, grpc, Wireshark, #lang-en

## Introduce

[Wireshark](https://en.wikipedia.org/wiki/Wireshark?ref=huizhou92.com) is a popular tool for capturing network packets. It can not only capture packets itself but also parse packet files captured by tcpdump.  
[gRPC](https://grpc.io/?ref=huizhou92.com) is a high-performance RPC framework developed by Google, based on the HTTP/2 protocol and the `protobuf` serialization protocol.  
This article mainly introduces how to capture gRPC packets using Wireshark and parse the packet content.

> This article was first published in the medium MPP plan. If you are a Medium user, please follow me on [Medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

Wireshark version: 4.2.2

## Configuration

Since gRPC uses the `protobuf` serialization protocol, we need to add the `protobuf` file path.  
Click `Wireshark -> Preferences... -> Protocols -> Protobuf -> Protobuf search paths -> Edit...`  
Click + to add the path of your `protobuf` file. Don't forget to check the Load all files on the right side.  
![Pasted image 20240511225144](https://images.yixiao9206.cn/blog-images/2024/05/1a9713fe0a3351183153022057ec8fd0.png)

## Specific Operations

First, we write a simple gRPC service,

```proto
syntax = "proto3";  
option go_package = "example.com/hxzhouh/go-example/grpc/helloworld/api";  
package api;  
  
// The greeting service definition.  
service Greeter {  
  // Sends a greeting  
  rpc SayHello (HelloRequest) returns (HelloReply) {}  
}  
  
// The request message containing the user's name.  
message HelloRequest {  
  string name = 1;  
}  
  
// The response message containing the greetings  
message HelloReply {  
  string message = 1;  
}

```

It has just one function `Greeter`. After completing the server-side code, run it.

```go
type server struct {  
    api.UnimplementedGreeterServer  
}  
  
func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloReply, error) {  
    log.Printf("Received: %v", in.GetName())  
    return &api.HelloReply{Message: "Hello " + in.GetName()}, nil  
}  
  
func main() {  
    lis, err := net.Listen("tcp", ":50051")  
    if err != nil {  
       log.Fatalf("failed to listen: %v", err)  
    }  
    s := grpc.NewServer()  
    api.RegisterGreeterServer(s, &server{})  
    if err := s.Serve(lis); err != nil {  
       log.Fatalf("failed to serve: %v", err)  
    }  
}

```

Then open Wireshark, select the local network card, and listen to `tcp.port == 50051`.

> If you are new to Wireshark, I recommend reading this article first: [https://www.lifewire.com/wireshark-tutorial-4143298](https://www.lifewire.com/wireshark-tutorial-4143298?ref=huizhou92.com)

![Pasted image 20240511230443](https://images.yixiao9206.cn/blog-images/2024/05/4eeb7c7807a7631de7fe25d9fdbf47d3.png)  
![Pasted image 20240511230512](https://images.yixiao9206.cn/blog-images/2024/05/cb06c62507b550a76232004d2a577f03.png)

### Unary Function

Now we have a gRPC service running on the local `50051` port. We can use `BloomRPC` or any other tool you like to initiate an RPC request to the server, or use the following code:

```go
func Test_server_SayHello(t *testing.T) {  
    // Set up a connection to the server.  
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())  
    if err != nil {  
       log.Fatalf("did not connect: %v", err)  
    }  
    defer conn.Close()  
    c := api.NewGreeterClient(conn)  
  
    // Contact the server and print out its response.  
    name := "Hello"  
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)  
    defer cancel()  
    r, err := c.SayHello(ctx, &api.HelloRequest{Name: name})  
    if err != nil {  
       log.Fatalf("could not greet: %v", err)  
    }  
    log.Printf("Greeting: %s", r.GetMessage())  
}

```

At this point, Wireshark should be able to capture the traffic packets.  
![Pasted image 20240511232027](https://images.yixiao9206.cn/blog-images/2024/05/7bc4dfc35da907893667cd8d2054a170.png)  
As mentioned earlier, `gRPC = HTTP2 + protobuf`, and since we have already loaded the `protobuf` file, we should now be able to parse the packet.  
Use the Wireshark shortcut `shift+command+U` or click `Analyze -> Decode As...` and set the packet to be decoded as HTTP2 format.  
![Pasted image 20240511232316](https://images.yixiao9206.cn/blog-images/2024/05/cf93744e0a24f21827748b9c3c2d1953.png)  
At this point, we can see the request clearly.

![Pasted image 20240511232448](https://images.yixiao9206.cn/blog-images/2024/05/143b988235f1d9f4123c2e7a3aed4da6.png)  
![Pasted image 20240511232507](https://images.yixiao9206.cn/blog-images/2024/05/8e4a2e3af863de81124d29cd997b8496.png)  
![Pasted image 20240511232539](https://images.yixiao9206.cn/blog-images/2024/05/7470e60a7cd0364702e9cf56201168bd.png)

### Metadata

We know that gRPC metadata is transmitted through HTTP2 headers. Now let's verify this by capturing packets.  
Slightly modify the client code:

```go
func Test_server_SayHello(t *testing.T) {  
    // Set up a connection to the server.  
   ..... 
   // add md 
    md := map[string][]string{"timestamp": {time.Now().Format(time.Stamp)}}  
    md["testmd"] = []string{"testmd"}  
    ctx := metadata.NewOutgoingContext(context.Background(), md)  
    // Contact the server and print out its response.  
    name := "Hello"  
    ctx, cancel := context.WithTimeout(ctx, time.Second)  
   ....
}

```

Then capture packets again. We can see that `md` is indeed placed in the header.  
![Pasted image 20240512154517](https://images.yixiao9206.cn/blog-images/2024/05/36eefdf35b8f66f40824a5702409a44c.png)

We also see `grpc-timeout` in the header, indicating that the request timeout is also placed in the header. The specific details may be covered in a dedicated article, but today we focus on packet capturing.

### TLS

The examples above use plaintext transmission, and we used `grpc.WithInsecure()` when dialing. However, in a production environment, we generally use TLS for encrypted transmission. Detailed information can be found in my previous article.  
[https://medium.com/gitconnected/secure-communication-with-grpc-from-ssl-tls-certification-to-san-certification-d9464c3d706f](https://medium.com/gitconnected/secure-communication-with-grpc-from-ssl-tls-certification-to-san-certification-d9464c3d706f?ref=huizhou92.com)  
Let's modify the server-side code:  
[https://gist.github.com/hxzhouh/e08546cf0457d28a614d59ec28870b11](https://gist.github.com/hxzhouh/e08546cf0457d28a614d59ec28870b11?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/e08546cf0457d28a614d59ec28870b11?ref=huizhou92.com)

```go
func main() {  
  
    certificate, err := tls.LoadX509KeyPair("./keys/server.crt", "./keys/server.key")  
    if err != nil {  
       log.Fatalf("Failed to load key pair: %v", err)  
    }  
    certPool := x509.NewCertPool()  
    ca, err := os.ReadFile("./keys/ca.crt")  
    if err != nil {  
       log.Fatalf("Failed to read ca: %v", err)  
    }  
  
    if ok := certPool.AppendCertsFromPEM(ca); !ok {  
       log.Fatalf("Failed to append ca certificate")  
    }  
  
    opts := []grpc.ServerOption{  
       grpc.Creds( // Enable TLS for all incoming connections  
          credentials.NewTLS(&tls.Config{  
             ClientAuth:   tls.RequireAndVerifyClientCert,  
             Certificates: []tls.Certificate{certificate},  
             ClientCAs:    certPool,  
          },  
          )),  
    }  
  
    listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", 50051))  
    if err != nil {  
       log.Fatalf("failed to listen %d port", 50051)  
    }  
    // Create a new gRPC server instance with the provided TLS server credentials
    s := grpc.NewServer(opts...)  
    api.RegisterGreeterServer(s, &server{})  
    log.Printf("server listening at %v", listen.Addr())  
    if err := s.Serve(listen); err != nil {  
       log.Fatalf("Failed to serve: %v", err)  
    }  
}

```

Now let's also modify the client code:  
[https://gist.github.com/hxzhouh/46a7a31e2696b87fe6fb83c8ce7e036c](https://gist.github.com/hxzhouh/46a7a31e2696b87fe6fb83c8ce7e036c?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/46a7a31e2696b87fe6fb83c8ce7e036c?ref=huizhou92.com)

```go
func Test_server_SayHello(t *testing.T) {  
    certificate, err := tls.LoadX509KeyPair("./keys/client.crt", "./keys/client.key")  
    if err != nil {  
       log.Fatalf("Failed to load client key pair, %v", err)  
    }  
  
    certPool := x509.NewCertPool()  
    ca, err := os.ReadFile("./keys/ca.crt")  
    if err != nil {  
       log.Fatalf("Failed to read %s, error: %v", "./keys/ca.crt", err)  
    }  
  
    if ok := certPool.AppendCertsFromPEM(ca); !ok {  
       log.Fatalf("Failed to append ca certs")  
    }  
  
    opts := []grpc.DialOption{  
       grpc.WithTransportCredentials(credentials.NewTLS(  
          &tls.Config{  
             ServerName:   "localhost",  
             Certificates: []tls.Certificate{certificate},  
             RootCAs:      certPool,  
          })),  
    }  
    // Establish connection to the server
    conn, err := grpc.Dial("localhost:50051", opts...)  
    if err != nil {  
       log.Fatalf("Connect to %s failed", "localhost:50051")  
    }  
    defer conn.Close()  
  
    client := api.NewGreeterClient(conn)  
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)  
    defer cancel()  
    r, err := client.SayHello(ctx, &api.HelloRequest{Name: "Hello"})  
    if err != nil {  
       log.Printf("Failed to greet, error: %v", err)  
    } else {  
       log.Printf("Greeting: %v", r.GetMessage())  
    }  
}

```

At this point, we can capture packets again and use the same method to decode them. However, we will find that decoding as HTTP2 is no longer possible, but we can decode it as TLS1.3.

![Pasted image 20240519210518](https://images.yixiao9206.cn/blog-images/2024/05/61ece2aeee6ae2976ded717d45f08b52.png)  
![Pasted image 20240519211040](https://images.yixiao9206.cn/blog-images/2024/05/f07e7f5de6fffa9b5c54ea6ce4a1212f.png)

## Conclusion

This article summarizes the basic process of using Wireshark to capture gRPC packets.  
By capturing packets, we learn that gRPC parameter transmission is done through HTTP2 data frames, and CTX and other metadata are transmitted through headers. These concepts might be familiar, but hands-on experiments enhance understanding.  
With TLS, we can achieve secure gRPC communication. In the next article, we will attempt to decrypt TLS packets.

## References

- [Wireshark Tutorial](https://www.wireshark.org/docs/wsug%5Fhtml%5Fchunked/ChapterIntroduction.html?ref=huizhou92.com)
- [https://grpc.io/blog/wireshark/](https://grpc.io/blog/wireshark/?ref=huizhou92.com)
- [https://www.lifewire.com/wireshark-tutorial-4143298](https://www.lifewire.com/wireshark-tutorial-4143298?ref=huizhou92.com)