treewide: move //net to //osbase/net
The net package contains the utility to dump a network configuration in
proto format. It should be in osbase.
Change-Id: I4d25d9c7d600f4a04b9b79bd1ba98286bf9daec3
Reviewed-on: https://review.monogon.dev/c/monogon/+/3313
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/osbase/net/dump/BUILD.bazel b/osbase/net/dump/BUILD.bazel
new file mode 100644
index 0000000..3175e4e
--- /dev/null
+++ b/osbase/net/dump/BUILD.bazel
@@ -0,0 +1,16 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "dump",
+ srcs = [
+ "hwaddr_compat.go",
+ "netdump.go",
+ ],
+ importpath = "source.monogon.dev/osbase/net/dump",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//osbase/net/proto",
+ "@com_github_vishvananda_netlink//:netlink",
+ "@org_golang_x_sys//unix",
+ ],
+)
diff --git a/osbase/net/dump/cli/BUILD.bazel b/osbase/net/dump/cli/BUILD.bazel
new file mode 100644
index 0000000..acd4bdb
--- /dev/null
+++ b/osbase/net/dump/cli/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "cli_lib",
+ srcs = ["main.go"],
+ importpath = "source.monogon.dev/osbase/net/dump/cli",
+ visibility = ["//visibility:private"],
+ deps = [
+ "//osbase/net/dump",
+ "@org_golang_google_protobuf//encoding/prototext",
+ ],
+)
+
+go_binary(
+ name = "cli",
+ embed = [":cli_lib"],
+ visibility = ["//visibility:public"],
+)
diff --git a/osbase/net/dump/cli/main.go b/osbase/net/dump/cli/main.go
new file mode 100644
index 0000000..c2de81a
--- /dev/null
+++ b/osbase/net/dump/cli/main.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "google.golang.org/protobuf/encoding/prototext"
+
+ netdump "source.monogon.dev/osbase/net/dump"
+)
+
+func main() {
+ netconf, _, err := netdump.Dump()
+ if err != nil {
+ log.Fatalf("failed to dump network configuration: %v", err)
+ }
+ fmt.Println(prototext.Format(netconf))
+}
diff --git a/osbase/net/dump/hwaddr_compat.go b/osbase/net/dump/hwaddr_compat.go
new file mode 100644
index 0000000..07067bf
--- /dev/null
+++ b/osbase/net/dump/hwaddr_compat.go
@@ -0,0 +1,83 @@
+package netdump
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "runtime"
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// @linux//include/uapi/linux:if.h
+type ifreq struct {
+ ifname [16]byte
+ data uintptr
+}
+
+// @linux//include/uapi/linux:ethtool.h ethtool_perm_addr
+type ethtoolPermAddr struct {
+ Cmd uint32
+ Size uint32
+ // Make this an array for memory layout reasons (see
+ // comment on the kernel struct)
+ Data [32]byte
+}
+
+var errNoPermenentHWAddr = errors.New("no permanent hardware address available")
+
+func isAllZeroes(data []byte) bool {
+ for _, b := range data {
+ if b != 0 {
+ return false
+ }
+ }
+ return true
+}
+
+// Get permanent hardware address on Linux kernels older than 5.6. On newer
+// kernels this is available via normal netlink. Returns errNoPermanentHWAddr
+// in case no such address is available.
+func getPermanentHWAddrLegacy(ifName string) (net.HardwareAddr, error) {
+ fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
+ if err != nil {
+ fd, err = unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_GENERIC)
+ if err != nil {
+ return nil, err
+ }
+ }
+ defer unix.Close(fd)
+
+ var ioctlPins runtime.Pinner
+ defer ioctlPins.Unpin()
+
+ var data ethtoolPermAddr
+ data.Cmd = unix.ETHTOOL_GPERMADDR
+ data.Size = uint32(len(data.Data))
+
+ var req ifreq
+ copy(req.ifname[:], ifName)
+ ioctlPins.Pin(&data)
+ req.data = uintptr(unsafe.Pointer(&data))
+ for {
+ _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.SIOCETHTOOL, uintptr(unsafe.Pointer(&req)))
+ if err == unix.EINTR {
+ continue
+ }
+ if err != 0 {
+ return nil, fmt.Errorf("ioctl(SIOETHTOOL) failed: %w", err)
+ }
+ break
+ }
+ runtime.KeepAlive(req)
+ runtime.KeepAlive(data)
+ // This kernel API is rather old and can indicate the absence of a permanent
+ // hardware MAC in two ways: a size of zero (in case the driver does not
+ // implement a permanent hardware address at all) or an all-zero value in
+ // case the driver has support for returning one but hasn't populated it.
+ if data.Size == 0 || isAllZeroes(data.Data[:data.Size]) {
+ return nil, errNoPermenentHWAddr
+ }
+ return data.Data[:data.Size], nil
+}
diff --git a/osbase/net/dump/netdump.go b/osbase/net/dump/netdump.go
new file mode 100644
index 0000000..a7d8023
--- /dev/null
+++ b/osbase/net/dump/netdump.go
@@ -0,0 +1,255 @@
+package netdump
+
+import (
+ "bytes"
+ "fmt"
+ "math"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+
+ "github.com/vishvananda/netlink"
+ "golang.org/x/sys/unix"
+
+ netapi "source.monogon.dev/osbase/net/proto"
+)
+
+var vlanProtoMap = map[netlink.VlanProtocol]netapi.VLAN_Protocol{
+ netlink.VLAN_PROTOCOL_8021Q: netapi.VLAN_CVLAN,
+ netlink.VLAN_PROTOCOL_8021AD: netapi.VLAN_SVLAN,
+}
+
+// From iproute2's rt_protos
+const (
+ protoUnspec = 0
+ protoKernel = 2
+ protoBoot = 3
+ protoStatic = 4
+)
+
+// 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
+// internet connectivity.
+// The second return value is a list of warnings, i.e. things which might be
+// problematic, but might still result in a working (though less complete)
+// configuration. The Net in the first return value is set to a non-nil value
+// even if there are warnings. The third return value is for a hard error,
+// the Net value will be nil in that case.
+func Dump() (*netapi.Net, []error, error) {
+ var n netapi.Net
+ var warnings []error
+ links, err := netlink.LinkList()
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to list network links: %w", err)
+ }
+ // Map interface index -> interface pointer
+ ifIdxMap := make(map[int]*netapi.Interface)
+ // Map interface index -> names of children
+ ifChildren := make(map[int][]string)
+ // Map interface index -> number of reverse dependencies
+ ifNRevDeps := make(map[int]int)
+ for _, link := range links {
+ linkAttrs := link.Attrs()
+ // Ignore loopback interfaces. The default one will always be
+ // created, and we don't have support for additional loopbacks.
+ if linkAttrs.EncapType == "loopback" {
+ continue
+ }
+ // Gather interface-type-specific data into a netapi interface.
+ var iface netapi.Interface
+ switch l := link.(type) {
+ case *netlink.Device:
+ mac := link.Attrs().PermHardwareAddr
+ if len(mac) == 0 {
+ // Try legacy method for old kernels
+ mac, err = getPermanentHWAddrLegacy(l.Name)
+ // Errors are expected, not all interfaces support this.
+ // If a permanent hardware address could not be obtained, fall
+ // back to the configured hardware address.
+ if err != nil {
+ mac = link.Attrs().HardwareAddr
+ }
+ }
+ iface.Type = &netapi.Interface_Device{Device: &netapi.Device{
+ HardwareAddress: mac.String(),
+ }}
+ case *netlink.Bond:
+ bond := netapi.Bond{
+ MinLinks: int32(l.MinLinks),
+ TransmitHashPolicy: netapi.Bond_TransmitHashPolicy(l.XmitHashPolicy),
+ }
+ switch l.Mode {
+ case netlink.BOND_MODE_802_3AD:
+ lacp := netapi.Bond_LACP{
+ Rate: netapi.Bond_LACP_Rate(l.LacpRate),
+ ActorSystemPriority: int32(l.AdActorSysPrio),
+ UserPortKey: int32(l.AdUserPortKey),
+ SelectionLogic: netapi.Bond_LACP_SelectionLogic(l.AdSelect),
+ }
+ if len(bytes.TrimLeft(l.AdActorSystem, "\x00")) != 0 {
+ lacp.ActorSystemMac = l.AdActorSystem.String()
+ }
+ bond.Mode = &netapi.Bond_Lacp{Lacp: &lacp}
+ default:
+ }
+ iface.Type = &netapi.Interface_Bond{Bond: &bond}
+ case *netlink.Vlan:
+ parentLink, err := netlink.LinkByIndex(l.ParentIndex)
+ if err != nil {
+ warnings = append(warnings, fmt.Errorf("unable to get parent for VLAN interface %q, interface ignored: %w", iface.Name, err))
+ continue
+ }
+ iface.Type = &netapi.Interface_Vlan{Vlan: &netapi.VLAN{
+ Id: int32(l.VlanId),
+ Protocol: vlanProtoMap[l.VlanProtocol],
+ Parent: parentLink.Attrs().Name,
+ }}
+ default:
+ continue
+ }
+ // Append common interface data to netapi interface.
+ iface.Name = linkAttrs.Name
+ iface.Mtu = int32(linkAttrs.MTU)
+ // Collect addresses into interface.
+ addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
+ if err != nil {
+ warnings = append(warnings, fmt.Errorf("unable to get addresses for interface %q, interface ignored: %w", iface.Name, err))
+ continue
+ }
+ for _, a := range addrs {
+ // Ignore IPv6 link-local addresses
+ if a.IP.IsLinkLocalUnicast() && a.IP.To4() == nil {
+ continue
+ }
+ // Sadly it's not possible to reliably determine if a DHCP client is
+ // running. Good clients usually either don't set the permanent flag
+ // and/or a lifetime.
+ if a.Flags&unix.IFA_F_PERMANENT == 0 || (a.ValidLft > 0 && a.ValidLft < math.MaxUint32) {
+ if a.IP.To4() == nil {
+ // Enable IPv6 Autoconfig
+ if iface.Ipv6Autoconfig == nil {
+ iface.Ipv6Autoconfig = &netapi.IPv6Autoconfig{}
+ iface.Ipv6Autoconfig.Privacy, err = getIPv6IfaceAutoconfigPrivacy(linkAttrs.Name)
+ if err != nil {
+ warnings = append(warnings, err)
+ }
+ }
+ } else {
+ if iface.Ipv4Autoconfig == nil {
+ iface.Ipv4Autoconfig = &netapi.IPv4Autoconfig{}
+ }
+ }
+ // Dynamic address, ignore
+ continue
+ }
+ if a.Peer != nil {
+ // Add an interface route for the peer
+ iface.Route = append(iface.Route, &netapi.Interface_Route{
+ Destination: a.Peer.String(),
+ SourceIp: a.IP.String(),
+ })
+ }
+ iface.Address = append(iface.Address, a.IPNet.String())
+ }
+ if linkAttrs.MasterIndex != 0 {
+ ifChildren[linkAttrs.MasterIndex] = append(ifChildren[linkAttrs.MasterIndex], iface.Name)
+ ifNRevDeps[linkAttrs.Index]++
+ }
+ if linkAttrs.ParentIndex != 0 {
+ ifNRevDeps[linkAttrs.ParentIndex]++
+ }
+ ifIdxMap[link.Attrs().Index] = &iface
+ }
+ routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to list routes: %w", err)
+ }
+ // Collect all routes into routes assigned to exact netapi interfaces.
+ for _, r := range routes {
+ if r.Family != netlink.FAMILY_V4 && r.Family != netlink.FAMILY_V6 {
+ continue
+ }
+ var route netapi.Interface_Route
+ // Ignore all dynamic routes
+ if r.Protocol != protoUnspec && r.Protocol != protoBoot &&
+ r.Protocol != protoStatic {
+ continue
+ }
+ if r.LinkIndex == 0 {
+ // Only for "exotic" routes like "unreachable" which are not
+ // necessary for connectivity, skip for now
+ continue
+ }
+ if r.Dst == nil {
+ switch r.Family {
+ case netlink.FAMILY_V4:
+ route.Destination = "0.0.0.0/0"
+ case netlink.FAMILY_V6:
+ route.Destination = "::/0"
+ default:
+ // Switch is complete, all other families get ignored at the start
+ // of the loop.
+ panic("route family changed under us")
+ }
+ } else {
+ route.Destination = r.Dst.String()
+ }
+ if !r.Gw.IsUnspecified() && len(r.Gw) != 0 {
+ route.GatewayIp = r.Gw.String()
+ }
+ if !r.Src.IsUnspecified() && len(r.Src) != 0 {
+ route.SourceIp = r.Src.String()
+ }
+ // Linux calls the metric RTA_PRIORITY even though it behaves as lower-
+ // is-better. Note that RTA_METRICS is NOT the metric.
+ route.Metric = int32(r.Priority)
+ iface, ok := ifIdxMap[r.LinkIndex]
+ if !ok {
+ continue
+ }
+
+ iface.Route = append(iface.Route, &route)
+ }
+ // Finally, gather all interface into a list, filtering out unused ones.
+ for ifIdx, iface := range ifIdxMap {
+ switch i := iface.Type.(type) {
+ case *netapi.Interface_Bond:
+ // Add children here, as now they are all known
+ i.Bond.MemberInterface = ifChildren[ifIdx]
+ case *netapi.Interface_Device:
+ // Drop physical interfaces from the config if they have no IPs and
+ // no reverse dependencies.
+ if len(iface.Address) == 0 && iface.Ipv4Autoconfig == nil &&
+ iface.Ipv6Autoconfig == nil && ifNRevDeps[ifIdx] == 0 {
+ continue
+ }
+ }
+ n.Interface = append(n.Interface, iface)
+ }
+ // Make the output stable
+ sort.Slice(n.Interface, func(i, j int) bool { return n.Interface[i].Name < n.Interface[j].Name })
+ return &n, warnings, nil
+}
+
+func getIPv6IfaceAutoconfigPrivacy(name string) (netapi.IPv6Autoconfig_Privacy, error) {
+ useTempaddrRaw, err := os.ReadFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/use_tempaddr", name))
+ if err != nil {
+ return netapi.IPv6Autoconfig_DISABLE, fmt.Errorf("failed to read use_tempaddr sysctl for interface %q: %w", name, err)
+ }
+ useTempaddr, err := strconv.ParseInt(strings.TrimSpace(string(useTempaddrRaw)), 10, 64)
+ if err != nil {
+ return netapi.IPv6Autoconfig_DISABLE, fmt.Errorf("failed to parse use_tempaddr sysctl for interface %q: %w", name, err)
+ }
+ switch {
+ case useTempaddr <= 0:
+ return netapi.IPv6Autoconfig_DISABLE, nil
+ case useTempaddr == 1:
+ return netapi.IPv6Autoconfig_AVOID, nil
+ case useTempaddr > 1:
+ return netapi.IPv6Autoconfig_PREFER, nil
+ default:
+ panic("switch is complete but hit default case")
+ }
+}
diff --git a/osbase/net/proto/BUILD.bazel b/osbase/net/proto/BUILD.bazel
new file mode 100644
index 0000000..cdc115e
--- /dev/null
+++ b/osbase/net/proto/BUILD.bazel
@@ -0,0 +1,23 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+ name = "net_proto_proto",
+ srcs = ["net.proto"],
+ visibility = ["//visibility:public"],
+)
+
+go_proto_library(
+ name = "net_proto_go_proto",
+ importpath = "source.monogon.dev/osbase/net/proto",
+ proto = ":net_proto_proto",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "proto",
+ embed = [":net_proto_go_proto"],
+ importpath = "source.monogon.dev/osbase/net/proto",
+ visibility = ["//visibility:public"],
+)
diff --git a/osbase/net/proto/gomod-generated-placeholder.go b/osbase/net/proto/gomod-generated-placeholder.go
new file mode 100644
index 0000000..92256db
--- /dev/null
+++ b/osbase/net/proto/gomod-generated-placeholder.go
@@ -0,0 +1 @@
+package proto
diff --git a/osbase/net/proto/net.proto b/osbase/net/proto/net.proto
new file mode 100644
index 0000000..1fd46aa
--- /dev/null
+++ b/osbase/net/proto/net.proto
@@ -0,0 +1,216 @@
+syntax = "proto3";
+
+// This package provides a configuration format for configuring IP-based
+// networking on Linux. This is going to be used in cases where automatic
+// configuration by Monogon's network stack is infeasible or network
+// configuration information needs to be provided to non-Monogon systems.
+// It's kept human-readable as it may be written by humans directly when
+// configuring Monogon systems.
+package osbase.net.proto;
+option go_package = "source.monogon.dev/osbase/net/proto";
+
+// Device references one or more network adapters, i.e. network devices which
+// connect this kernel to an outside system.
+// All conditions which are set in the message are ANDed together.
+message Device {
+ // Matches the permanent hardware address of the interface. The raw address
+ // is hex-encoded and colons are inserted between every byte boundary.
+ // This is the MAC address on Ethernet interfaces.
+ string hardware_address = 1;
+ // Matches the Linux driver of the network interface
+ string driver = 2;
+ // In case of multiple matches, use the n-th interface instead of the first.
+ int32 index = 3;
+}
+// Bond defines an aggregate of physical layer 2 links which behave as one
+// virtual layer 2 link. This includes active-passive as well as active-
+// active configurations with two or more links.
+message Bond {
+ // List of interface names which are a member of this bond. It's recommended
+ // to only use Device-type interfaces here as others might behave
+ // unexpectedly (Bond on VLAN interfaces) or fail to be configured entirely
+ // (Bond on Bond). All interface names listed here must exist as part of the
+ // same Net message as this Bond interface.
+ repeated string member_interface = 1;
+ // Minimum number of links to be up to consider the bond to be up.
+ // Can be used in case expected bandwith is more than a single interface
+ // can take in which case it might be preferable to not pass any traffic
+ // over causing significant packet loss.
+ int32 min_links = 2;
+ message CarrierMonitor {
+ // Interval at which the PCS is polled for physical link status if Linux's
+ // carrier monitoring is not available.
+ int32 polling_interval_ms = 1;
+ // Disable the use of Linux's carrier monitoring which can use interrupts
+ // and force polling in all cases.
+ bool force_polling = 4;
+ // Amount of time to delay marking the link as down in the bond after the
+ // carrier has been lost. Should be a multiple of polling_interval_ms.
+ int32 down_delay_ms = 2;
+ // Amount of time to delay marking the link as up in the bond after the
+ // carrier is available. Should be a multiple of polling_interval_ms.
+ int32 up_delay_ms = 3;
+ }
+ oneof link_monitor {
+ CarrierMonitor carrier_monitor = 3;
+ // TODO(#186): Support ARP monitor for other modes
+ }
+ enum TransmitHashPolicy {
+ // Layer 2 MAC address
+ LAYER2 = 0;
+ // IP address, protocol and port
+ LAYER3_4 = 1;
+ // MAC address and IP address
+ LAYER2_3 = 2;
+ // Encapsulated MAC address and IP address
+ ENCAP_LAYER2_3 = 3;
+ // Encapsulated IP address, protocol and port
+ ENCAP_LAYER3_4 = 4;
+ // VLAN ID and source MAC
+ VLAN_SRCMAC = 5;
+ }
+ TransmitHashPolicy transmit_hash_policy = 4;
+ // Use the Link Aggregation Control Protocol to automatically use the
+ // available links as best as possible.
+ message LACP {
+ enum Rate {
+ // LACP slow rate, one packet every 30s
+ SLOW = 0;
+ // LACP fast rate, one packet every 1s
+ FAST = 1;
+ }
+ Rate rate = 1;
+ enum SelectionLogic {
+ STABLE = 0;
+ BANDWIDTH = 1;
+ COUNT = 2;
+ }
+ SelectionLogic selection_logic = 2;
+ int32 actor_system_priority = 3;
+ int32 user_port_key = 4;
+ string actor_system_mac = 5;
+ }
+ message ActiveBackup {
+ // TODO(#186): More settings
+ }
+ oneof mode {
+ LACP lacp = 5;
+ ActiveBackup active_backup = 6;
+ }
+}
+
+message VLAN {
+ // Name of the parent interface passing tagged packets. The interface
+ // referenced here must exist in the same Net message as this VLAN
+ // interface.
+ string parent = 1;
+ // VLAN ID (1-4094)
+ int32 id = 2;
+ enum Protocol {
+ // C-VLAN, also known as "standard" VLAN inserts a header with the
+ // VLAN ID (VID) right before the EtherType.
+ CVLAN = 0;
+ // S-VLAN, also known as QinQ or 802.1ad (obsolete) inserts a second VLAN ID
+ // before the C-VLAN header. This allows stacking two VLANs. The ID
+ // specified here is just for the outer VLAN, the inner one can be set by
+ // creating another VLAN interface and setting this one to be its parent.
+ SVLAN = 1;
+ }
+ Protocol protocol = 3;
+}
+
+// IPv4Autoconfig contains settings for the automatic configuration of IPv4
+// addresses, routes and further network information via DHCPv4.
+message IPv4Autoconfig {}
+
+// IPv6Autoconfig contains settings for the automatic configuration of IPv6
+// addreses, routes and further network information via ICMPv6 Router
+// Advertisements and optionally DHCPv6 if indicated by the Router
+// Advertisement.
+message IPv6Autoconfig {
+ enum Privacy {
+ // Do not generate privacy addresses.
+ DISABLE = 0;
+ // Generate privacy addresses, but prefer non-privacy addresses.
+ AVOID = 1;
+ // Generate privacy addresses and use them over other non-privacy
+ // addresses.
+ PREFER = 2;
+ }
+ // privacy controls if and how privacy addresses (see RFC 4941) are used if
+ // DHCPv6 is not used for addressing. If DHCPv6 is used for addressing
+ // any privacy considerations lie with the DHCPv6 server.
+ Privacy privacy = 1;
+}
+
+message Interface {
+ // Name of the interface. Used as a reference in this config as well as for
+ // the name of the kernel interface. Must not be empty, less than 16 UTF-8
+ // bytes long and cannot contain spaces, forward slashes, colons or percent
+ // signs. The UTF-8 encoding can also not include 0xa0 which is interpreted
+ // as a space by Linux since its ctype database is based on Latin1.
+ string name = 1;
+
+ // Type of interface
+ oneof type {
+ Device device = 3;
+ Bond bond = 4;
+ VLAN vlan = 5;
+ }
+ // Enable automatic IPv4 network configuration via DHCPv4.
+ IPv4Autoconfig ipv4_autoconfig = 10;
+
+ // Enable automatic IPv6 network configuration via router advertisements and
+ // DHCPv6.
+ IPv6Autoconfig ipv6_autoconfig = 11;
+
+ // IP addresses to be statically configured. These can either be single
+ // IP addresses (both IPv4 and IPv6) as well as CIDR-style networks for
+ // which a corresponding route is automatically added. If single IP addreses
+ // are used, a corresponding route must be added, otherwise no traffic will
+ // be routed out of the interface.
+ repeated string address = 12;
+
+ message Route {
+ // Destination in CIDR form or as a single IP.
+ string destination = 1;
+
+ // If set, the destination network is not directly on-link, but reachable
+ // via a gateway which is on-link. On point-to-point networks without
+ // ARP/NDP this doesn't do anything should never be set.
+ // Note that here, different from other network configs, the gateway IP (if
+ // configured) is assumed to be on-link for the interface it's configured
+ // under. Configuring a route with a gateway IP which is routed to another
+ // interface is invalid.
+ string gateway_ip = 2;
+
+ // An optional hint to the kernel which source address to prefer when using
+ // this route.
+ string source_ip = 3;
+
+ // Metric of this interface route. A lower metric route wins over one with a
+ // higher metric. If unset, defaults to 0 which is the default metric in
+ // Linux.
+ int32 metric = 4;
+ }
+ // List of routes which direct traffic into this interface.
+ repeated Route route = 14;
+
+ // Maximum transmission unit of the interface. If unset it will be
+ // automatically configured by DHCP or LLDP or left at the interface default
+ // value. Minimum value is 1280 bytes as required by IPv6.
+ int32 mtu = 13;
+}
+
+message Nameserver {
+ // The IP address of the nameserver in string form.
+ string ip = 1;
+}
+
+// Net contains a network configuration for a single network namespace.
+//
+// This is effectively the top-level configuration message for a machine.
+message Net {
+ repeated Interface interface = 1;
+ repeated Nameserver nameserver = 3;
+}