いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-310 (go-packetメモ-05)(*pcap.Packetの中身を表示)

関連記事

Goメモ-306 (go-packetメモ-01)(ネットワークインターフェースを表示) - いろいろ備忘録日記

Goメモ-307 (go-packetメモ-02)(流れるパケットをキャプチャする)(OpenLive) - いろいろ備忘録日記

Goメモ-308 (go-packetメモ-03)(pcapファイルを読み込み)(OpenOffline) - いろいろ備忘録日記

Goメモ-309 (go-packetメモ-04)(BPFフィルタを設定)(SetBPFFilter) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないうちにメモメモ。。。

GoでWireSharkやtcpdumpのようにパケットを直接見たいときなどに利用できるライブラリに

github.com

pkg.go.dev

というのがあります。

今まで使ったこと無かったのですが、使うと面白かったので自分用のメモ代わりにちょこちょこ残しておこうと思います。

Linux (Ubuntu) 上で遊んでいますので、Windowsの場合はWinPcap (WireSharkをインストールするときについでにインストールできたはず)が必要になると思います。

インストール

libpcap が必要ですので、以下でインストールします。

$ sudo apt install libpcap-dev

あと、tcpdumpが入っていない場合は以下もついでに入れておきます。(これはオプショナルです)

$ sudo apt install tcpdump

また、今回のサンプルでは nc (netcat) を使っていますので、以下もある方が良いです。

$ sudo apt install netcat

試してみる

使い方に関しては、上に挙げている go-packet の godoc の方に詳しく書かれています。

今回は、実際のパケットを表す *pcap.Packet の中身を見てみます。

main.go

// Package main is the example of gopacket.Packet
package main

import (
   "fmt"
   "log"
   "os"
   "os/signal"

   "github.com/google/gopacket"
   "github.com/google/gopacket/layers"
   "github.com/google/gopacket/pcap"
)

var (
   appLog = log.New(os.Stderr, "", 0)
)

func main() {
    if err := run(); err != nil {
        appLog.Panic(err)
    }
}

func run() error {
    const (
        device      = "lo"
        filter      = "tcp port 22222"
        snapshotLen = int32(128)
        promiscuous = false
    )

    // --------------------------------------
    // Open capture handle
    // --------------------------------------
    var (
        handle *pcap.Handle
        err    error
    )

    handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, pcap.BlockForever)
    if err != nil {
        return fmt.Errorf("error open handle: %w", err)
    }
    defer handle.Close()

    // --------------------------------------
    // Apply capture filter (optional)
    // --------------------------------------
    if filter != "" {
        err = handle.SetBPFFilter(filter)
        if err != nil {
            return fmt.Errorf("error apply filter: %w", err)
        }
    }

    // --------------------------------------
    // Set signal handler
    // --------------------------------------
    var (
        sigCh = make(chan os.Signal, 1)
    )

    signal.Notify(sigCh, os.Interrupt)

    // --------------------------------------
    // Make packet source and display.
    // --------------------------------------
    var (
        dataSource   gopacket.PacketDataSource = handle
        decoder      gopacket.Decoder          = handle.LinkType()
        packetSource *gopacket.PacketSource    = gopacket.NewPacketSource(dataSource, decoder)
        packetCh     <-chan gopacket.Packet    = packetSource.Packets()
    )

LOOP:
    for {
        select {
        case <-sigCh:
            break LOOP
        case p, ok := <-packetCh:
            if !ok {
                break LOOP
            }

            see(p)
        }
    }

    return nil
}

func see(p gopacket.Packet) {
    // ダンプ出力(各レイヤー毎の詳細も見れる)
    //appLog.Printf("[Dump] %v", p.Dump())
    // データ (各レイヤー毎のフルパケットデータが見れる)
    //appLog.Printf("[Data] %v", p.Data())

    appLog.Println("------------------------------")
    {
        appLog.Printf("[Capture Length] %v", p.Metadata().CaptureLength)

        ipLayer := p.Layer(layers.LayerTypeIPv4)
        if ipLayer != nil {
            ipv4, ok := ipLayer.(*layers.IPv4)
            if ok {
                appLog.Printf("[Src           ] %v", ipv4.SrcIP)
                appLog.Printf("[Dst           ] %v", ipv4.DstIP)
                appLog.Printf("[Protocol      ] %v", ipv4.Protocol)
            }
        }

        tcpLayer := p.Layer(layers.LayerTypeTCP)
        if tcpLayer != nil {
            tcp, ok := tcpLayer.(*layers.TCP)
            if ok {
                appLog.Printf("[SRC PORT      ] %v", tcp.SrcPort)
                appLog.Printf("[DST PORT      ] %v", tcp.DstPort)
                appLog.Println("[TCP FLAGS     ]")
                appLog.Printf(">>> SYN=%v", tcp.SYN)
                appLog.Printf(">>> ACK=%v", tcp.ACK)
                appLog.Printf(">>> PSH=%v", tcp.PSH)
                appLog.Printf(">>> RST=%v", tcp.RST)
                appLog.Printf(">>> FIN=%v", tcp.FIN)
            }
        }

        appLayer := p.ApplicationLayer()
        if appLayer != nil {
            payload := appLayer.Payload()
            if payload != nil {
                appLog.Printf("[Payload       ] %v", payload)
            }
        }

    }
    appLog.Println("------------------------------")
}

app.sh

#!/usr/bin/env bash

./packet &
echo -n $! > app.pid
exit 0

server.sh

#!/usr/bin/env bash

nc -l -k 127.0.0.1 22222 &
echo -n $! > server.pid
exit 0

client.sh

#!/usr/bin/env bash

echo -n helloworld > .tmp
nc -N 127.0.0.1 22222 < .tmp

exit 0

kill.sh

#!/usr/bin/env bash

kill $(cat ./app.pid)
kill $(cat ./server.pid)

以下、Gitpod上で実行してみた結果です。

gitpod /workspace/go-gopacket-example (main) $ task packet
task: [packet] go build
task: [packet] sudo bash ./app.sh
task: [packet] sleep 1
task: [packet] sudo bash ./server.sh
task: [packet] sudo bash ./client.sh
helloworldtask: [packet] sleep 3
------------------------------
[Capture Length] 74
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 53850
[DST PORT      ] 22222(easyengine)
[TCP FLAGS     ]
>>> SYN=true
>>> ACK=false
>>> PSH=false
>>> RST=false
>>> FIN=false
------------------------------
------------------------------
[Capture Length] 74
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 22222(easyengine)
[DST PORT      ] 53850
[TCP FLAGS     ]
>>> SYN=true
>>> ACK=true
>>> PSH=false
>>> RST=false
>>> FIN=false
------------------------------
------------------------------
[Capture Length] 66
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 53850
[DST PORT      ] 22222(easyengine)
[TCP FLAGS     ]
>>> SYN=false
>>> ACK=true
>>> PSH=false
>>> RST=false
>>> FIN=false
------------------------------
------------------------------
[Capture Length] 76
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 53850
[DST PORT      ] 22222(easyengine)
[TCP FLAGS     ]
>>> SYN=false
>>> ACK=true
>>> PSH=true
>>> RST=false
>>> FIN=false
[Payload       ] [104 101 108 108 111 119 111 114 108 100]
------------------------------
------------------------------
[Capture Length] 66
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 22222(easyengine)
[DST PORT      ] 53850
[TCP FLAGS     ]
>>> SYN=false
>>> ACK=true
>>> PSH=false
>>> RST=false
>>> FIN=false
------------------------------
------------------------------
[Capture Length] 66
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 53850
[DST PORT      ] 22222(easyengine)
[TCP FLAGS     ]
>>> SYN=false
>>> ACK=true
>>> PSH=false
>>> RST=false
>>> FIN=true
------------------------------
------------------------------
[Capture Length] 66
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 22222(easyengine)
[DST PORT      ] 53850
[TCP FLAGS     ]
>>> SYN=false
>>> ACK=true
>>> PSH=false
>>> RST=false
>>> FIN=true
------------------------------
------------------------------
[Capture Length] 66
[Src           ] 127.0.0.1
[Dst           ] 127.0.0.1
[Protocol      ] TCP
[SRC PORT      ] 53850
[DST PORT      ] 22222(easyengine)
[TCP FLAGS     ]
>>> SYN=false
>>> ACK=true
>>> PSH=false
>>> RST=false
>>> FIN=false
------------------------------
task: [packet] sudo bash ./kill.sh

実行すると、ダミーのサーバを 22222 ポートに立てて、そこに通信しています。

それをキャプチャして表示しています。

パケットの情報を表示してみると、ちゃんとTCPヘッダのフラグとかを確認できますね。

SYN,ACKとかPSH, FINとかが見えます。

リポジトリ

上のサンプルなどは、以下のリポジトリでアップしています。ご参考までに。

github.com

参考情報

Goのおすすめ書籍

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。