Go言語でsFlowのFlowSampleをデコードする方法

# はじめに

前回の記事

https://qiita.com/twsnmp/items/17936394543d29bee00e

の続きです。
前回の記事では、受信したFlowSampleを

```go
        case *sflow.FlowSample:
				log.Printf("FlowSample from %s", raIP)
				for _, record := range s.Records {
					log.Printf("%+v", record)
				}
```

のような処理で表示していたので

```teminal
2024/09/29 17:10:18 RawPacketFlow: {Protocol:1 FrameLength:146 Stripped:4 HeaderSize:128 Header:[80 0 0 4 0 0 0 37 54 171 119 83 8 0 69 0 3 29 103 109 0 0 57 17 143 109 198 41 0 4 192 168 1 32 0 53 136 185 3 9 83 11 156 87 128 0 0 1 0 0 0 8 0 13 3 111 114 103 0 0 1 0 1 192 12 0 2 0 1 0 2 163 0 0 25 2 97 50 3 111 114 103 11 97 102 105 108 105 97 115 45 110 115 116 4 105 110 102 111 0 192 12 0 2 0 1 0 2 163 0 0 21 2 98 50 3 111 114 103 11 97 102 105 108 105 97 115 45]}

```

のようにHeader部分が何のパケットなのかわかりません。
今回は、このHeaderをデコードして人の理解できる表示にする方法を紹介します。


# gopacketパッケージ

Go言語でネットワークパケットを扱うパッケージが、gopacketです。

https://github.com/google/gopacket

パケットのキャプチャーやパケットの生成の方法を紹介した記事がいくつかあります。

https://qiita.com/ksw/items/0849d9c02d26266b03b3

今回は、このパッケージをパケットのデコードに使います。

# サンプルパケットデコード付きのsFlow受信プログラム

前回のソースコードを修正してパケットのデコード処理を追加したものが

```go
package main

import (
	"bytes"
	"log"
	"net"

	"github.com/Cistern/sflow"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
)

func main() {
	sv, err := net.ListenPacket("udp", ":6343")
	if err != nil {
		log.Fatalln(err)
	}
	defer sv.Close()
	data := make([]byte, 8192)
	for {
		l, ra, err := sv.ReadFrom(data)
		if err != nil {
			log.Println(err)
			continue
		}
		r := bytes.NewReader(data[:l])
		d := sflow.NewDecoder(r)
		dg, err := d.Decode()
		if err != nil {
			log.Println(err)
			continue
		}
		raIP := ""
		switch a := ra.(type) {
		case *net.UDPAddr:
			raIP = a.IP.String()
		case *net.TCPAddr:
			raIP = a.IP.String()
		}
		for _, sample := range dg.Samples {
			switch s := sample.(type) {
			case *sflow.CounterSample:
				log.Printf("CounterSample from %s:", raIP)
				for _, record := range s.Records {
					log.Printf("%+v", record)
				}
			case *sflow.FlowSample:
				log.Printf("FlowSample from %s", raIP)
				for _, record := range s.Records {
					//					log.Printf("%+v", record)
					switch fsr := record.(type) {
					case sflow.RawPacketFlow:
						rawPacketFlow(&fsr)
					}
				}

			case *sflow.EventDiscardedPacket:
				log.Printf("EventDiscardedPacket from %s", raIP)
				for _, record := range s.Records {
					log.Printf("%+v", record)
				}
			default:
				log.Printf("%v form %s", s, raIP)
			}
		}
	}
}

func rawPacketFlow(r *sflow.RawPacketFlow) {
	packet := gopacket.NewPacket(r.Header, layers.LayerTypeEthernet, gopacket.Default)
	var record = make(map[string]interface{})
	ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
	if ethernetLayer != nil {
		if eth, ok := ethernetLayer.(*layers.Ethernet); ok {
			record["sourceMacAddress"] = eth.SrcMAC.String()
			record["destinationMacAddress"] = eth.SrcMAC.String()
		}
	}
	src := ""
	dst := ""
	bytes := 0
	ipv4Layer := packet.Layer(layers.LayerTypeIPv4)
	if ipv4Layer != nil {
		ip, ok := ipv4Layer.(*layers.IPv4)
		if !ok {
			return
		}
		src = ip.SrcIP.String()
		dst = ip.DstIP.String()
		bytes = int(ip.Length)
	} else {
		ipv6Layer := packet.Layer(layers.LayerTypeIPv6)
		if ipv6Layer != nil {
			ipv6, ok := ipv6Layer.(*layers.IPv6)
			if !ok {
				return
			}
			src = ipv6.SrcIP.String()
			dst = ipv6.DstIP.String()
			bytes = int(ipv6.Length)
		}
	}
	sp := 0
	dp := 0
	prot := 0
	flag := uint8(0)
	// UDP
	udpLayer := packet.Layer(layers.LayerTypeUDP)
	if udpLayer != nil {
		udp, ok := udpLayer.(*layers.UDP)
		if !ok {
			return
		}
		sp = int(udp.SrcPort)
		dp = int(udp.DstPort)
		prot = 17
	} else {
		// TCP
		tcpLayer := packet.Layer(layers.LayerTypeTCP)
		if tcpLayer != nil {
			tcp, ok := tcpLayer.(*layers.TCP)
			if !ok {
				return
			}
			sp = int(tcp.SrcPort)
			dp = int(tcp.DstPort)
			if tcp.FIN {
				flag |= 0x01
			}
			if tcp.SYN {
				flag |= 0x02
			}
			if tcp.RST {
				flag |= 0x04
			}
			if tcp.PSH {
				flag |= 0x08
			}
			if tcp.ACK {
				flag |= 0x10
			}
			if tcp.URG {
				flag |= 0x20
			}
			if tcp.ECE {
				flag |= 0x40
			}
			if tcp.CWR {
				flag |= 0x80
			}
			prot = 6
		} else {
			icmpV4Layer := packet.Layer(layers.LayerTypeICMPv4)
			if icmpV4Layer != nil {
				icmp, ok := icmpV4Layer.(*layers.ICMPv4)
				if !ok {
					return
				}
				prot = 1
				dp = int(icmp.TypeCode)
			} else {
				icmpV6Layer := packet.Layer(layers.LayerTypeICMPv6)
				if icmpV6Layer == nil {
					return
				}
				icmp, ok := icmpV6Layer.(*layers.ICMPv6)
				if !ok {
					return
				}
				prot = 58
				dp = int(icmp.TypeCode)
			}
		}
	}
	record["srcIP"] = src
	record["srcPort"] = float64(sp)
	record["dstIP"] = dst
	record["dstPort"] = float64(dp)
	record["bytes"] = float64(bytes)
	record["tcpflags"] = float64(flag)
	record["protocol"] = float64(prot)

	log.Printf("rawPacketFlow : %+v", record)
}

```

変更したのは
```diff_go

			case *sflow.FlowSample:
				log.Printf("FlowSample from %s", raIP)
				for _, record := range s.Records {
-					log.Printf("%+v", record)
+					switch fsr := record.(type) {
+					case sflow.RawPacketFlow:
+						rawPacketFlow(&fsr)
+					}
				}
```
の部分とrawPacketFlow（）関数の追加です。

rawPacketFlow（）関数のポイントは、gopacketを使ってパケットのデコードをする部分です。

### sFlowのサンプルのHead部分をデコード

```go
	packet := gopacket.NewPacket(r.Header, layers.LayerTypeEthernet, gopacket.Default)

```

### 情報を取得したいレイアーのデコード

```go
    // Ethernet
	ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
    // IPv4
    ipv4Layer := packet.Layer(layers.LayerTypeIPv4)
	// IPv6
    ipv6Layer := packet.Layer(layers.LayerTypeIPv6)

     // UDP
	udpLayer := packet.Layer(layers.LayerTypeUDP)
	
     // TCP
	tcpLayer := packet.Layer(layers.LayerTypeTCP)

```

# 実行結果

```terminal
2024/09/30 16:08:17 FlowSample from 192.168.1.32
2024/09/30 16:08:17 rawPacketFlow : map[bytes:36 destinationMacAddress:00:25:36:ab:77:53 dstIP:ff02::1 dstPort:33280 protocol:58 sourceMacAddress:00:25:36:ab:77:53 srcIP:fe80::225:36ff:feab:7753 srcPort:0 tcpflags:0]
2024/09/30 16:08:19 FlowSample from 192.168.1.32
2024/09/30 16:08:19 rawPacketFlow : map[bytes:88 destinationMacAddress:50:00:00:04:00:00 dstIP:192.168.1.250 dstPort:2055 protocol:17 sourceMacAddress:50:00:00:04:00:00 srcIP:10.30.1.12 srcPort:3285 tcpflags:0]
```

送信元、宛先のMACアドレスやIPアドレス、ポート番号、プロトコルなどをデコードできています。

# 余談

gopacketは、

https://github.com/twsnmp/twpcap

の開発で利用しました。

使い方は

https://note.com/twsnmp/n/nba97c75b0efc

にも書いています。

TWSNMP FCに同梱しています。