理由
Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、IPv6アドレスは表記方法が複数あり、このIPv6アドレス処理を文字列で処理しようとする場合正規化する必要がある。その場合のコード例をメモしておく
例)
- 2001:db8::1 は 2001:0db8:0000:0000:0000:0000:0000:0001 ともかける→文字列比較すると違うと判断されてしまう
参考) RFC5952-IPv6アドレスの推奨表記
参考コード
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"}
// スライスを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
アンチパターン
やってしまいがちなアンチパターンは文字列処理してしまうこと。正規表現判断することも同じアンチパターンと考える。
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"}
// スライスを1つづつチェック
for i, v := range addrs {
// v6Formatの返答を確認
fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
}
}
FULL形式に全部してしまえ
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"}
// スライスを1つづつチェック
for i, v := range addrs {
// v6Formatの返答を確認
fmt.Printf("%d: %s = %s\n", i, v, v6Format(v))
}
}
まとめ
IPv6アドレスを正規化せずに判断するのはNG。正規化を文字列変換するのもNG