route: retry FetchRIB a few times in case data grows

Fixes golang/go#45736

Change-Id: I7e7926ba1a0ff752ba914429c5886ff2be9801a8
Reviewed-on: https://go-review.googlesource.com/c/net/+/313649
Trust: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/route/route.go b/route/route.go
index e3d6da0..fd0019e 100644
--- a/route/route.go
+++ b/route/route.go
@@ -108,17 +108,28 @@
 // an interface index or a set of interface flags. In most cases, zero
 // means a wildcard.
 func FetchRIB(af int, typ RIBType, arg int) ([]byte, error) {
-	mib := [6]int32{sysCTL_NET, sysAF_ROUTE, 0, int32(af), int32(typ), int32(arg)}
-	n := uintptr(0)
-	if err := sysctl(mib[:], nil, &n, nil, 0); err != nil {
-		return nil, os.NewSyscallError("sysctl", err)
+	try := 0
+	for {
+		try++
+		mib := [6]int32{sysCTL_NET, sysAF_ROUTE, 0, int32(af), int32(typ), int32(arg)}
+		n := uintptr(0)
+		if err := sysctl(mib[:], nil, &n, nil, 0); err != nil {
+			return nil, os.NewSyscallError("sysctl", err)
+		}
+		if n == 0 {
+			return nil, nil
+		}
+		b := make([]byte, n)
+		if err := sysctl(mib[:], &b[0], &n, nil, 0); err != nil {
+			// If the sysctl failed because the data got larger
+			// between the two sysctl calls, try a few times
+			// before failing. (golang.org/issue/45736).
+			const maxTries = 3
+			if err == syscall.ENOMEM && try < maxTries {
+				continue
+			}
+			return nil, os.NewSyscallError("sysctl", err)
+		}
+		return b[:n], nil
 	}
-	if n == 0 {
-		return nil, nil
-	}
-	b := make([]byte, n)
-	if err := sysctl(mib[:], &b[0], &n, nil, 0); err != nil {
-		return nil, os.NewSyscallError("sysctl", err)
-	}
-	return b[:n], nil
 }
diff --git a/route/route_test.go b/route/route_test.go
index 2693269..27f05b8 100644
--- a/route/route_test.go
+++ b/route/route_test.go
@@ -11,7 +11,6 @@
 	"fmt"
 	"os/exec"
 	"runtime"
-	"time"
 )
 
 func (m *RouteMessage) String() string {
@@ -311,15 +310,7 @@
 }
 
 func fetchAndParseRIB(af int, typ RIBType) ([]Message, error) {
-	var err error
-	var b []byte
-	for i := 0; i < 3; i++ {
-		if b, err = FetchRIB(af, typ, 0); err != nil {
-			time.Sleep(10 * time.Millisecond)
-			continue
-		}
-		break
-	}
+	b, err := FetchRIB(af, typ, 0)
 	if err != nil {
 		return nil, fmt.Errorf("%v %d %v", addrFamily(af), typ, err)
 	}