【Go言語】ChatGPTに聞きながらローカル環境で利用可能なIPアドレスとMacアドレスを取得して一覧表示してみる

開発で利用しているUbuntu環境のIPアドレスとMacアドレスを取得したいと思い、Go言語は書いたことがないので、ChatGPTに聞きながら実装してみた記録です。

---

ということでChatGPTにこんな質問をしてみました。

> Go言語でローカルのネットワークインターフェースの情報を取得したい。
> 取得したい情報は、外部ネットワークと通信可能なIPアドレスとMacアドレスです。
> 有線LANが利用可能だがLANケーブルが接続されていない場合や、無線WiFiが利用可能だが無効化されている場合には取得対象とはしない。
> なお、有線LANや無線WiFiのネットワークインターフェースが複数存在する場合にも対応したい。

---

以下の回答が得られました。

> Go言語で指定された条件に合うネットワークインターフェースの情報を取得するには、標準ライブラリの `net` パッケージを使用して実装できます。以下のコードは、外部ネットワークに接続可能なインターフェースのIPアドレスとMACアドレスを取得する方法を示しています。

#### 実装例

```
package main

import (
	"fmt"
	"net"
)

func main() {
	interfaces, err := net.Interfaces()
	if err != nil {
		fmt.Println("Error fetching interfaces:", err)
		return
	}

	for _, iface := range interfaces {
		// インターフェースが無効か、ループバックである場合はスキップ
		if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
			continue
		}

		// IPアドレス情報を取得
		addrs, err := iface.Addrs()
		if err != nil {
			fmt.Printf("Error fetching addresses for interface %s: %v\n", iface.Name, err)
			continue
		}

		for _, addr := range addrs {
			var ip net.IP

			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}

			// IPv4のみを対象
			if ip == nil || ip.IsLoopback() || !ip.IsGlobalUnicast() || ip.To4() == nil {
				continue
			}

			fmt.Printf("Interface: %s\n", iface.Name)
			fmt.Printf("  IP Address: %s\n", ip.String())
			fmt.Printf("  MAC Address: %s\n", iface.HardwareAddr.String())
		}
	}
}
```

---

提示されたコードを実行してみると、なんかたくさん表示されました。
利用しているUbuntu環境では有線LANと無線WiFiが利用可能な状態ですが、LANケーブルを接続していない有線LANは表示されておらず、無線WiFiの情報が表示されているところまでは意図通りな動作ですが、関係なさそうなインターフェースが大量に表示されています。

```
$ go run main.go 
Interface: wlp44s0
  IP Address: 192.168.XXX.XXX
  MAC Address: 4c:d5:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.21.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.23.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.27.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.26.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: docker0
  IP Address: 172.17.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.29.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.22.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.28.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.20.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.24.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.18.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.19.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 192.168.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
Interface: br-XXXXXXXXXXXX
  IP Address: 172.25.XXX.XXX
  MAC Address: 02:42:XX:XX:XX:XX
```

---

関係なさそうなインターフェースは、恐らくDockerで利用しているものでは？と予想できたので、こんな風に返信しました。

> Dockerで利用しているネットワークインターフェースは除外したいです。

---

以下の回答が得られました。

> Dockerのネットワークインターフェースを除外するためには、 `net.Interface` 構造体の `Name` フィールドを使用してインターフェース名に基づくフィルタリングを追加するのが簡単です。通常、Dockerで使用されるインターフェース名は `docker0` や `br-*` （ブリッジネットワーク）などのパターンです。
> 
> 以下に、Dockerのネットワークインターフェースを除外するコードを示します。

#### 改良版コード

```
package main

import (
	"fmt"
	"net"
	"strings"
)

func main() {
	interfaces, err := net.Interfaces()
	if err != nil {
		fmt.Println("Error fetching interfaces:", err)
		return
	}

	for _, iface := range interfaces {
		// インターフェースが無効か、ループバック、またはDocker関連の場合はスキップ
		if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 || isDockerInterface(iface.Name) {
			continue
		}

		// IPアドレス情報を取得
		addrs, err := iface.Addrs()
		if err != nil {
			fmt.Printf("Error fetching addresses for interface %s: %v\n", iface.Name, err)
			continue
		}

		for _, addr := range addrs {
			var ip net.IP

			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}

			// IPv4のみを対象
			if ip == nil || ip.IsLoopback() || !ip.IsGlobalUnicast() || ip.To4() == nil {
				continue
			}

			fmt.Printf("Interface: %s\n", iface.Name)
			fmt.Printf("  IP Address: %s\n", ip.String())
			fmt.Printf("  MAC Address: %s\n", iface.HardwareAddr.String())
		}
	}
}

// Docker関連のインターフェース名を判定するヘルパー関数
func isDockerInterface(name string) bool {
	// Dockerで使われる典型的なインターフェース名
	dockerPrefixes := []string{"docker", "br-", "veth", "tunl", "flannel"}

	for _, prefix := range dockerPrefixes {
		if strings.HasPrefix(name, prefix) {
			return true
		}
	}
	return false
}
```

---

実行してみると、意図通り無線WiFiのネットワークインターフェースのみが表示されるようになりました。

```
$ go run main.go 
Interface: wlp44s0
  IP Address: 192.168.XXX.XXX
  MAC Address: 4c:d5:XX:XX:XX:XX
```