osbase/net/dump: correct address prefixes if not on-link

Currently if an interface address has an on-link prefix (i.e. is not a
/32 or /128) this gets automatically added as a route. Certain
in-the-wild configs have these but also a route with a gateway, making
the prefix not on-link. Fix the interface addresses in these cases to a
single IP to avoid the spurious on-link route.

Change-Id: If601c61cbbab7f05e72c7f4908071def2dcdb44b
Reviewed-on: https://review.monogon.dev/c/monogon/+/3771
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/net/dump/BUILD.bazel b/osbase/net/dump/BUILD.bazel
index 3175e4e..a3e38de 100644
--- a/osbase/net/dump/BUILD.bazel
+++ b/osbase/net/dump/BUILD.bazel
@@ -11,6 +11,7 @@
     deps = [
         "//osbase/net/proto",
         "@com_github_vishvananda_netlink//:netlink",
+        "@org_go4_netipx//:netipx",
         "@org_golang_x_sys//unix",
     ],
 )
diff --git a/osbase/net/dump/netdump.go b/osbase/net/dump/netdump.go
index c3bf9f7..7ea9ae2 100644
--- a/osbase/net/dump/netdump.go
+++ b/osbase/net/dump/netdump.go
@@ -4,12 +4,14 @@
 	"bytes"
 	"fmt"
 	"math"
+	"net/netip"
 	"os"
 	"sort"
 	"strconv"
 	"strings"
 
 	"github.com/vishvananda/netlink"
+	"go4.org/netipx"
 	"golang.org/x/sys/unix"
 
 	netapi "source.monogon.dev/osbase/net/proto"
@@ -28,6 +30,11 @@
 	protoStatic = 4
 )
 
+type ifaceAddrRef struct {
+	iface   *netapi.Interface
+	addrIdx int
+}
+
 // Dump dumps the network configuration of the current network namespace into
 // a osbase.net.proto.Net structure. This is currently only expected to work for
 // systems which do not use a dynamic routing protocol to establish basic
@@ -48,6 +55,8 @@
 	ifIdxMap := make(map[int]*netapi.Interface)
 	// Map interface index -> names of children
 	ifChildren := make(map[int][]string)
+	// Interface address implied on-link routes
+	impliedOnLinkRoutes := make(map[netip.Prefix]ifaceAddrRef)
 	// Map interface index -> number of reverse dependencies
 	ifNRevDeps := make(map[int]int)
 	for _, link := range links {
@@ -151,7 +160,17 @@
 					SourceIp:    a.IP.String(),
 				})
 			}
-			iface.Address = append(iface.Address, a.IPNet.String())
+			ones, bits := a.Mask.Size()
+			baseAddr, ok := netip.AddrFromSlice(a.IP.Mask(a.Mask))
+			prefix := netip.PrefixFrom(baseAddr, ones)
+			if ok && bits != 0 {
+				if !prefix.IsSingleIP() {
+					impliedOnLinkRoutes[prefix] = ifaceAddrRef{iface: &iface, addrIdx: len(iface.Address)}
+				}
+				iface.Address = append(iface.Address, a.IPNet.String())
+			} else {
+				warnings = append(warnings, fmt.Errorf("address %v on %q is invalid, ignoring", a.IPNet, iface.Name))
+			}
 		}
 		if linkAttrs.MasterIndex != 0 {
 			ifChildren[linkAttrs.MasterIndex] = append(ifChildren[linkAttrs.MasterIndex], iface.Name)
@@ -194,6 +213,16 @@
 				panic("route family changed under us")
 			}
 		} else {
+			dst, ok := netipx.FromStdIPNet(r.Dst)
+			if !ok {
+				warnings = append(warnings, fmt.Errorf("route %v invalid, ignoring", r.Dst))
+			}
+			if ref, ok := impliedOnLinkRoutes[dst]; ok && !r.Gw.IsUnspecified() && len(r.Gw) != 0 {
+				// Address is not on-link, remove prefix from address to not
+				// get an improper on-link route.
+				prefix := netip.MustParsePrefix(ref.iface.Address[ref.addrIdx])
+				ref.iface.Address[ref.addrIdx] = netip.PrefixFrom(prefix.Addr(), prefix.Addr().BitLen()).String()
+			}
 			route.Destination = r.Dst.String()
 		}
 		if !r.Gw.IsUnspecified() && len(r.Gw) != 0 {