関連記事
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) - いろいろ備忘録日記
Goメモ-310 (go-packetメモ-05)(*pcap.Packetの中身を表示) - いろいろ備忘録日記
Goメモ-311 (go-packetメモ-06)(*layers.Ethernetの情報を表示) - いろいろ備忘録日記
Goメモ-312 (go-packetメモ-07)(*layers.ARPの情報を表示) - いろいろ備忘録日記
Goメモ-313 (go-packetメモ-08)(*layers.ICMPv4の情報を表示) - いろいろ備忘録日記
Goメモ-314 (go-packetメモ-09)(*layers.IPv4の情報を表示) - いろいろ備忘録日記
Goメモ-315 (go-packetメモ-10)(*layers.TCPの情報を表示) - いろいろ備忘録日記
Goメモ-316 (go-packetメモ-11)(*layers.UDPの情報を表示) - いろいろ備忘録日記
Goメモ-317 (go-packetメモ-12)(アプリケーションレイヤーの情報を表示) - いろいろ備忘録日記
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。忘れないうちにメモメモ。。。
GoでWireSharkやtcpdumpのようにパケットを直接見たいときなどに利用できるライブラリに
というのがあります。
今まで使ったこと無かったのですが、使うと面白かったので自分用のメモ代わりにちょこちょこ残しておこうと思います。
Linux (Ubuntu) 上で遊んでいますので、Windowsの場合はWinPcap (WireSharkをインストールするときについでにインストールできたはず)が必要になると思います。
インストール
libpcap
が必要ですので、以下でインストールします。
$ sudo apt install libpcap-dev
あと、tcpdumpが入っていない場合は以下もついでに入れておきます。(これはオプショナルです)
$ sudo apt install tcpdump
試してみる
使い方に関しては、上に挙げている go-packet の godoc の方に詳しく書かれています。
今回は、HTTPの中身を見てみます。
アプリケーションレイヤーを取得して、ペイロードが取れれば後はそれをHTTPだと解釈してやれば良いはずです。
main.go
// Package main is the example of HTTP using go-packet. package main import ( "bufio" "fmt" "io" "log" "net/http" "os" "os/signal" "strings" "time" "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 { panic(err) } } func run() error { const ( device = "lo" filter = "tcp and port 12345" snapshotLen = int32(1600) promiscuous = false timeout = 1 * time.Second ) defer func() { appLog.Println("DONE") }() // -------------------------------------- // Open capture handle // -------------------------------------- var ( handle *pcap.Handle err error ) handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout) 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 ( doneCh = make(chan struct{}) 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: close(doneCh) break LOOP case p, ok := <-packetCh: if !ok { break LOOP } if err = see(p); err != nil { return err } } } return nil } func see(p gopacket.Packet) error { // // レイヤーを確認 // var ( tcpLayer gopacket.Layer ) tcpLayer = p.Layer(layers.LayerTypeTCP) if tcpLayer == nil { return nil } // // ペイロードを取得 // var ( tcp = tcpLayer.(*layers.TCP) payload = tcp.LayerPayload() ) if len(payload) == 0 { appLog.Printf("[Src Port ] %v", tcp.SrcPort) appLog.Printf("[Dst Port ] %v", tcp.DstPort) appLog.Printf("[Seq Number ] %v", tcp.Seq) appLog.Printf("[Ack Number ] %v", tcp.Ack) appLog.Printf("[Window Size ] %v", tcp.Window) appLog.Printf("[TCP Flags - SYN] %v", tcp.SYN) appLog.Printf("[TCP Flags - ACK] %v", tcp.ACK) appLog.Printf("[TCP Flags - PSH] %v", tcp.PSH) appLog.Printf("[TCP Flags - RST] %v", tcp.RST) appLog.Printf("[TCP Flags - FIN] %v", tcp.FIN) appLog.Printf("[Checksum ] %v", tcp.Checksum) appLog.Printf("[Urgent Pointer ] %v", tcp.Urgent) appLog.Println("------------------------------------") return nil } // // ペイロードを文字列に変換してHTTPとしての解析を試みる // var ( payloadStr = string(payload) reader = bufio.NewReader(strings.NewReader(payloadStr)) ) defer func() { appLog.Println("------------------------------------") }() if isRequest(payloadStr) { // // HTTPリクエスト // var ( req *http.Request err error ) req, err = http.ReadRequest(reader) if err != nil { return err } appLog.Println("HTTP Method:", req.Method) appLog.Println("HTTP URL:", req.URL) appLog.Println("HTTP Protocol:", req.Proto) appLog.Println("HTTP Headers:") for name, values := range req.Header { appLog.Printf(" %s: %s\n", name, strings.Join(values, ", ")) } } if isResponse(payloadStr) { // // HTTPレスポンス // var ( resp *http.Response body []byte err error ) resp, err = http.ReadResponse(reader, nil) if err != nil { return err } body, err = io.ReadAll(resp.Body) if err != nil { return err } defer resp.Body.Close() appLog.Println("HTTP Status Code:", resp.StatusCode) appLog.Println("HTTP Protocol:", resp.Proto) appLog.Println("HTTP Headers:") for name, values := range resp.Header { appLog.Printf(" %s: %s\n", name, strings.Join(values, ", ")) } appLog.Println("BODY:", string(body)) } return nil } func isResponse(s string) bool { return strings.HasPrefix(s, "HTTP") } func isRequest(s string) bool { var ( methods = []string{ "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "CONNECT", "PATCH", } target = s ) for _, method := range methods { if strings.HasPrefix(target, method) { return true } } return false }
メインは上で、HTTPレスポンスを返してくれるダミーサーバを以下で用意。
package main import ( "io" "log" "net/http" "os" ) var ( appLog = log.New(os.Stderr, "", 0) ) func main() { if err := run(); err != nil { panic(err) } } func run() error { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "helloworld\n") }) return http.ListenAndServe("localhost:12345", nil) }
Taskfile.yml
タスク定義は以下のような感じ。
applayer-http: desc: See HTTP info dir: cmd/applayer/http cmds: - go build -o webserver server/main.go - go build -o http main.go - ./webserver & - sudo ./http & - sleep 1 - curl --silent http://localhost:12345/ > /dev/null - sleep 3 - pkill webserver - sudo pkill http
以下、Gitpod上で実行してみた結果です。
gitpod /workspace/go-gopacket-example (main) $ task applayer-http task: [applayer-http] go build -o webserver server/main.go task: [applayer-http] go build -o http main.go task: [applayer-http] ./webserver & task: [applayer-http] sudo ./http & task: [applayer-http] sleep 1 START task: [applayer-http] curl --silent http://localhost:12345/ > /dev/null task: [applayer-http] sleep 3 [Src Port ] 45726 [Dst Port ] 12345(italk) [Seq Number ] 43762840 [Ack Number ] 0 [Window Size ] 43690 [TCP Flags - SYN] true [TCP Flags - ACK] false [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] false [Checksum ] 65072 [Urgent Pointer ] 0 ------------------------------------ [Src Port ] 12345(italk) [Dst Port ] 45726 [Seq Number ] 2956243871 [Ack Number ] 43762841 [Window Size ] 43690 [TCP Flags - SYN] true [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] false [Checksum ] 65072 [Urgent Pointer ] 0 ------------------------------------ [Src Port ] 45726 [Dst Port ] 12345(italk) [Seq Number ] 43762841 [Ack Number ] 2956243872 [Window Size ] 342 [TCP Flags - SYN] false [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] false [Checksum ] 65064 [Urgent Pointer ] 0 ------------------------------------ HTTP Method: GET HTTP URL: / HTTP Protocol: HTTP/1.1 HTTP Headers: User-Agent: curl/7.81.0 Accept: */* ------------------------------------ [Src Port ] 12345(italk) [Dst Port ] 45726 [Seq Number ] 2956243872 [Ack Number ] 43762920 [Window Size ] 341 [TCP Flags - SYN] false [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] false [Checksum ] 65064 [Urgent Pointer ] 0 ------------------------------------ HTTP Status Code: 200 HTTP Protocol: HTTP/1.1 HTTP Headers: Date: Mon, 29 May 2023 06:28:41 GMT Content-Length: 11 Content-Type: text/plain; charset=utf-8 BODY: helloworld ------------------------------------ [Src Port ] 45726 [Dst Port ] 12345(italk) [Seq Number ] 43762920 [Ack Number ] 2956244000 [Window Size ] 341 [TCP Flags - SYN] false [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] false [Checksum ] 65064 [Urgent Pointer ] 0 ------------------------------------ [Src Port ] 45726 [Dst Port ] 12345(italk) [Seq Number ] 43762920 [Ack Number ] 2956244000 [Window Size ] 342 [TCP Flags - SYN] false [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] true [Checksum ] 65064 [Urgent Pointer ] 0 ------------------------------------ [Src Port ] 12345(italk) [Dst Port ] 45726 [Seq Number ] 2956244000 [Ack Number ] 43762921 [Window Size ] 342 [TCP Flags - SYN] false [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] true [Checksum ] 65064 [Urgent Pointer ] 0 ------------------------------------ [Src Port ] 45726 [Dst Port ] 12345(italk) [Seq Number ] 43762921 [Ack Number ] 2956244001 [Window Size ] 342 [TCP Flags - SYN] false [TCP Flags - ACK] true [TCP Flags - PSH] false [TCP Flags - RST] false [TCP Flags - FIN] false [Checksum ] 65064 [Urgent Pointer ] 0 ------------------------------------ task: [applayer-http] pkill webserver task: [applayer-http] sudo pkill http
みると、3way-handshakeして、それからHTTPリクエストが流れて、その後HTTPレスポンスが流れ、最後にお互いがFIN/ACKして終了ってなってますね。
リポジトリ
上のサンプルなどは、以下のリポジトリでアップしています。ご参考までに。
参考情報
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。