【GoネットワークプログラムでIPv6対応させる】ログ等のアドレス文字列からアドレスファミリー判別をする方法


# 理由
Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、アドレス処理を文字列で処理する事がある場合、そのアドレス文字列(ログやサーバの環境変数で見えるクライアントアドレス)がどちらのアドレスファミリーなのかチェックをし、それぞれの処理をする必要がある
その場合のコード例をメモしておく

# 参考コード
 - https://play.golang.org/p/mKUGd7lUNBP

```golang:main.go
package main

import (
	"fmt"
	"net"
)

// アドレスファミリーチェック関数
func afCheck(addr string) string {
	// 文字列をnet.IPにParse
	ip := net.ParseIP(addr)

	// Parseできたら
	if ip != nil {
		if len(ip.To4()) == net.IPv4len {
			// アドレスの長さをチェックし、4byte=32bit -> IPv4
			return "ipv4"ß
		} else if len(ip.To16()) == net.IPv6len {
			// アドレスの長さをチェックし、16byte=128bit -> IPv6
			return "ipv6"
		} else {
			// 未知の場合
			return "unknown"
		}
	} else {
		// Parseできない場合
		return "invalid"
	}
}

// メイン関数
func main() {
	// チェック対象のアドレスをスライスに入れる
	addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256","::192.0.2.1"}

	// スライスを１つづつチェック
	for i, v := range addrs {
		// afCheckの返答を確認
		fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
	}
}

```

## 解説
ip.To4(),ip.To16()がどのような動きをするか詳細を見てみると

```golang
	// チェック対象のアドレスをスライスに入れる
	addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}

	// スライスを１つづつチェック
	for i, v := range addrs {
		ip := net.ParseIP(v)
		fmt.Printf("%d: %s %#v(%d) %#v(%d)\n", i, v, ip.To4(), len(ip.To4()), ip.To16(), len(ip.To16()))
	}
```

上記結果を整形すると

|i|v|ip.To4()|len(ip.To4())|ip.To16()|len(ip.To16()|
|:--|:--|:--|:--|:--|:--|
|0|2001:db8::1|net.IP(nil)|0|net.IP{0x20, 0x1, 0xd, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}|16|
|1|192.168.0.1|net.IP{0xc0, 0xa8, 0x0, 0x1}|4|net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xc0, 0xa8, 0x0, 0x1}|16|
|2|ffff::|net.IP(nil)|0|net.IP{0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}|16|
|3|::1|net.IP(nil)|0|net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}|16|
|4|12345|net.IP(nil)|0|net.IP(nil)|0|
|5|192.168.1|net.IP(nil)|0|net.IP(nil)|0|
|6|192.168.1.256|net.IP(nil)|0|net.IP(nil)|0|
|7|::192.0.2.1|net.IP(nil)|0|net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x0, 0x2, 0x1}|16|

これにより、有効なIPアドレスの場合、Parseが成功し

- IPv4の場合「ip.To4()」に値が入る。（この場合、「ip.To16()」にも値がある。これはIPv4射影アドレス(IPv4-mapped IPv6 address)となっている点注意が必要）
- IPv6の場合「ip.To4()」に値が入らず、「ip.To16()」に値が入る。

# アンチパターン
やってしまいがちなアンチパターンは文字列判断してしまうこと。ここでは単純な判断にしているが、「：」があって「.」がないとか、「：」の数を数えたり、正規表現判断することも同じアンチパターンと考える。
 - https://play.golang.org/p/8mNeoxGyxLb

```golang:main.go
package main

import (
	"fmt"
	"strings"
)

// アドレスファミリーチェック関数
func afCheck(addr string) string {
	if strings.Contains(addr, ".") {
		// アドレスの文字列から「.」を含んでいたらIPv4と判断
		return "ipv4"
	} else if strings.Contains(addr, ":") {
		// アドレスの文字列から「:」を含んでいたらIPv6と判断
		return "ipv6"
	} else {
		// 未知の場合
		return "unknown"
	}
}

// メイン関数
func main() {
	// チェック対象のアドレスをスライスに入れる
	addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}

	// スライスを１つづつチェック
	for i, v := range addrs {
		// afCheckの返答を確認
		fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
	}
}

```

# まとめ
アドレスファミリーを文字列で判断するのはNG