]> Cypherpunks repositories - gostls13.git/commitdiff
net: update zoneCache on cache misses to cover appearing interfaces
authorMichael Stapelberg <stapelberg@google.com>
Fri, 2 Nov 2018 10:23:54 +0000 (11:23 +0100)
committerMikio Hara <mikioh.public.networking@gmail.com>
Tue, 6 Nov 2018 00:05:32 +0000 (00:05 +0000)
performance differences are in measurement noise as per benchcmp:

benchmark                            old ns/op     new ns/op     delta
BenchmarkUDP6LinkLocalUnicast-12     5012          5009          -0.06%

Fixes #28535

Change-Id: Id022e2ed089ce8388a2398e755848ec94e77e653
Reviewed-on: https://go-review.googlesource.com/c/146941
Run-TryBot: Mikio Hara <mikioh.public.networking@gmail.com>
Reviewed-by: Mikio Hara <mikioh.public.networking@gmail.com>
src/net/interface.go
src/net/interface_bsd_test.go
src/net/interface_linux_test.go
src/net/interface_unix_test.go

index 375a4568e3f289438887af4139a7fe560b021c0a..46b0400f2f5ecadf50d7979705c408120b86cd08 100644 (file)
@@ -102,7 +102,7 @@ func Interfaces() ([]Interface, error) {
                return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
        }
        if len(ift) != 0 {
-               zoneCache.update(ift)
+               zoneCache.update(ift, false)
        }
        return ift, nil
 }
@@ -159,7 +159,7 @@ func InterfaceByName(name string) (*Interface, error) {
                return nil, &OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
        }
        if len(ift) != 0 {
-               zoneCache.update(ift)
+               zoneCache.update(ift, false)
        }
        for _, ifi := range ift {
                if name == ifi.Name {
@@ -187,18 +187,21 @@ var zoneCache = ipv6ZoneCache{
        toName:  make(map[int]string),
 }
 
-func (zc *ipv6ZoneCache) update(ift []Interface) {
+// update refreshes the network interface information if the cache was last
+// updated more than 1 minute ago, or if force is set. It returns whether the
+// cache was updated.
+func (zc *ipv6ZoneCache) update(ift []Interface, force bool) (updated bool) {
        zc.Lock()
        defer zc.Unlock()
        now := time.Now()
-       if zc.lastFetched.After(now.Add(-60 * time.Second)) {
-               return
+       if !force && zc.lastFetched.After(now.Add(-60*time.Second)) {
+               return false
        }
        zc.lastFetched = now
        if len(ift) == 0 {
                var err error
                if ift, err = interfaceTable(0); err != nil {
-                       return
+                       return false
                }
        }
        zc.toIndex = make(map[string]int, len(ift))
@@ -209,16 +212,25 @@ func (zc *ipv6ZoneCache) update(ift []Interface) {
                        zc.toName[ifi.Index] = ifi.Name
                }
        }
+       return true
 }
 
 func (zc *ipv6ZoneCache) name(index int) string {
        if index == 0 {
                return ""
        }
-       zoneCache.update(nil)
+       updated := zoneCache.update(nil, false)
        zoneCache.RLock()
-       defer zoneCache.RUnlock()
        name, ok := zoneCache.toName[index]
+       zoneCache.RUnlock()
+       if !ok {
+               if !updated {
+                       zoneCache.update(nil, true)
+                       zoneCache.RLock()
+                       name, ok = zoneCache.toName[index]
+                       zoneCache.RUnlock()
+               }
+       }
        if !ok {
                name = uitoa(uint(index))
        }
@@ -229,10 +241,18 @@ func (zc *ipv6ZoneCache) index(name string) int {
        if name == "" {
                return 0
        }
-       zoneCache.update(nil)
+       updated := zoneCache.update(nil, false)
        zoneCache.RLock()
-       defer zoneCache.RUnlock()
        index, ok := zoneCache.toIndex[name]
+       zoneCache.RUnlock()
+       if !ok {
+               if !updated {
+                       zoneCache.update(nil, true)
+                       zoneCache.RLock()
+                       index, ok = zoneCache.toIndex[name]
+                       zoneCache.RUnlock()
+               }
+       }
        if !ok {
                index, _, _ = dtoi(name)
        }
index 69b0fbcab3de30f6928562d9d4496e87b457dbdd..947dde71e61b2935c062a0366bb5d1a73672a6a5 100644 (file)
@@ -7,6 +7,7 @@
 package net
 
 import (
+       "errors"
        "fmt"
        "os/exec"
        "runtime"
@@ -53,3 +54,7 @@ func (ti *testInterface) setPointToPoint(suffix int) error {
        })
        return nil
 }
+
+func (ti *testInterface) setLinkLocal(suffix int) error {
+       return errors.New("not yet implemented for BSD")
+}
index 6959ddb3d930f95a5a180b9c26bf3739ec51f4e6..0699fec636833f8fe36469012ebb569222e60c1b 100644 (file)
@@ -35,6 +35,31 @@ func (ti *testInterface) setBroadcast(suffix int) error {
        return nil
 }
 
+func (ti *testInterface) setLinkLocal(suffix int) error {
+       ti.name = fmt.Sprintf("gotest%d", suffix)
+       xname, err := exec.LookPath("ip")
+       if err != nil {
+               return err
+       }
+       ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+               Path: xname,
+               Args: []string{"ip", "link", "add", ti.name, "type", "dummy"},
+       })
+       ti.setupCmds = append(ti.setupCmds, &exec.Cmd{
+               Path: xname,
+               Args: []string{"ip", "address", "add", ti.local, "dev", ti.name},
+       })
+       ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
+               Path: xname,
+               Args: []string{"ip", "address", "del", ti.local, "dev", ti.name},
+       })
+       ti.teardownCmds = append(ti.teardownCmds, &exec.Cmd{
+               Path: xname,
+               Args: []string{"ip", "link", "delete", ti.name, "type", "dummy"},
+       })
+       return nil
+}
+
 func (ti *testInterface) setPointToPoint(suffix int) error {
        ti.name = fmt.Sprintf("gotest%d", suffix)
        xname, err := exec.LookPath("ip")
index c3d981dc5c54bc235d0feef84e1a856700353e6c..20e75cd03679597bc97ac508771197badc9816f6 100644 (file)
@@ -176,3 +176,37 @@ func TestInterfaceArrivalAndDeparture(t *testing.T) {
                }
        }
 }
+
+func TestInterfaceArrivalAndDepartureZoneCache(t *testing.T) {
+       if testing.Short() {
+               t.Skip("avoid external network")
+       }
+       if os.Getuid() != 0 {
+               t.Skip("must be root")
+       }
+
+       // Ensure zoneCache is filled:
+       _, _ = Listen("tcp", "[fe80::1%nonexistant]:0")
+
+       ti := &testInterface{local: "fe80::1"}
+       if err := ti.setLinkLocal(0); err != nil {
+               t.Skipf("test requires external command: %v", err)
+       }
+       if err := ti.setup(); err != nil {
+               t.Fatal(err)
+       }
+       defer ti.teardown()
+
+       time.Sleep(3 * time.Millisecond)
+
+       // If Listen fails (on Linux with “bind: invalid argument”), zoneCache was
+       // not updated when encountering a nonexistant interface:
+       ln, err := Listen("tcp", "[fe80::1%"+ti.name+"]:0")
+       if err != nil {
+               t.Fatal(err)
+       }
+       ln.Close()
+       if err := ti.teardown(); err != nil {
+               t.Fatal(err)
+       }
+}