【GoネットワークプログラムでIPv6対応させる】アドレス正規化をする方法


# 理由
Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、IPv6アドレスは表記方法が複数あり、このIPv6アドレス処理を文字列で処理しようとする場合正規化する必要がある。その場合のコード例をメモしておく

例）
 - 2001:db8::1 は　2001:0db8:0000:0000:0000:0000:0000:0001　ともかける→文字列比較すると違うと判断されてしまう
参考） [RFC5952-IPv6アドレスの推奨表記](https://www.nic.ad.jp/ja/newsletter/No46/0800.html)

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

```golang:main.go
package main

import (
	"fmt"
	"net"
)

func v6Format(addr string) string {
	ip := net.ParseIP(addr)
	// Parseできたら
	if ip != nil {
		return ip.String()
	} else {
		return "error"
	}
}

func main() {
	// チェック対象のアドレスをスライスに入れる
	addrs := []string{"2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

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

```

## 解説
v6Formatがどのような動きをするか詳細を見てみると、文字列をParseIPして、Stringで文字列化しているだけ。
これだけで正規化できます

- input

```
2001:db8:0:0:1:0:0:1
2001:0db8:0:0:1:0:0:1
2001:db8::1:0:0:1
2001:db8::0:1:0:0:1
2001:0db8::1:0:0:1
2001:db8:0:0:1::1
2001:db8:0000:0:1::1
2001:DB8:0:0:1::1
2001:db8:aaaa:bbbb:cccc:dddd::1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8:0:0:0::1
2001:db8:0:0::1
2001:db8:0::1
2001:db8::1
2001:db8::aaaa:0:0:1
2001:db8:0:0:aaaa::1
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA
2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa
```

- output

```
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8::1:0:0:1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8:aaaa:bbbb:cccc:dddd:0:1
2001:db8::1
2001:db8::1
2001:db8::1
2001:db8::1
2001:db8::aaaa:0:0:1
2001:db8::aaaa:0:0:1
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa
```

# アンチパターン
やってしまいがちなアンチパターンは文字列処理してしまうこと。正規表現判断することも同じアンチパターンと考える。

```golang:main.go
package main

import (
	"fmt"
	"strings"
)

func v6Format(addr string) string {
	addr = strings.Replace(addr, ":0000", ":", -1)
	addr = strings.Replace(addr, ":::", ":", -1)
	addr = strings.Replace(addr, ":0", ":", -1)
	return strings.ToLower(addr)
}

func main() {
	// チェック対象のアドレスをスライスに入れる
	addrs := []string{"2001:0db8:0000:0000:0000:0000:0000:0001", "2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

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

FULL形式に全部してしまえ

```golang
package main

import (
	"fmt"
	"net"
)

func v6Format(addr string) string {
	ip := net.ParseIP(addr)
	full := ""
	for i, j := range ip {
		if i > 0 && i%2 == 0 {
			full = full + ":"
		}
		full = full + fmt.Sprintf("%02x", j)
	}
	return full
}

func main() {
	// チェック対象のアドレスをスライスに入れる
	addrs := []string{"2001:0db8:0000:0000:0000:0000:0000:0001", "2001:db8::1", "192.168.0.1", "2001:db8:0:0:1:0:0:1", "::1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8::aaaa:0:0:1", "192.168.1.256", "::192.0.2.1"}

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

# まとめ
IPv6アドレスを正規化せずに判断するのはNG。正規化を文字列変換するのもNG