Skip to content
Snippets Groups Projects
config.go 11.7 KiB
Newer Older
// Copyright 2021 the System Transparency Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package host exposes functionality to interact with the host mashine.
package host
var (
	ErrMissingIPAddrMode        = errors.New("field IP address mode must be set")
	ErrMissingBondName          = errors.New("bond name must be set")
	ErrInvalidBondMode          = errors.New("bond mode is unknown")
	ErrMissingNetworkInterfaces = errors.New("one or more network interfaces must be set")
	ErrEmptyNetworkInterfaces   = errors.New("network interfaces are set but empty")
	ErrMissingOSPkgPointer      = errors.New("missing OS package pointer")
	ErrMissingIPAddr            = errors.New("field IP address must not be empty when static IP mode is set")
	ErrMissingGateway           = errors.New("default gateway must not be empty when static IP mode is set")
// IPAddrMode sets the method for network setup.
	IPUnset IPAddrMode = iota
	IPStatic
	IPDynamic
// String implements fmt.Stringer.
func (i IPAddrMode) String() string {
	return [...]string{"unset", "static", "dhcp"}[i]
// MarshalJSON implements json.Marshaler.
func (i IPAddrMode) MarshalJSON() ([]byte, error) {
	if i != IPUnset {
		return json.Marshal(i.String())
	}
	return []byte(Null), nil
// UnmarshalJSON implements json.Unmarshaler.
func (i *IPAddrMode) UnmarshalJSON(data []byte) error {
	if string(data) == Null {
		*i = IPUnset
	} else {
Jens Drenhaus's avatar
Jens Drenhaus committed
		var str string
		if err := json.Unmarshal(data, &str); err != nil {
Jens Drenhaus's avatar
Jens Drenhaus committed
		toID := map[string]IPAddrMode{
			"static": IPStatic,
			"dhcp":   IPDynamic,
		}
Jens Drenhaus's avatar
Jens Drenhaus committed
		mode, ok := toID[str]
		if !ok {
			return &json.UnmarshalTypeError{
Jens Drenhaus's avatar
Jens Drenhaus committed
				Value: fmt.Sprintf("string %q", str),
				Type:  reflect.TypeOf(i),
			}
// BondingMode sets the mode for bonding.
type BondingMode int

const (
	BondingUnset BondingMode = iota
	BondingBalanceRR
	BondingActiveBackup
	BondingBalanceXOR
	BondingBroadcast
	Bonding8023AD
	BondingBalanceTLB
	BondingBalanceALB
	BondingUnknown
)

func StringToBondingMode(str string) BondingMode {
	bondString := map[string]BondingMode{
		"":              BondingUnset,
		"balance-rr":    BondingBalanceRR,
		"active-backup": BondingActiveBackup,
		"balance-xor":   BondingBalanceXOR,
		"broadcast":     BondingBroadcast,
		"802.3ad":       Bonding8023AD,
		"balance-tlb":   BondingBalanceTLB,
		"balance-alb":   BondingBalanceALB,
	}

	if mode, ok := bondString[str]; ok {
		return mode
	}

	return BondingUnknown
}

// String implements fmt.Stringer.
func (b BondingMode) String() string {
	return [...]string{"",
		"balance-rr",
		"active-backup",
		"balance-xor",
		"broadcast",
		"802.3ad",
		"balance-tlb",
		"balance-alb"}[b]
}

// MarshalJSON implements json.Marshaler.
func (b BondingMode) MarshalJSON() ([]byte, error) {
	if b != BondingUnset {
		return json.Marshal(b.String())
	}

	return []byte(Null), nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (b *BondingMode) UnmarshalJSON(data []byte) error {
	if string(data) == Null {
		*b = BondingUnset
	} else {
		var str string
		if err := json.Unmarshal(data, &str); err != nil {
			return err
		}

		mode := StringToBondingMode(str)
		if mode == BondingUnknown {
			return &json.UnmarshalTypeError{
				Value: fmt.Sprintf("string %q", str),
				Type:  reflect.TypeOf(b),
			}
		}
		*b = mode
	}

	return nil
}

type NetworkInterface struct {
	InterfaceName *string           `json:"interface_name"`
	MACAddress    *net.HardwareAddr `json:"mac_address"`
}

func (n NetworkInterface) MarshalJSON() ([]byte, error) {
	// It is a bit loose to permit both fields to be unset.  If our codebase
	// stopped calling json.Marshal() in random places or if we don't support
	// backwards-compatible "network_interfaces", then we can be stricter here.
	return json.Marshal(struct {
		InterfaceName *string          `json:"interface_name"`
		MACAddress    *netHardwareAddr `json:"mac_address"`
		InterfaceName: n.InterfaceName,
		MACAddress:    (*netHardwareAddr)(n.MACAddress),
	})
}

func (n *NetworkInterface) UnmarshalJSON(data []byte) error {
	aux := struct {
		InterfaceName *string          `json:"interface_name"`
		MACAddress    *netHardwareAddr `json:"mac_address"`
	}{}
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}
	if aux.InterfaceName == nil || aux.MACAddress == nil {
		return fmt.Errorf("all network interface fields are required: have %v", aux)
	n.InterfaceName = aux.InterfaceName
	n.MACAddress = (*net.HardwareAddr)(aux.MACAddress)
// Config stores host specific configuration.
type Config struct {
	IPAddrMode        *IPAddrMode          `json:"network_mode"`
	HostIP            *netlink.Addr        `json:"host_ip"`
	DefaultGateway    *net.IP              `json:"gateway"`
	DNSServer         *[]*net.IP           `json:"dns"`
	NetworkInterfaces *[]*NetworkInterface `json:"network_interfaces"`
	OSPkgPointer      *string              `json:"ospkg_pointer"`
	BondingMode       BondingMode          `json:"bonding_mode"`
	BondName          *string              `json:"bond_name"`
	Description       *string              `json:"description,omitempty"`
type config struct {
	IPAddrMode        *IPAddrMode              `json:"network_mode"`
	HostIP            *netlinkAddr             `json:"host_ip"`
	DefaultGateway    *netIP                   `json:"gateway"`
	DNSServer         *compatDNSServers        `json:"dns"`
	NetworkInterfaces *compatNetworkInterfaces `json:"network_interfaces"`
	OSPkgPointer      *string                  `json:"ospkg_pointer"`
	BondingMode       BondingMode              `json:"bonding_mode"`
	BondName          *string                  `json:"bond_name"`
	Description       *string                  `json:"description,omitempty"`

	// Parse obsolete fields in order to offer backwards-compatibility.  The
	// below uses omitempty to not output explicit nulls on obsolete fields.
	// See git-commit 9b8fc85fd7f041663c7a76b9ffe211375ef96b1b (URLs).
	// See git-commit c3ccf5555caa09578100b0a6f3c71b28f74e1da0 (nic).
	CompatProvisiongURLs   []string        `json:"provisioning_urls,omitempty"`
	CompatNetworkInterface netHardwareAddr `json:"network_interface,omitempty"`
// MarshalJSON implements json.Marshaler.
func (c Config) MarshalJSON() ([]byte, error) {
	alias := config{
		IPAddrMode:        c.IPAddrMode,
		HostIP:            (*netlinkAddr)(c.HostIP),
		DefaultGateway:    (*netIP)(c.DefaultGateway),
		DNSServer:         (*compatDNSServers)(ips2alias(c.DNSServer)),
		OSPkgPointer:      c.OSPkgPointer,
		NetworkInterfaces: (*compatNetworkInterfaces)(c.NetworkInterfaces),
		BondingMode:       c.BondingMode,
		BondName:          c.BondName,
		Description:       c.Description,
// UnmarshalJSON implements json.Unmarshaler.
Jens Drenhaus's avatar
Jens Drenhaus committed
//
// All fields of Config need to be present in JSON.
func (c *Config) UnmarshalJSON(data []byte) error {
	alias := config{}
	if err := json.Unmarshal(data, &alias); err != nil {
	if err := alias.maybeCompatProvisioningURLs(); err != nil {
		return err
	}
	alias.maybeCompatNetworkInterface()
	c.IPAddrMode = alias.IPAddrMode
	c.HostIP = (*netlink.Addr)(alias.HostIP)
	c.DefaultGateway = (*net.IP)(alias.DefaultGateway)
	c.DNSServer = alias2ips((*[]*netIP)(alias.DNSServer))
	c.OSPkgPointer = alias.OSPkgPointer
	c.NetworkInterfaces = (*[]*NetworkInterface)(alias.NetworkInterfaces)
	c.BondingMode = alias.BondingMode
	c.BondName = alias.BondName
	c.Description = alias.Description

	if err := c.validate(); err != nil {
		*c = Config{}
Jens Drenhaus's avatar
Jens Drenhaus committed

		return fmt.Errorf("unmarshal host config: %w", err)
func (c *Config) validate() error {
	var validationSet = []func(*Config) error{
		checkIPAddrMode,
		checkHostIP,
		checkGateway,
		checkNetworkInterfaces,
	}

	for _, f := range validationSet {
		if err := f(c); err != nil {
func checkIPAddrMode(cfg *Config) error {
	if cfg.IPAddrMode == nil || *cfg.IPAddrMode == IPUnset {
func checkHostIP(cfg *Config) error {
	if *cfg.IPAddrMode == IPStatic && cfg.HostIP == nil {
func checkGateway(cfg *Config) error {
	if *cfg.IPAddrMode == IPStatic && cfg.DefaultGateway == nil {
func checkNetworkInterfaces(cfg *Config) error {
	if cfg.NetworkInterfaces != nil {
		if len(*cfg.NetworkInterfaces) == 0 {
			return ErrEmptyNetworkInterfaces
		}
		return cfg.checkCompatNetworkInterfaces()
func checkOSPkgPointer(cfg *Config) error {
	if cfg.OSPkgPointer == nil {
		return ErrMissingOSPkgPointer
	} else if *cfg.OSPkgPointer == "" {
		return ErrMissingOSPkgPointer
func checkBonding(cfg *Config) error {
	if cfg.BondingMode == BondingUnset {
		return nil
	}

	if cfg.BondingMode == BondingUnknown {
		return ErrInvalidBondMode
	}

	if cfg.BondName == nil || *cfg.BondName == "" {
		return ErrMissingBondName
	}

	if cfg.NetworkInterfaces == nil || len(*cfg.NetworkInterfaces) == 0 {
		return ErrMissingNetworkInterfaces
	}

	return nil
}

func ips2alias(input *[]*net.IP) *[]*netIP {
	if input == nil {
		return nil
	}

	ret := make([]*netIP, len(*input))
	for i := range ret {
		if (*input)[i] != nil {
			u := *(*input)[i]
			cast := netIP(u)
			ret[i] = &cast
		}
	}

	return &ret
}

func alias2ips(input *[]*netIP) *[]*net.IP {
	if input == nil {
		return nil
	}

	ret := make([]*net.IP, len(*input))
	for i := range ret {
		if (*input)[i] != nil {
			u := *(*input)[i]
			cast := net.IP(u)
			ret[i] = &cast
		}
	}

	return &ret
}

type netlinkAddr netlink.Addr

func (n netlinkAddr) MarshalJSON() ([]byte, error) {
Jens Drenhaus's avatar
Jens Drenhaus committed
	return json.Marshal(netlink.Addr(n).String())
}

func (n *netlinkAddr) UnmarshalJSON(data []byte) error {
	var str string
	if err := json.Unmarshal(data, &str); err != nil {
		return err
	}
Jens Drenhaus's avatar
Jens Drenhaus committed

	ipAddr, err := netlink.ParseAddr(str)
	if err != nil {
		return &json.UnmarshalTypeError{
			Value: fmt.Sprintf("string %q", str),
Jens Drenhaus's avatar
Jens Drenhaus committed
			Type:  reflect.TypeOf(ipAddr),
Jens Drenhaus's avatar
Jens Drenhaus committed

	*n = netlinkAddr(*ipAddr)

	return nil
}

type netIP net.IP

func (n netIP) MarshalJSON() ([]byte, error) {
Jens Drenhaus's avatar
Jens Drenhaus committed
	return json.Marshal(net.IP(n).String())
}

func (n *netIP) UnmarshalJSON(data []byte) error {
	if string(data) == Null {
		*n = nil
	} else {
		var str string
		if err := json.Unmarshal(data, &str); err != nil {
			return err
Jens Drenhaus's avatar
Jens Drenhaus committed
		ipAddr := net.ParseIP(str)
		if ipAddr == nil {
			return &json.UnmarshalTypeError{
				Value: fmt.Sprintf("string %q", str),
Jens Drenhaus's avatar
Jens Drenhaus committed
				Type:  reflect.TypeOf(ipAddr),
Jens Drenhaus's avatar
Jens Drenhaus committed
		*n = netIP(ipAddr)
	}

	return nil
}

type netHardwareAddr net.HardwareAddr

func (n netHardwareAddr) MarshalJSON() ([]byte, error) {
Jens Drenhaus's avatar
Jens Drenhaus committed
	return json.Marshal(net.HardwareAddr(n).String())
}

func (n *netHardwareAddr) UnmarshalJSON(data []byte) error {
	if string(data) == Null {
		*n = nil
	} else {
		var str string
		if err := json.Unmarshal(data, &str); err != nil {
			return err
		hwa, err := net.ParseMAC(str)
		if err != nil {
			return &json.UnmarshalTypeError{
				Value: fmt.Sprintf("string %q", str),
				Type:  reflect.TypeOf(hwa),
			}
		}
		*n = netHardwareAddr(hwa)
	}

	return nil
}

type urlURL url.URL

func (u urlURL) MarshalJSON() ([]byte, error) {
Jens Drenhaus's avatar
Jens Drenhaus committed
	return json.Marshal((*url.URL)(&u).String())
}

func (u *urlURL) UnmarshalJSON(data []byte) error {
	if string(data) == Null {
		*u = urlURL{}
	} else {
		var str string
		if err := json.Unmarshal(data, &str); err != nil {
			return err
		}
Jens Drenhaus's avatar
Jens Drenhaus committed
		uri, err := url.ParseRequestURI(str)
		if err != nil {
			return &json.UnmarshalTypeError{
				Value: fmt.Sprintf("string %q", str),
Jens Drenhaus's avatar
Jens Drenhaus committed
				Type:  reflect.TypeOf(uri),
Jens Drenhaus's avatar
Jens Drenhaus committed
		*u = urlURL(*uri)