Go 1.18 で追加された net/netip パッケージ
- Authors
- Name
- ごとれん
- X
- @ren510dev
目次
- 目次
- はじめに
- 特徴
- netip パッケージの登場背景
- 比較処理の非効率性
- メモリアロケーションの発生
- UDP パフォーマンスの低下
- netaddr.IP 型の提案
- 命名規則の議論
- netip.Addr 型の構造
- addr uint128
- z *intern.Value
- intern.Value 型
- なぜ構造体管理なのか
- 省メモリ
- メモリアロケーションが発生しない
- イミュータブル
- == 演算子で比較可能
- map の key に使用可能
- net.IP 型との互換性
- 依存関係
- UDP 関連のメソッド
- 相互型変換
- netip.Addr 型の使い方
- netip.Addr 型の表示
- netip.Addr 型の生成
- netip.Addr 型の変換
- アドレスの比較
- アドレス種別の判別
- IP アドレスの操作
- Addr の Marshal と Unmarshal
- netip.AddrPort の構造
- netip.AddrPort の使い方
- AddrPort 型の生成
- アドレスポートの表示と判定
- アドレスポートの比較
- AddrPort の Marshal と Unmarshal
- netip.Prefix の構造
- netip.Prefix の使い方
- Prefix 型の生成
- 操作
- プレフィックスの比較
- プレフィックスベースの表示
- Prefix の Marshal と Unmarshal
- まとめ
- 参考・引用

はじめに
Go 1.18 は "our biggest change ever to the language" と言われるほど、Go 言語誕生以来、最大の仕様拡張が行われました。

特に注目を浴びたのは Generics でしょうか。 以前の Go は型パラメータを持たず、汎用的なデータ構造や関数を実装する際は、interface{}
や型アサーションを使用するため、冗長な書き方が必要でした。
// 以前の書き方
func SumInts(m map[string]int) int {
sum := 0
for _, v := range m {
sum += v
}
return sum
}
func SumFloats(m map[string]float64) float64 {
sum := 0.0
for _, v := range m {
sum += v
}
return sum
}
これに対し、Go Generics では、型パラメータを活用して汎用化することができるため、型安全性や再利用性が大幅に向上し、コードを簡素化することができます。
// Generics を使用した書き方
func SumNumbers[T int | float64](m map[string]T) T {
var sum T
for _, v := range m {
sum += v
}
return sum
}
ここで、T int | float64
という型セットを定義すると、型パラメータ T
は、int
と float64
の両方の型を振る舞うことができます。
そして、もう一つ Go 1.18 では Go のパフォーマンスを向上させる重要なパッケージが追加されました。
それは netip パッケージ の登場です。 netip パッケージの追加は Go でネットワークプロトコルを書いている身として非常に画期的だと思いました。
これまで、Go を用いて IP アドレスやパケット処理を実装する際は、主に net パッケージを使用していましたが、言語的な特性により、処理やパフォーマンスに関していくつかの課題がありました。
netip パッケージは、net パッケージの課題を解決し、より使いやすく、より IP 操作のパフォーマンスを向上させるための機構が追加されています。
今回のブログでは、netip の導入背景を踏まえ、パッケージの特徴、互換性、メソッドの使い方等、従来の net パッケージと何が変わったのかを紹介したいと思います。
特徴

Compared to the net.IP type, Addr type takes less memory, is immutable, and is comparable (supports == and being a map key).
netip パッケージに追加されている Addr
型には、従来の net パッケージの IP
型に対して次のような特徴があります。
- 省メモリ
- メモリアロケーションが発生しない
- イミュータブル
==
演算子で比較可能- map の key に使用可能
netip パッケージの登場背景
netip パッケージの追加は、元 Go チーム、現在 Tailscale, Inc. の bradfitz 氏 からの提案で始まりました。

この Issue は実際に bradfitz 氏 が提案した、net パッケージにおける IP アドレスの表現方式を改善するために、inet.af/netaddr パッケージ を標準ライブラリに取り込み、netaddr.IP
型を導入するというものでした。
ここでは net.IP
の課題と対応策について、次のように言及されています。
比較処理の非効率性
net.IP
は可変長のバイトスライス([]byte
)で表現されているため、Go の組み込みの比較演算子(==
)を直接使用できません。 そのため、IP アドレスを比較するたびに手動でバイト列の要素を 1 つずつチェックする必要があります。
例えば、通常の整数や構造体であれば ==
を使って直接比較できるところを、net.IP
では bytes.Equal
を使って比較しなければなりません。
ip1 := net.ParseIP("192.168.1.1")
ip2 := net.ParseIP("192.168.1.1")
// 直接の比較はコンパイルエラーになる
// if ip1 == ip2 { ... }
// bytes.Equal で比較する
if bytes.Equal(ip1, ip2) {
fmt.Println("IP addresses are equal")
}
bytes.Equal
はスライスのすべてのバイトを走査するため、比較操作が遅くなります。
また、==
を使えないため、可読性が悪くなりバグを引き起こしやすいという実装上の課題も生じます。
メモリアロケーションの発生
net.IP
はスライスなので、値をコピーする際にメモリアロケーションが発生することで不要なメモリリソースを喰う可能性があります。
例えば、IP アドレスを map の key として使用する場合、スライスのポインタを格納するため、不要なメモリアロケーションが発生しやすくなります。
スライスは直接、map の key として扱うことができないため IP
型を string
型にキャストしてから格納します。
ipMap := make(map[string]string)
ip := net.ParseIP("192.168.1.1")
ipMap[ip] = "IPv4" // NG: IP は map の key にできない
ipMap[ip.String()] = "IPv4" // OK: []byte を string に変換する
ここで ip.String()
を呼び出すと、内部的には新たに string
型 16byte 分を確保するため、追加のメモリアロケーションが発生します。
UDP パフォーマンスの低下
比較処理の非効率性や不要なメモリアローケーションにより、特に ベストエフォート指向でコネクションレスに大量のパケットを処理する UDP ではパフォーマンス低下が顕著 になります。
Tailscale は Wireguard という OSS がベースになっています。 Wireguard は Go で実装された次世代の VPN ソリューションで、トンネリング の構築に UDP を使用します。

また、HTTP/3 の普及が進む中、Google が開発した QUIC:Quick UDP Internet Connections も Go で実装されており、UDP ベースで動作します。

今回、特に netip パッケージに惹かれたのはこの部分で、僕が研究開発に携わっている CYPHONIC:CYber PHysical Overlay Network over Internet Communication という Peer-to-Peer に基づくゼロトラストセキュリティネットワークを構築するためのオーバーレイネットワークプロトコルも Go で実装しており、UDP ベースとなっています。

netaddr.IP
型の提案
netaddr.IP
型は固定長の構造体(struct
)を用いることで、上記の問題を解決するというものです。
inet.af/netaddr は、現 net/netip の前身にあたり、元々 Tailscale で導入されていた IP 操作に特化したパッケージです。
netaddr.IP
型は、構造体を用いることで、==
演算子による比較ができます。
type IP struct {
addr uint128
z *intern.Value
}
また、固定長な構造体のコピーはスタックを使用するため、ヒープ領域を使用するスライスと比較してメモリリソースの削減や Go コンパイラの最適化が可能です。
ipMap := make(map[netaddr.IP]string)
ip := netaddr.MustParseAddr("192.168.1.1")
ipMap[ip] = "IPv4" // メモリアロケーションが発生しない
命名規則の議論
当初の提案では、netaddr.IP
という名称が検討されました。 これは、Go の net パッケージにある net.IP
に対応する形で、新たな IP
型として導入しようという試みです。
しかし、netaddr というパッケージ名は net パッケージと独立しているものの、addr.IP
という命名には違和感があるとの意見が出ました。
その後、シンプルな ip.Addr
という名称が提案されましたが、今度は ip をパッケージ名にしてしまうと、変数名として一般的に使用される ip
と衝突する可能性があるという問題が指摘されました。
その結果、最終的に netip.Addr
という名称が採用されました。 この命名により、net パッケージとは明確に分離されつつも、IP アドレス型の表現として適切であると判断されました。
また、net パッケージ自体が既に大きく複雑になっているため、新たな IP 型を独立した netip パッケージとして切り出すことは合理的だとされました。
実際に利用する際は、net/netip
をインポートします。
import "net/netip"
ちなみに前進の netaddr パッケージは非推奨となり、2023 年 5 月にはリポジトリもアーカイブされていました。

netip.Addr
型の構造
type Addr struct {
addr uint128
z *intern.Value
}
netip.Addr
型は構造体で、IPv4 アドレス、IPv6 アドレスとゾーンインデックス を表現します。 addr
は IPv4 アドレスまたは IPv6 アドレスを管理し、z
フィールドはゾーン(後述)を管理します。
net パッケージでは net.IP
型とゾーンを持つ net.IPAddr
型をそれぞれ定義しているのに対して、netip.Addr
型は構造体で両方を管理しています。
addr uint128
type Addr struct {
// addr is the hi and lo bits of an IPv6 address. If z==z4,
// hi and lo contain the IPv4-mapped IPv6 address.
//
// hi and lo are constructed by interpreting a 16-byte IPv6
// address as a big-endian 128-bit number. The most significant
// bits of that number go into hi, the rest into lo.
//
// For example, 0011:2233:4455:6677:8899:aabb:ccdd:eeff is stored as:
// addr.hi = 0x0011223344556677
// addr.lo = 0x8899aabbccddeeff
//
// We store IPs like this, rather than as [16]byte, because it
// turns most operations on IPs into arithmetic and bit-twiddling
// operations on 64-bit registers, which is much faster than
// bytewise processing.
addr uint128
// 略
z *intern.Value
}
// uint128 represents a uint128 using two uint64s.
//
// When the methods below mention a bit number, bit 0 is the most
// significant bit (in hi) and bit 127 is the lowest (lo&1).
type uint128 struct {
hi uint64
lo uint64
}
uint128
は、netip パッケージに定義されている 128bit(16byte)を表現するカスタム型です。
IPv6 アドレスは 128bit(16byte)で構成されており、z
フィールドが v4(IPv4)の場合には ::ffff:192.168.1.1
のような IPv4-Mapped IPv6 Address の形式で保持されます。
- IPv4-Mapped IPv6 Address
| 80 bits | 16 | 32 bits |
+--------------------------------------+--------------------------+
|0000..............................0000|FFFF| IPv4 address |
+--------------------------------------+----+---------------------+
ここで、IPv6 アドレスが 0011:2233:4455:6677:8899:aabb:ccdd:eeff
の場合、addr.hi = 0x0011223344556677
/ addr.lo = 0x8899aabbccddeeff
とそれぞれ保存されます。
IPv6 は 128bit(16 オクテット)で表現されるため [16]byte
のバイト配列を使用すれば良いのではと思うかもしれませんが、uint64
2 つで表現すると、64bit CPU では 2 つのレジスタで IPv6 を表現できます。 そのため、メモリアクセスやビット演算のオーバーヘッドが少なくなり、IP アドレスに対する算術演算を高速化できます。

- 比較が簡単
func (a IPv6Addr) Equals(b IPv6Addr) bool {
return a.high == b.high && a.low == b.low
}
- ビット演算(マスク処理)の簡略化
func (ip IPv6Addr) Mask(mask IPv6Addr) IPv6Addr {
return IPv6Addr{
high: ip.high & mask.high,
low: ip.low & mask.low,
}
}
これをバイト配列で表現した場合、16 回の for ループで AND 演算を取る必要があります。
func Mask(a, b [16]byte) [16]byte {
var result [16]byte
for i := 0; i < 16; i++ {
result[i] = a[i] & b[i]
}
return result
}
ベンチマーク結果:#63: Change the representation of IP to a pair of uint64s
z *intern.Value
type Addr struct {
addr uint128
// 略
// z is a combination of the address family and the IPv6 zone.
//
// nil means invalid IP address (for a zero Addr).
// z4 means an IPv4 address.
// z6noz means an IPv6 address without a zone.
//
// Otherwise it's the interned zone name string.
z *intern.Value
}
// z0, z4, and z6noz are sentinel IP.z values.
// See the IP type's field docs.
var (
z0 = (*intern.Value)(nil)
z4 = new(intern.Value)
z6noz = new(intern.Value)
)
z
フィールドは、z0
、z4
、z6noz
変数が定義されており、z0
は不正な IP アドレス、z4
は IPv4 アドレス、z6noz
はゾーンインデックスを持たない IPv6 アドレスを示します。 さらに z
フィールドには、任意のゾーンインデックスを表す文字列を保存することが可能で、その際はゾーンインデックス付きの IPv6 アドレスであることを示します。
ゾーンインデックス(Zone Index) とは、パケットを送信するネットワークインターフェースを指定するための識別子です。 パケットはルーティングテーブルの情報を参照して IP アドレスのゾーンに応じて、送信するネットワークインターフェースを決定します。
ゾーンインデックスは、特に IPv6 の LLA(Link Local Address) 等において重要になります。 IPv6 ネットワークでは、仕組み上、異なるネットワークに同一の LLA が存在する場合があります。
例:
- eth0(ネットワーク 1)に接続された機器 A の IP:
fe80::1ff:fe23:4567:890a
- eth1(ネットワーク 2)に接続された機器 B の IP:
fe80::1ff:fe23:4567:890a
この場合、送信先の IP アドレスが同じであるため、どのネットワークインターフェースを使用するべきか、アドレスのみでは判断できません。
そこで、fe80::1ff:fe23:4567:890a%eth0
や fe80::1ff:fe23:4567:890a%eth1
のように IPv6 アドレスの末尾に %
ゾーンインデックスを指定することで、ネットワークインターフェースを指定します。 これにより、インターフェースが接続する先のネットワークを一意に特定することができます。
ここで、fe80::1ff:fe23:4567:890a%eth0
の場合、netip.Addr
型の z
フィールドに eth0
という値が格納されます。
これは ping6
コマンドでも同様です。 ping6
は宛先 IP アドレスの末尾に、%
で使用する送信元のインターフェースが指定されます。
$ ping6 -c 5 fe80::cd9:23bb:538b:5fa7
PING6(56=40+8+8 bytes) fe80::cd9:23bb:538b:5fa7%en0 --> fe80::cd9:23bb:538b:5fa7
16 bytes from fe80::cd9:23bb:538b:5fa7%en0, icmp_seq=0 hlim=64 time=0.255 ms
16 bytes from fe80::cd9:23bb:538b:5fa7%en0, icmp_seq=1 hlim=64 time=0.470 ms
16 bytes from fe80::cd9:23bb:538b:5fa7%en0, icmp_seq=2 hlim=64 time=0.385 ms
16 bytes from fe80::cd9:23bb:538b:5fa7%en0, icmp_seq=3 hlim=64 time=0.437 ms
16 bytes from fe80::cd9:23bb:538b:5fa7%en0, icmp_seq=4 hlim=64 time=0.390 ms
--- fe80::cd9:23bb:538b:5fa7 ping6 statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 0.255/0.387/0.470/0.073 ms
intern.Value
型
z
フィールドは intern.Value
型です。
intern.Value
は以下のような型定義を取るカスタム型です。
// A Value pointer is the handle to an underlying comparable value.
// See func Get for how Value pointers may be used.
type Value struct {
_ [0]func() // prevent people from accidentally using value type as comparable
cmpVal any
// resurrected is guarded by mu (for all instances of Value).
// It is set true whenever v is synthesized from a uintptr.
resurrected bool
}
ここで、[0]func()
は長さゼロの関数型配列です。 Go では、ゼロ長の配列自体はメモリを消費せず、ただの型制約として機能します。 関数型 func()
は比較不可能(non-comparable)な型であるため、これを言わばダミーフィールドとして Value
構造体内に含めることで Value
自体を Go のコンパイラが比較できないようにするというトリックを使っています。 つまり、Value
は 常にポインタ *Value
で管理されることになります。
また、Go 1.18 以降、any
型は interface{}
のエイリアス型であり、どんな値でも格納することが可能です。 cmpVal
には、比較対象となる値を格納することで間接的に比較を可能にします。
Addr
型が Value
構造体を採用する理由は、string
型よりも 8byte 分メモリフットプリントを小さくするためです。
Go の string
型は、ポインタ + 長さ で構成され、それぞれが 8byte となっています。
string の構造(16byte)
┌──────────────┬──────────────┐
│ pointer (8) │ length (8) │
└──────────────┴──────────────┘
つまり、string
型の比較には、ポインタと長さの両方を使用する必要があるわけです。
一方の、*intern.Value
の場合、ポインタの参照先に実際の文字列が格納されることで、同じ文字列は同じポインタとなる ため、比較がポインタのアドレスのみで可能となります。 これにより、ポインタアドレスを比較することで同一文字列であるかを確認できます。
*intern.Value の構造(8byte)
┌──────────────┐
│ pointer (8) │
└──────────────┘
なぜ構造体管理なのか
netip.Addr
型は構造体を採用することで次のようなメリットがあります。
省メモリ
従来の net パッケージの net.IP
型は合計 40byte、ゾーンを持つ net.IPAddr
型は 56byte でしたが、netip パッケージの netip.Addr
型は 24byte とメモリフットプリントが小さくなっています。
net.IP
型は、スライスであるため、長さや容量を管理する len
や cap
の情報を持つ Slice Header の 24byte が必要となります。 これは IPv6 アドレスの 16byte よりも大きい値です。

netip.Addr
型は構造体を採用することで、この問題を解決しています。
ip := make(net.IP, net.IPv6len)
sliceHeaderSize := int(unsafe.Sizeof(ip))
ipSize := len([net.IPv6len]net.IP{})
fmt.Printf("net.IP([%v]byte) Size = %vbyte\n", net.IPv6len, sliceHeaderSize+ipSize) // net.IP([16]byte) Size = 40byte
var addr netip.Addr
fmt.Printf("netip.Addr Size = %vbyte\n", unsafe.Sizeof(addr)) // netip.Addr Size = 24byte
メモリアロケーションが発生しない
netip.Addr
型は値レシーバ(func (a T) Method() {}
)を採用しています。
func (a netip.Addr) IsValid() bool {
return a.z != 0
}
Go ランタイムの仕組み上、ポインタを持つ変数はヒープに割り当てられやすいですが、値渡し(Call by value)であればエスケープ解析によってスタック上で処理されます。

これにより、ヒープアロケーションが発生しないためガベージコレクションの負担も抑えることができます。
また、他の関数に渡す場合も Call by value となります。
イミュータブル
イミュータブル(不変性)とは、関数または値の呼び出し先を書き換えても、呼び出し元の値が変わらない という性質です。
netip.Addr
型のメンバ変数はパッケージ外に非公開となっており、実体に対する値レシーバが用意されています。 値レシーバのコールはスタック上の操作となるため、呼び出し元の値は不変(Immutable)です。
// net.IP:アドレスは変わらないが値が変わっている
ip := net.ParseIP("192.168.1.1")
fmt.Printf("ip %v: %p\n", ip, ip) // ip 192.168.1.1: 0x1400000e170
ip[15] = 2
fmt.Printf("ip %v: %p\n", ip, ip) // ip 192.168.1.2: 0x1400000e170
// netip.Addr:アドレスと値のどちらも変わっている
addr := netip.MustParseAddr("192.168.1.1")
addrNext := addr.Next()
fmt.Printf("addr %v: %p\n", addr, &addr) // addr 192.168.1.1: 0x1400000c198
fmt.Printf("addrNext %v: %p\n", addrNext, &addrNext) // addrNext 192.168.1.2: 0x1400000c1b0

==
演算子で比較可能
net.IP
型はスライスであるため直接的な比較ができませんが、netip.Addr
型は構造体であるため、==
演算子で比較ができます。
ip := net.ParseIP("192.168.1.1")
ip2 := net.ParseIP("192.168.1.2")
fmt.Printf("%v\n", ip == ip2) // NG: invalid operation: ip == ip2 (slice can only be compared to nil)
addr := netip.MustParseAddr("192.168.1.1")
addr2 := netip.MustParseAddr("192.168.1.1")
fmt.Printf("%v\n", addr == addr2) // true
addr3 := netip.MustParseAddr("192.168.1.2")
fmt.Printf("%v\n", addr == addr3) // false
map の key に使用可能
The comparison operators == and != must be fully defined for operands of the key type;
map の key は比較可能な型である必要があるため、netip.Addr
型では key として利用できます。
addr := netip.MustParseAddr("192.168.1.1")
addrKeyMap := map[netip.Addr]string{addr: "IPv4"}
fmt.Printf("%v\n", addrKeyMap) // map[192.168.1.1:IPv4]
net.IP
型との互換性
依存関係
Go の開発チームは将来的に、過去の API は内部で netip.Addr
型を使い、入出力だけを net.IP
型に変換して返したいと考えているようですが、Go 1.18 のリリース時点ではこの変更は入っていません。
また、net → netip への依存はありますが、逆はありません。
net.IP
型の IP アドレスに関する操作メソッドは、基本的に netip.Addr
型でも提供 されています。
以下は、与えられた IP アドレス(netip.Addr
型)がプライベート IP アドレスであるかを判定するメソッドの例です。
func (ip Addr) IsPrivate() bool {
// Match the stdlib's IsPrivate logic.
if ip.Is4() {
// RFC 1918 allocates 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 as
// private IPv4 address subnets.
return ip.v4(0) == 10 ||
(ip.v4(0) == 172 && ip.v4(1)&0xf0 == 16) ||
(ip.v4(0) == 192 && ip.v4(1) == 168)
}
if ip.Is6() {
// RFC 4193 allocates fc00::/7 as the unique local unicast IPv6 address
// subnet.
return ip.v6(0)&0xfe == 0xfc
}
return false // zero value
}
注意すべきは、net.IP
型を利用していた機能のすべてが netip.Addr
型で提供されているわけではないという点です。
例えば、LookupNetIP
メソッドのように既存機能とほぼ同じ機能で、netip.Addr
型を返すようにしたものもありますが、すべてのメソッドに存在するわけではありません。
// LookupIP メソッドの netip.Addr バージョン
func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
ips, err := r.LookupIP(ctx, network, host)
if err != nil {
return nil, err
}
ret := make([]netip.Addr, 0, len(ips))
for _, ip := range ips {
if a, ok := netip.AddrFromSlice(ip); ok {
ret = append(ret, a)
}
}
return ret, nil
}
UDP 関連のメソッド
本来の目的であった UDP 関連の処理を高速化するためにいくつかのメソッドが追加されました。
例えば、net パッケージの UDPConn
をレシーバに取る 以下のメソッドは netip.Addr
型を使用して高速化しています。
ReadFromUDPAddrPort
func (c *UDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error)
ReadMsgUDPAddrPort
func (c *UDPConn) ReadMsgUDPAddrPort(b, oob []byte) (n, oobn, flags int, addr netip.AddrPort, err error)
WriteToUDPAddrPort
func (c *UDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error)
WriteMsgUDPAddrPort
func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error)
相互型変換
net.IP
型と netip.Addr
型は相互に変換が可能です。
- net.IP → netip.Addr
ip := net.ParseIP("192.168.1.1")
if addr, ok := netip.AddrFromSlice(ip); ok {
fmt.Printf("addr: %v\n", addr) // addr: ::ffff:192.168.1.1
}
ここで ::ffff:192.168.1.1
は IPv4-mapped IPv6 形式であるため、元の IPv4 アドレスを取り出すには Unmap を使用します。
fmt.Printf("addr: %v\n", addr.Unmap()) // addr: 192.168.1.1
- netip.Addr → net.IP
addr := netip.MustParseAddr("192.168.1.1")
ip2 := net.IP(addr.AsSlice())
fmt.Printf("ip: %v\n", ip2)
netip.Addr
型の使い方
netip.Addr 型に関連する関数やメソッドを紹介します。
netip.Addr
型の表示
addr := netip.MustParseAddr("fe80::1%eth1")
fmt.Printf("addr: %v\n", addr) // fe80::1%eth1
fmt.Printf("addr.Zone(): %v\n", addr.Zone()) // eth1
fmt.Printf("addr.BitLen(): %v\n", addr.BitLen()) // 128
fmt.Printf("addr.String(): %v\n", addr.String()) // fe80::1%eth1
fmt.Printf("addr.StringExpanded(): %v\n", addr.StringExpanded()) // fe80:0000:0000:0000:0000:0000:0000:0001%eth1
netip.Addr
型の生成
parseAddr, err := netip.ParseAddr("192.168.1.1")
if err != nil {
return err
}
fmt.Printf("parseAddr: %v\n", parseAddr)
mustParseAddr := netip.MustParseAddr("192.168.1.1")
fmt.Printf("mustParseAddr: %v\n", mustParseAddr)
ip := net.ParseIP("192.168.1.1")
fmt.Printf("ip: %v\n", ip)
var ipv4 [net.IPv4len]byte
copy(ipv4[:], ip[12:16])
addr4 := netip.AddrFrom4(ipv4)
fmt.Printf("addr4: %v\n", addr4)
var ipv6 [net.IPv6len]byte
copy(ipv6[:], ip[:])
addr16 := netip.AddrFrom16(ipv6)
fmt.Printf("addr16: %v\n", addr16)
if addr, ok := netip.AddrFromSlice(ip); ok {
fmt.Printf("addr: %v\n", addr)
}
netip.Addr
型の変換
addr4 := netip.MustParseAddr("192.168.1.1")
fmt.Printf("addr4: %v\n", addr4) // 192.168.1.1
fmt.Printf("addr4.As4(): %v\n", addr4.As4()) // [192 168 1 1]
fmt.Printf("addr4.As16(): %v\n", addr4.As16()) // [0 0 0 0 0 0 0 0 0 0 255 255 192 168 1 1]
fmt.Printf("addr4.AsSlice(): %v\n", addr4.AsSlice()) // [192 168 1 1]
addr16 := netip.MustParseAddr("fe80::1%eth1")
fmt.Printf("addr16: %v\n", addr16) // fe80::1%eth1
// fmt.Printf("addr16.As4(): %v\n", addr16.As4()) // panic: As4 called on IPv6 address
fmt.Printf("addr16.As16(): %v\n", addr16.As16()) // [254 128 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
fmt.Printf("addr16.AsSlice(): %v\n", addr16.AsSlice()) // [254 128 0 0 0 0 0 0 0 0 0 0 0 0 0 1]
アドレスの比較
addr1 := netip.MustParseAddr("192.168.1.1")
addr2 := netip.MustParseAddr("192.168.1.2")
fmt.Printf("192.168.1.1.Compare(192.168.1.2) = %v\n", addr1.Compare(addr2)) // -1
fmt.Printf("192.168.1.2.Compare(192.168.1.1) = %v\n", addr2.Compare(addr1)) // 1
fmt.Printf("192.168.1.1.Compare(192.168.1.1) = %v\n", addr1.Compare(addr1)) // 0
fmt.Printf("192.168.1.1.Less(192.168.1.2) = %v\n", addr1.Less(addr2)) // true
fmt.Printf("192.168.1.1.Is4() = %v\n", addr1.Is4()) // true
fmt.Printf("192.168.1.1.Is6() = %v\n", addr1.Is6()) // false
fmt.Printf("192.168.1.1.Is4In6() = %v\n", addr1.Is4In6()) //false
// IPv4-Mapped IPv6 Address
addr3 := netip.MustParseAddr("::ffff:192.168.1.1")
fmt.Printf("::ffff:192.168.1.1.Is4() = %v\n", addr3.Is4()) // false
fmt.Printf("::ffff:192.168.1.1.Is6() = %v\n", addr3.Is6()) // true
fmt.Printf("::ffff:192.168.1.1.Is4In6() = %v\n", addr3.Is4In6()) // true
// Unmap することで IPv4-Mapped IPv6 Address が IPv4 アドレスになる
fmt.Printf("::ffff:192.168.1.1.Unmap().Is4() = %v\n", addr3.Unmap().Is4()) // true
fmt.Printf("::ffff:192.168.1.1.Unmap().Is6() = %v\n", addr3.Unmap().Is6()) // false
fmt.Printf("::ffff:192.168.1.1.Unmap().Is4In6() = %v\n", addr3.Unmap().Is4In6()) // false
アドレス種別の判別
// IPv6
fmt.Printf("2000::1.IsGlobalUnicast() = %v\n", netip.MustParseAddr("2000::1").IsGlobalUnicast()) // true
fmt.Printf("ff01::1.IsInterfaceLocalMulticast() = %v\n", netip.MustParseAddr("ff01::1").IsInterfaceLocalMulticast()) // true
fmt.Printf("ff02::1.IsLinkLocalMulticast() = %v\n", netip.MustParseAddr("ff02::1").IsLinkLocalMulticast()) // true
fmt.Printf("fe80::1.IsLinkLocalUnicast() = %v\n", netip.MustParseAddr("fe80::1").IsLinkLocalUnicast()) // true
fmt.Printf("::1.IsLoopback() = %v\n", netip.MustParseAddr("::1").IsLoopback()) // true
fmt.Printf("ff02::1.IsMulticast() = %v\n", netip.MustParseAddr("ff02::1").IsMulticast()) // true
fmt.Printf("fd00::1.IsPrivate() = %v\n", netip.MustParseAddr("fd00::1").IsPrivate()) // true
fmt.Printf("::.IsUnspecified() = %v\n", netip.MustParseAddr("::").IsUnspecified()) // true
fmt.Printf("fe80::1.IsValid() = %v\n", netip.MustParseAddr("fe80::1").IsValid()) // true
fmt.Printf("::.IsValid() = %v\n", netip.MustParseAddr("::").IsValid()) // true
// IPv4
fmt.Printf("192.0.2.1.IsGlobalUnicast() = %v\n", netip.MustParseAddr("192.0.2.1").IsGlobalUnicast()) // true
fmt.Printf("224.0.0.1.IsInterfaceLocalMulticast() = %v\n", netip.MustParseAddr("224.0.0.1").IsInterfaceLocalMulticast()) // false(IPv4 に InterfaceLocalMulticast は存在しない)
fmt.Printf("224.0.0.1.IsLinkLocalMulticast() = %v\n", netip.MustParseAddr("224.0.0.1").IsLinkLocalMulticast()) // true
fmt.Printf("169.254.0.1.IsLinkLocalUnicast() = %v\n", netip.MustParseAddr("169.254.0.1").IsLinkLocalUnicast()) // true
fmt.Printf("127.0.0.1.IsLoopback() = %v\n", netip.MustParseAddr("127.0.0.1").IsLoopback()) // true
fmt.Printf("224.0.0.1.IsMulticast() = %v\n", netip.MustParseAddr("224.0.0.1").IsMulticast()) // true
fmt.Printf("192.168.1.1.IsPrivate() = %v\n", netip.MustParseAddr("192.168.1.1").IsPrivate()) // true
fmt.Printf("0.0.0.0.IsUnspecified() = %v\n", netip.MustParseAddr("0.0.0.0").IsUnspecified()) // true
fmt.Printf("192.168.1.1.IsValid() = %v\n", netip.MustParseAddr("192.168.1.1").IsValid()) // true
fmt.Printf("0.0.0.0.IsValid() = %v\n", netip.MustParseAddr("0.0.0.0").IsValid()) // true
// その他
fmt.Printf("netip.Addr{}.IsValid() = %v\n", netip.Addr{}.IsValid()) // false
IP アドレスの操作
addr4 := netip.MustParseAddr("192.168.1.2")
fmt.Printf("addr: %v\n", addr4) // 192.168.1.2
fmt.Printf("addr.Prev(): %v\n", addr4.Prev()) // 192.168.1.1
fmt.Printf("addr.Next(): %v\n", addr4.Next()) // 192.168.1.3
addr4Prefix, err := addr4.Prefix(24)
if err != nil {
return err
}
fmt.Printf("addr4.Prefix(): %v\n", addr4Prefix) // 192.168.1.0/24
fmt.Printf("addr4.WithZone(eth1): %v\n", addr4.WithZone("eth1")) // 192.168.1.2(IPv4 アドレスはゾーンに対応していない)
addr6 := netip.MustParseAddr("fe80::2")
fmt.Printf("addr6: %v\n", addr6) // fe80::2
fmt.Printf("addr6.Prev(): %v\n", addr6.Prev()) // fe80::1
fmt.Printf("addr6.Next(): %v\n", addr6.Next()) // fe80::3
addr6Prefix, err := addr6.Prefix(24)
if err != nil {
return err
}
fmt.Printf("addr6.Prefix(): %v\n", addr6Prefix) // fe80::/24
fmt.Printf("addr6.WithZone(eth1): %v\n", addr6.WithZone("eth1")) // fe80::2%eth1
Addr
の Marshal と Unmarshal
addr4 := netip.MustParseAddr("192.168.1.1")
addr4b, err := addr4.MarshalBinary()
if err != nil {
return err
}
fmt.Printf("addr4.MarshalBinary(): %v\n", addr4b) // [192 168 1 1]
var addr42 netip.Addr
if err := addr42.UnmarshalBinary(addr4b); err != nil {
return err
}
fmt.Printf("addr42.UnmarshalBinary(addr4b): %v\n", addr42) // 192.168.1.1
addr4b2, err := addr4.MarshalText()
if err != nil {
return err
}
fmt.Printf("addr4.MarshalText(): %v\n", addr4b2) // [49 57 50 46 49 54 56 46 49 46 49]
var addr43 netip.Addr
if err := addr43.UnmarshalText(addr4b2); err != nil {
return err
}
fmt.Printf("addr43.UnmarshalText(addr4b2): %v\n", addr43) // 192.168.1.1
netip.AddrPort
の構造
// AddrPort is an IP and a port number.
type AddrPort struct {
ip Addr
port uint16
}
netip.AddrPort
型は、netip.Addr
型とともに port
を持っています。
netip.AddrPort
の使い方
AddrPort
型の生成
addr4 := netip.MustParseAddr("192.168.1.1")
addr4Port := netip.AddrPortFrom(addr4, 8080)
fmt.Printf("AddrPortFrom(): %v\n", addr4Port) // 192.168.1.1:8080
parseAddr4Port, err := netip.ParseAddrPort("192.168.1.1:8080")
if err != nil {
return err
}
fmt.Printf("ParseAddrPort(192.168.1.1:8080): %v\n", parseAddr4Port) // 192.168.1.1:8080
mustParseAddr4Port := netip.MustParseAddrPort("192.168.1.1:8080")
fmt.Printf("MustParseAddrPort(192.168.1.1:8080): %v\n", mustParseAddr4Port) // 192.168.1.1:8080
アドレスポートの表示と判定
addr4Port := netip.MustParseAddrPort("192.168.1.1:8080")
fmt.Printf("addr4Port.String(): %v\n", addr4Port.String()) // 192.168.1.1:8080
fmt.Printf("addr4Port.Port(): %v\n", addr4Port.Port()) // 8080
fmt.Printf("addr4Port.Addr(): %v\n", addr4Port.Addr()) // 192.168.1.1
fmt.Printf("addr4Port.IsValid(): %v\n", addr4Port.IsValid()) // true
b := make([]byte, 0, len(addr4Port.String()))
fmt.Printf("addr4Port.AppendTo(b): %s\n", addr4Port.AppendTo(b)) // 192.168.1.1:8080
アドレスポートの比較
netip.Addr
型と同様に ==
演算子で比較可能です。
addrPort := netip.MustParseAddrPort("192.168.1.1:8080")
addrPort2 := netip.MustParseAddrPort("192.168.1.1:8080")
addrPort3 := netip.MustParseAddrPort("192.168.1.2:8080")
addrPort4 := netip.MustParseAddrPort("192.168.1.1:443")
fmt.Printf("%v\n", addrPort == addrPort2) // true
fmt.Printf("%v\n", addrPort == addrPort3) // false(IP アドレスが異なる)
fmt.Printf("%v\n", addrPort == addrPort4) // false(ポートが異なる)
AddrPort
の Marshal と Unmarshal
addr4Port := netip.MustParseAddrPort("192.168.1.1:8080")
addr4Portb, err := addr4Port.MarshalBinary()
if err != nil {
return err
}
fmt.Printf("addr4Portb.MarshalBinary(): %v\n", addr4Portb) // [192 168 1 1 144 31]
var addr4Port2 netip.AddrPort
if err := addr4Port2.UnmarshalBinary(addr4Portb); err != nil {
return err
}
fmt.Printf("addr4Port2.UnmarshalBinary(addr4Portb): %v\n", addr4Port2) // 192.168.1.1:8080
addr4Portb2, err := addr4Port.MarshalText()
if err != nil {
return err
}
fmt.Printf("addr4Port.MarshalText(): %v\n", addr4Portb2) // [49 57 50 46 49 54 56 46 49 46 49 58 56 48 56 48]
var addr4Port3 netip.AddrPort
if err := addr4Port3.UnmarshalText(addr4Portb2); err != nil {
return err
}
fmt.Printf("addr43.UnmarshalText(addr4Portb2): %v\n", addr4Port3) // 192.168.1.1:8080
netip.Prefix
の構造
// Prefix is an IP address prefix (CIDR) representing an IP network.
//
// The first Bits() of Addr() are specified. The remaining bits match any address.
// The range of Bits() is [0,32] for IPv4 or [0,128] for IPv6.
type Prefix struct {
ip Addr
// bits is logically a uint8 (storing [0,128]) but also
// encodes an "invalid" bit, currently represented by the
// invalidPrefixBits sentinel value. It could be packed into
// the uint8 more with more complicated expressions in the
// accessors, but the extra byte (in padding anyway) doesn't
// hurt and simplifies code below.
bits int16
}
netip.Prefix
は、CIDR(Classless Inter-domain Routing)を用いて IP アドレスをネットワークプレフィックスで管理する構造体です。
CIDR とは、クラスレスネットワークにおいて IP アドレスを 192.168.1.0/24 のように表現することです。
ここで、/24 は 192.168.1 までの 24bit がネットワークアドレスを表し、残りの 8bit がホストアドレスを表すことを意味します。
この時、192.168.1.0 が ip
フィールドに保持され、24 が bits
フィールドに保持されます。
netip.Prefix
の使い方
Prefix
型の生成
parsePrefix, err := netip.ParsePrefix("192.168.1.0/24")
if err != nil {
return err
}
fmt.Printf("ParsePrefix: %v\n", parsePrefix) // 192.168.1.0/24
mustParsePrefix := netip.MustParsePrefix("192.168.1.0/24")
fmt.Printf("MustParsePrefix: %v\n", mustParsePrefix) // 192.168.1.0/24
prefixFrom := netip.PrefixFrom(mustParsePrefix.Addr(), 24)
fmt.Printf("PrefixForm: %v\n", prefixFrom) // 192.168.1.0/24
操作
prefix := netip.MustParsePrefix("192.168.1.1/24")
fmt.Printf("prefix.Masked(): %v\n", prefix.Masked()) // 192.168.1.0/24
b := make([]byte, 0, len(prefix.String()))
fmt.Printf("prefix.AppendTo(b): %s\n", prefix.AppendTo(b)) // 192.168.1.1/24
プレフィックスの比較
netip.Addr
型 や netip.AddrPort
型と同様に ==
で比較可能です。
prefix := netip.MustParsePrefix("192.168.1.1/24")
prefix1 := netip.MustParsePrefix("192.168.1.1/24")
prefix2 := netip.MustParsePrefix("192.168.1.1/16")
fmt.Printf("192.168.1.1/24 == 192.168.1.1/24: %v\n", prefix == prefix1) // true
fmt.Printf("192.168.1.1/24 == 192.168.1.1/16: %v\n", prefix == prefix2) // false
fmt.Printf("192.168.1.1/24.Overlaps(192.168.1.1/16): %v\n", prefix.Overlaps(prefix2)) // true
prefix3 := netip.MustParsePrefix("172.16.0.0/12")
fmt.Printf("192.168.1.1/24.Overlaps(172.16.0.0/12): %v\n", prefix.Overlaps(prefix3)) // false
fmt.Printf("192.168.1.1/24.IsSingleIP(): %v\n", prefix.IsSingleIP()) // false
prefix4 := netip.MustParsePrefix("192.168.1.1/32")
fmt.Printf("192.168.1.1/32.IsSingleIP(): %v\n", prefix4.IsSingleIP()) // true
プレフィックスベースの表示
prefix := netip.MustParsePrefix("192.168.1.1/24")
fmt.Printf("192.168.1.1/24.Addr(): %v\n", prefix.Addr()) // 192.168.1.1
fmt.Printf("192.168.1.1/24.Bits(): %v\n", prefix.Bits()) // 24
fmt.Printf("192.168.1.1/24.String(): %v\n", prefix.Bits()) // 192.168.1.1/24
Prefix
の Marshal と Unmarshal
prefix := netip.MustParsePrefix("192.168.1.0/24")
prefixb, err := prefix.MarshalBinary()
if err != nil {
return err
}
fmt.Printf("prefix.MarshalBinary(): %v\n", prefixb) // [192 168 1 0 24]
var prefix2 netip.Prefix
if err := prefix2.UnmarshalBinary(prefixb); err != nil {
return err
}
fmt.Printf("prefix2.UnmarshalBinary(prefixb): %v\n", prefix2) // 192.168.1.0/24
prefixb2, err := prefix.MarshalText()
if err != nil {
return err
}
fmt.Printf("prefix.MarshalText(): %v\n", prefixb2) // [49 57 50 46 49 54 56 46 49 46 48 47 50 52]
var prefix3 netip.Prefix
if err := prefix3.UnmarshalText(prefixb2); err != nil {
return err
}
fmt.Printf("prefix3.UnmarshalText(prefix4b2): %v\n", prefix3) // 192.168.1.0/24
まとめ
今回のブログでは Go 1.18 で追加された netip パッケージについて紹介しました。
netip パッケージには、構造体で定義された Addr
型が追加されており、バイトスライスを使用する net パッケージ IP
型と比べて、メモリリソースやオーバーヘッドの削減、IP 操作に関する処理の簡素化が実現されています。 netip パッケージは UDP コネクションを使用する既存のメソッドへの導入が進められており、今後も net パッケージの内部処理は Addr
型への移行が進むと考えられます。
現在、研究開発している CYPHONIC も、これを気に net パッケージから netip パッケージに移行していきたいと思います。
参考・引用
- #46518: net/netip: add new IP address package, use in net
- #47323: proposal: net/netaddr: add new IP address type, netaddr package (discussion)
- netaddr.IP: a new IP address type for Go
- #63: Change the representation of IP to a pair of uint64s
- RFC 4007 - IPv6 Scoped Address Architecture
- RFC 4291 - IP Version 6 Addressing Architecture
- RFC 2632 - Classless Inter-domain Routing (CIDR): The Internet Address Assignment and Aggregation Plan
- 新たに追加された net/netip とは
- Go 1.18 から導入される netip package/netip-package
- Go 1.18 で導入された net/netip package