Skip to content

Commit 1d214f7

Browse files
committed
net: cache IPv6 zone information for applications using IPv6 link-local address
This change reduces the overhead of calling routing information per IPv6 link-local datagram read by caching IPv6 addressing scope zone information. Fixes #15237. name old time/op new time/op delta UDP6LinkLocalUnicast-8 64.9µs ± 0% 18.6µs ± 0% -71.30% name old alloc/op new alloc/op delta UDP6LinkLocalUnicast-8 11.2kB ± 0% 0.2kB ± 0% -98.42% name old allocs/op new allocs/op delta UDP6LinkLocalUnicast-8 101 ± 0% 3 ± 0% -97.03% Change-Id: I5ae2ef5058df1028bbb7f4ab32b13edfb330c3a7 Reviewed-on: https://go-review.googlesource.com/21952 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 19db745 commit 1d214f7

File tree

3 files changed

+115
-24
lines changed

3 files changed

+115
-24
lines changed

src/net/interface.go

+78-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
package net
66

7-
import "errors"
7+
import (
8+
"errors"
9+
"sync"
10+
"time"
11+
)
812

913
var (
1014
errInvalidInterface = errors.New("invalid network interface")
@@ -88,9 +92,12 @@ func (ifi *Interface) MulticastAddrs() ([]Addr, error) {
8892
func Interfaces() ([]Interface, error) {
8993
ift, err := interfaceTable(0)
9094
if err != nil {
91-
err = &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
95+
return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
9296
}
93-
return ift, err
97+
if len(ift) != 0 {
98+
zoneCache.update(ift)
99+
}
100+
return ift, nil
94101
}
95102

96103
// InterfaceAddrs returns a list of the system's network interface
@@ -137,10 +144,78 @@ func InterfaceByName(name string) (*Interface, error) {
137144
if err != nil {
138145
return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
139146
}
147+
if len(ift) != 0 {
148+
zoneCache.update(ift)
149+
}
140150
for _, ifi := range ift {
141151
if name == ifi.Name {
142152
return &ifi, nil
143153
}
144154
}
145155
return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errNoSuchInterface}
146156
}
157+
158+
// An ipv6ZoneCache represents a cache holding partial network
159+
// interface information. It is used for reducing the cost of IPv6
160+
// addressing scope zone resolution.
161+
type ipv6ZoneCache struct {
162+
sync.RWMutex // guard the following
163+
lastFetched time.Time // last time routing information was fetched
164+
toIndex map[string]int // interface name to its index
165+
toName map[int]string // interface index to its name
166+
}
167+
168+
var zoneCache = ipv6ZoneCache{
169+
toIndex: make(map[string]int),
170+
toName: make(map[int]string),
171+
}
172+
173+
func (zc *ipv6ZoneCache) update(ift []Interface) {
174+
zc.Lock()
175+
defer zc.Unlock()
176+
now := time.Now()
177+
if zc.lastFetched.After(now.Add(-60 * time.Second)) {
178+
return
179+
}
180+
zc.lastFetched = now
181+
if len(ift) == 0 {
182+
var err error
183+
if ift, err = interfaceTable(0); err != nil {
184+
return
185+
}
186+
}
187+
zc.toIndex = make(map[string]int, len(ift))
188+
zc.toName = make(map[int]string, len(ift))
189+
for _, ifi := range ift {
190+
zc.toIndex[ifi.Name] = ifi.Index
191+
zc.toName[ifi.Index] = ifi.Name
192+
}
193+
}
194+
195+
func zoneToString(zone int) string {
196+
if zone == 0 {
197+
return ""
198+
}
199+
zoneCache.update(nil)
200+
zoneCache.RLock()
201+
defer zoneCache.RUnlock()
202+
name, ok := zoneCache.toName[zone]
203+
if !ok {
204+
name = uitoa(uint(zone))
205+
}
206+
return name
207+
}
208+
209+
func zoneToInt(zone string) int {
210+
if zone == "" {
211+
return 0
212+
}
213+
zoneCache.update(nil)
214+
zoneCache.RLock()
215+
defer zoneCache.RUnlock()
216+
index, ok := zoneCache.toIndex[zone]
217+
if !ok {
218+
index, _, _ = dtoi(zone, 0)
219+
}
220+
return index
221+
}

src/net/ipsock.go

-21
Original file line numberDiff line numberDiff line change
@@ -249,24 +249,3 @@ func internetAddrList(net, addr string, deadline time.Time) (addrList, error) {
249249
}
250250
return filterAddrList(filter, ips, inetaddr)
251251
}
252-
253-
func zoneToString(zone int) string {
254-
if zone == 0 {
255-
return ""
256-
}
257-
if ifi, err := InterfaceByIndex(zone); err == nil {
258-
return ifi.Name
259-
}
260-
return uitoa(uint(zone))
261-
}
262-
263-
func zoneToInt(zone string) int {
264-
if zone == "" {
265-
return 0
266-
}
267-
if ifi, err := InterfaceByName(zone); err == nil {
268-
return ifi.Index
269-
}
270-
n, _, _ := dtoi(zone, 0)
271-
return n
272-
}

src/net/udpsock_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,43 @@ import (
1212
"time"
1313
)
1414

15+
func BenchmarkUDP6LinkLocalUnicast(b *testing.B) {
16+
testHookUninstaller.Do(uninstallTestHooks)
17+
18+
if !supportsIPv6 {
19+
b.Skip("IPv6 is not supported")
20+
}
21+
ifi := loopbackInterface()
22+
if ifi == nil {
23+
b.Skip("loopback interface not found")
24+
}
25+
lla := ipv6LinkLocalUnicastAddr(ifi)
26+
if lla == "" {
27+
b.Skip("IPv6 link-local unicast address not found")
28+
}
29+
30+
c1, err := ListenPacket("udp6", JoinHostPort(lla+"%"+ifi.Name, "0"))
31+
if err != nil {
32+
b.Fatal(err)
33+
}
34+
defer c1.Close()
35+
c2, err := ListenPacket("udp6", JoinHostPort(lla+"%"+ifi.Name, "0"))
36+
if err != nil {
37+
b.Fatal(err)
38+
}
39+
defer c2.Close()
40+
41+
var buf [1]byte
42+
for i := 0; i < b.N; i++ {
43+
if _, err := c1.WriteTo(buf[:], c2.LocalAddr()); err != nil {
44+
b.Fatal(err)
45+
}
46+
if _, _, err := c2.ReadFrom(buf[:]); err != nil {
47+
b.Fatal(err)
48+
}
49+
}
50+
}
51+
1552
type resolveUDPAddrTest struct {
1653
network string
1754
litAddrOrName string

0 commit comments

Comments
 (0)