🌐

Go 1.18で導入されたnet/netip package

2022/03/16に公開

Go1.18からnet.IP型に代わるnetip.Addr型を提供されます。
この記事では、netip packageの特徴、互換性、メソッドの使い方の具体例を紹介します。

https://pkg.go.dev/net/netip

特徴

netip.Addr型は、net.IP型に対して以下のような特徴があります。

  • 省メモリ
  • メモリアロケーションがないので速い
  • 不変
  • == で比較可能
  • mapのKeyに使える

なぜnet.IP型を改善する必要があったのか

元Goチーム、現在tailscale社のbradfitz氏からの提案で始まりました。
Tailscale社のプロダクトはネットワークアプリケーションであるためIPアドレスの操作が多く高速化は重要です。またGoでは、UDPパケットの受信で利用するメソッドでIPを返すため、そのたびにnet.IP型を生成するとメモリアロケーションが発生して遅くなる問題がありました。また近年QUIC、WireGuardなどUDPを利用するプロトコルも増えているため、Goの標準ライブラリに入る価値が大きいです。

netip.Addr型の構造

netip.Addr型は構造体で、 IPv4アドレス、IPv6アスレス+ゾーンインデックスを表現する構造体です。 addr uint128 フィールドと、 z *intern.Value を持ちます。addrフィールドは、IPv4アドレスまたはIPv6アドレスを持ち、zフィールドがゾーンを持ちます。
ゾーンについては後述します。

net packageではnet.IP型とゾーンを持つnet.IPAddr型をそれぞれ定義しているのに対して、netip.Addr型は1つで両方を兼ねています。

type Addr struct {
	addr uint128
	z *intern.Value
}

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 packageに定義されている、128bit(16byte)を表現する型です。
IPv6アドレスは16byteであるため、128bit(16byte)になっています。

zフィールドがv4の場合には ::ffff:192.168.1.1 のようなIPv4-mapped IPv6 addressの形式で保持されます。 RFC4291 - 2.5.5.2. IPv4-Mapped IPv6 Address

IPv6アドレスが 0011:2233:4455:6677:8899:aabb:ccdd:eeff のようなときには addr.hi = 0x0011223344556677 addr.lo = 0x8899aabbccddeeffと保存されます。

  • 何故[16]byteのbyte配列ではないのか?
    uint64で表現することで64bit CPUでは2つのレジスタでIPv6を表現できるようになるため、IPアドレスに対する算術演算とビット演算処理を高速化することが可能であるためです。ベンチマークはこちらを参照

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フィールドの値

zフィールドは、z0、z4、z6noz変数が定義されており、z0は不正なIPアドレス、z4はIPv4アドレス、z6nozはゾーンインデックスを持たないIPv6アドレスを示しています。さらにzフィールドには、任意のゾーンインデックスを表す文字列を保存することが可能で、そのときはゾーンインデックス付きのIPv6アドレスであることを示します。

ゾーンインデックスとは、パケットを送信するネットワークインターフェースを指定するための識別子です。パケットはルーティングテーブルの情報を参照してIPアドレスのゾーンに応じて、送信するネットワークインターフェースを決定します。しかしこのときリンクローカルアドレスなどは、同一のアドレスが異なるインターフェースの先のネットワークで存在することがあるため、送信先にIPアドレスだけを指定すると送信先のネットワークを指定することができません。

そこで fe80::1ff:fe23:4567:890a%eth1fe80::1ff:fe23:4567:890a%eth2 のようにIPv6アドレスの末尾に %ゾーンインデックス を指定することで、ネットワークインターフェースを指定することができるようになります。

ゾーンインデックスは、 RFC4007 IPv6 Scoped Address Architectureで定義されています。

netip.Addr型のzフィールドには、 fe80::1ff:fe23:4567:890a%eth1 のときは、eth1 という値が入ります。

intern.Value型

intern.Value型は、stringのように文字列を持つ型です。stringとの違いはintern.Value型をポインタで定義した場合に、文字列が同じであれば同じポインタアドレスを返すことです。この特徴を持つためポインタアドレスを比較することで同一文字列であることを確認することができます。
この同一アドレス同一文字列という特徴がどのように生きるのかというと、stringは16byteなのに対して、 *intern.Value は8byteであるため、比較可能な文字列をstringより8byte小さく実現することが可能となります。

この特徴によりnetip.Addr型はintern.Value型を利用することで、比較可能であることとメモリフットプリントを小さくすることを両立しています。

特徴

netip.Addr型の特徴を紹介します。

省メモリ

従来のnet apckageのnet.IP型は合計40byte、ゾーンを持つnet.IPAddr型は56byteでしたが、新たなnetip packageの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)

var addr netip.Addr
fmt.Printf("netip.Addr Size = %vbyte\n", unsafe.Sizeof(addr))

// 実行結果
// net.IP([16]byte) Size = 40byte
// netip.Addr Size = 24byte

メモリアロケーションがないので速い

netip.Addr型は構造体です。netip.Addr型のメソッドを見てみると、多くがポインタではなく実体に対してメソッドが用意されていることがわかります。そのためメソッドでIPを操作したときもヒープではなくスタックにメモリが確保されます。他の関数に渡す場合も値をコピーして渡します。

不変

net.IP型はスライスなので不変を実現できませんでした。netip.Addr型はaddrフィールドなどはpackage外に非公開であり、メソッドは実体に対してメソッドが用意されているため、メソッドを実行してフィールドを操作すると新しくスタックにメモリが確保されるため、元の値は不変になります。

ip := net.ParseIP("192.168.1.1")
fmt.Printf("ip %v: %p\n", ip, ip)
ip[15] = 2
fmt.Printf("ip %v: %p\n", ip, ip)
// 実行結果 値は変わっているがアドレスは変わっていない
// ip 192.168.1.1: 0x1400001e2c0
// ip 192.168.1.2: 0x1400001e2c0

// netip.Addr型は不変
addr := netip.MustParseAddr("192.168.1.1")
addrNext := addr.Next()
fmt.Printf("addr pointer = %p\n", &addr)
fmt.Printf("addrNext pointer = %p\n", &addrNext)
// 実行結果 値とアドレスどちらも変わっている
// addr 192.168.1.1:0xc0000b40c0
// addrNext 192.168.1.2: 0xc0000b40d8

== で比較可能

net.IP型はスライスであるため比較できませんでしたが、netip.Addr型は構造体であるため比較可能です。

ip := net.ParseIP("192.168.1.1")
ip2 := net.ParseIP("192.168.1.2")
fmt.Printf("%v\n", ip == ip2)
// ./prog.go:11:21: 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;
https://go.dev/ref/spec#Comparison_operators

mapのkeyは比較可能である必要があるため、netip.Addr型ではkeyとして利用できるようになりました。

addr := netip.MustParseAddr("192.168.1.1")
addrKeyMap := map[netip.Addr]string{addr: "IPv4アドレス"}
fmt.Printf("%v\n", addrKeyMap)

互換性

net.IP型とnetip.Addr型の機能の差

今までnet.IP型周辺のIPアドレスに関する操作のメソッドは、netip.Addr型にも提供されています。

以下の例はIPアドレスがプライベート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
}

標準ライブラリはIP型を利用しなくなる?

Goチームは将来的には過去のAPIは内部ではnetip.Addr型を使い入出力だけをnet.IP型に変換して返したいと考えているようですが、Go 1.18ではこの変更は入っていません
またnet package -> netip packageへの依存はありますが、逆はありません。

今まで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の高速化のためいくつかのメソッドが追加されている

UDPConn.ReadFromUDPAddrPort, UDPConn.ReadMsgUDPAddrPort, UDPConn.WriteToUDPAddrPort, UDPConn.WriteMsgUDPAddrPortなどは、netip.Addr型を返すことで高速化しています。

自分でコードを書く場合は?

netip.Addr型を利用して、必要に応じてnet.IP型に変換するのがいいと思います。使えなくなるわけではないのでnet.IP型を利用しても問題はありません。

// net.IP -> netip.Addr
ip := net.ParseIP("192.168.1.1")
if addr, ok := netip.AddrFromSlice(ip); ok {
	fmt.Printf("addr: %v\n", addr)
}

// 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

IPアドレスの判別

// 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アドレスはZoneは対応していない

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

Marhshal、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 Portが異なる

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の構造

netip.Prefix構造体は、RFC4632 - Classless Inter-domain Routing (CIDR) The Internet Address Assignment and Aggregation Planを管理する構造体です。

CIDRとは、192.168.1.0/24 のように表現して、24というのは、192.168.1 までの24bitがネットワークを表し、残りの8bitがホストを表現することを意味します。
このときの 192.168.1.0 がipフィールドに保持され、24がbitsフィールドに保持されます。

// 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の使い方

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型と同様に == で比較可能です。

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

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

パッケージ名 決定までの議論

  • 最初の提案 netaddr.IP
    • IP型の名前をnet packageと同じにしようとした
    • addr.IPというのが少し違和感
    • net package から分かれているのは良い。net package大きすぎる by rsc
  • 次の提案 ip.Addr
    • すっきりしていて良さそう
    • ただしパッケージ名がipだと、変数名ipと衝突しそう
  • netip.Addr
    • 決定

まとめ

netip packageには、Addr型、AddrPort型、Prefix型が用意されています。net packageに比べて全体的な構造がシンプルになりパフォーマンスの向上もしているためとても使いやすいパッケージとなっています。
今後はnetip.Addr型を利用することで、特に意識することなく最大限のパフォーマンスの恩恵を受けらるので、是非使ってみてください。

参考

Discussion