理由
Goで書かれたネットワークプログラムをIPv6に対応させるためには、サーバサイドはあまり意識しなくてもいいが、アドレス処理を文字列で処理する事がある場合、そのアドレス文字列(ログやサーバの環境変数で見えるクライアントアドレス)がどちらのアドレスファミリーなのかチェックをし、それぞれの処理をする必要がある
その場合のコード例をメモしておく
参考コード
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"}
// スライスを1つづつチェック
for i, v := range addrs {
// afCheckの返答を確認
fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
}
}
解説
ip.To4(),ip.To16()がどのような動きをするか詳細を見てみると
// チェック対象のアドレスをスライスに入れる
addrs := []string{"2001:db8::1", "192.168.0.1", "ffff::", "::1", "12345", "192.168.1", "192.168.1.256", "::192.0.2.1"}
// スライスを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()」に値が入る。
アンチパターン
やってしまいがちなアンチパターンは文字列判断してしまうこと。ここでは単純な判断にしているが、「:」があって「.」がないとか、「:」の数を数えたり、正規表現判断することも同じアンチパターンと考える。
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"}
// スライスを1つづつチェック
for i, v := range addrs {
// afCheckの返答を確認
fmt.Printf("%d: %s = %s\n", i, v, afCheck(v))
}
}
まとめ
アドレスファミリーを文字列で判断するのはNG