いろいろ備忘録日記

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

Goメモ-309 (go-packetメモ-04)(BPFフィルタを設定)(SetBPFFilter)

関連記事

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

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

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

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

試してみる

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

今回は、BPF (Berkeley Packet Filter) フィルタを設定する方法についてです。

pcap.SetBPFFilter 関数を使います。

main.go

// Package main is the example of go-packet with BPF (Berkeley Packet Filter) filter
package main

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

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

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

func main() {
    if err := run(); err != nil {
        panic(err)
    }
}

func run() error {
    const (
        pcapfile = "example.pcap"
        filter   = "icmp or icmp6"
    )

    defer func() { appLog.Println("DONE") }()

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

    handle, err = pcap.OpenOffline(pcapfile)
    if err != nil {
        return fmt.Errorf("error open handle: %w", err)
    }
    defer handle.Close()

    // --------------------------------------
    // Apply capture filter
    //
    // # filter examples:
    //   - ip src 192.168.1.1
    //   - ip dst 192.168.1.2
    //   - ip host 192.168.1.1 and ip host 192.168.1.2
    //   - tcp port 80
    //   - udp port 53
    //   - icmp or icmp6
    //   - ether src aa:bb:cc:dd:ee:ff
    //   - ether dst aa:bb:cc:dd:ee:ff
    //   - vlan 100
    //   - ip host 192.168.1.1 and tcp port 80
    // --------------------------------------
    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()
    )
    appLog.Println("START")

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

            appLog.Println(p)
        }
    }

    return nil
}

tcpdump.sh

#!/usr/bin/env bash

tcpdump -t -n -i lo -w example.pcap &
echo $! > tcpdump.pid
exit 0

ping.sh

#!/usr/bin/env bash

timeout 3s ping localhost
exit 0

kill.sh

#!/usr/bin/env bash

kill $(cat tcpdump.pid)
exit 0

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

gitpod /workspace/go-gopacket-example (main) $ task bpffilter
task: [bpffilter] go build
task: [bpffilter] sudo bash ./tcpdump.sh
task: [bpffilter] bash ./ping.sh
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.014 ms
tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.034 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.039 ms
task: [bpffilter] sudo bash ./kill.sh
33 packets captured
66 packets received by filter
0 packets dropped by kernel
task: [bpffilter] sleep 1
task: [bpffilter] sudo ./bpffilter
START
PACKET: 118 bytes, wire length 118 cap length 118 @ 2023-04-18 02:07:43.05961 +0000 UTC
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..104..] SrcMAC=00:00:00:00:00:00 DstMAC=00:00:00:00:00:00 EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6     {Contents=[..40..] Payload=[..64..] Version=6 TrafficClass=0 FlowLabel=53830 Length=64 NextHeader=ICMPv6 HopLimit=64 SrcIP=::1 DstIP=::1 HopByHop=nil}
- Layer 3 (04 bytes) = ICMPv6   {Contents=[128, 0, 32, 129] Payload=[..60..] TypeCode=EchoRequest Checksum=8321 TypeBytes=[]}
- Layer 4 (00 bytes) = ICMPv6Echo       {Contents=[] Payload=[] Identifier=12517 SeqNumber=2}

PACKET: 118 bytes, wire length 118 cap length 118 @ 2023-04-18 02:07:43.059619 +0000 UTC
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..104..] SrcMAC=00:00:00:00:00:00 DstMAC=00:00:00:00:00:00 EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6     {Contents=[..40..] Payload=[..64..] Version=6 TrafficClass=0 FlowLabel=275845 Length=64 NextHeader=ICMPv6 HopLimit=64 SrcIP=::1 DstIP=::1 HopByHop=nil}
- Layer 3 (04 bytes) = ICMPv6   {Contents=[129, 0, 31, 129] Payload=[..60..] TypeCode=EchoReply Checksum=8065 TypeBytes=[]}
- Layer 4 (00 bytes) = ICMPv6Echo       {Contents=[] Payload=[] Identifier=12517 SeqNumber=2}

PACKET: 118 bytes, wire length 118 cap length 118 @ 2023-04-18 02:07:44.07939 +0000 UTC
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..104..] SrcMAC=00:00:00:00:00:00 DstMAC=00:00:00:00:00:00 EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6     {Contents=[..40..] Payload=[..64..] Version=6 TrafficClass=0 FlowLabel=53830 Length=64 NextHeader=ICMPv6 HopLimit=64 SrcIP=::1 DstIP=::1 HopByHop=nil}
- Layer 3 (04 bytes) = ICMPv6   {Contents=[128, 0, 222, 50] Payload=[..60..] TypeCode=EchoRequest Checksum=56882 TypeBytes=[]}
- Layer 4 (00 bytes) = ICMPv6Echo       {Contents=[] Payload=[] Identifier=12517 SeqNumber=3}

PACKET: 118 bytes, wire length 118 cap length 118 @ 2023-04-18 02:07:44.079401 +0000 UTC
- Layer 1 (14 bytes) = Ethernet {Contents=[..14..] Payload=[..104..] SrcMAC=00:00:00:00:00:00 DstMAC=00:00:00:00:00:00 EthernetType=IPv6 Length=0}
- Layer 2 (40 bytes) = IPv6     {Contents=[..40..] Payload=[..64..] Version=6 TrafficClass=0 FlowLabel=275845 Length=64 NextHeader=ICMPv6 HopLimit=64 SrcIP=::1 DstIP=::1 HopByHop=nil}
- Layer 3 (04 bytes) = ICMPv6   {Contents=[129, 0, 221, 50] Payload=[..60..] TypeCode=EchoReply Checksum=56626 TypeBytes=[]}
- Layer 4 (00 bytes) = ICMPv6Echo       {Contents=[] Payload=[] Identifier=12517 SeqNumber=3}

DONE

実行すると、最初に3秒間ほど tcpdump コマンドを実行して ping (ICMP エコーメッセージ) をキャプチャして、pcapファイルを出力します。

その後、それをgo-packetを使って読み込んでパケットをBPFフィルタ設定して表示しています。

pingコマンドを実行する際に -4 オプションを付けていないので、ICMPv6 でメッセージが送受信されています。

なので、上のソースのBPFフィルタを icmp or icmp6 から icmp にして、実行すると何も表示されなくなります。

ちゃんとフィルタリングされていますね。

リポジトリ

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

github.com

参考情報

Goのおすすめ書籍

Go言語による並行処理

Go言語による並行処理

Amazon


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

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