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
}
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 {
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))
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))
}
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)
}
package net
import (
+ "errors"
"fmt"
"os/exec"
"runtime"
})
return nil
}
+
+func (ti *testInterface) setLinkLocal(suffix int) error {
+ return errors.New("not yet implemented for BSD")
+}
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")
}
}
}
+
+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)
+ }
+}