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
import (
"encoding/json"
"fmt"
"net"
"net/url"
"github.com/vishvananda/netlink"
)
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")
const (
Null = "null"
)
// IPAddrMode sets the method for network setup.
IPUnset IPAddrMode = iota
IPStatic
IPDynamic
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())
}
// UnmarshalJSON implements json.Unmarshaler.
func (i *IPAddrMode) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
"static": IPStatic,
"dhcp": IPDynamic,
}
if !ok {
return &json.UnmarshalTypeError{
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// 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())
}
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *BondingMode) UnmarshalJSON(data []byte) error {
*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)
return nil
}
// Config stores host specific configuration.
type Config struct {
IPAddrMode *IPAddrMode `json:"network_mode"`
HostIP *netlink.Addr `json:"host_ip"`
DefaultGateway *net.IP `json:"gateway"`
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"`
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,
return json.Marshal(alias)
// UnmarshalJSON implements json.Unmarshaler.
// 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()
Jens Drenhaus
committed
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{}
return fmt.Errorf("unmarshal host config: %w", err)
Jens Drenhaus
committed
}
func (c *Config) validate() error {
var validationSet = []func(*Config) error{
checkIPAddrMode,
checkHostIP,
checkGateway,
checkNetworkInterfaces,
checkOSPkgPointer,
}
for _, f := range validationSet {
return err
}
}
return nil
}
func checkIPAddrMode(cfg *Config) error {
if cfg.IPAddrMode == nil || *cfg.IPAddrMode == IPUnset {
return ErrMissingIPAddrMode
}
return nil
}
func checkHostIP(cfg *Config) error {
if *cfg.IPAddrMode == IPStatic && cfg.HostIP == nil {
return ErrMissingIPAddr
}
return nil
}
func checkGateway(cfg *Config) error {
if *cfg.IPAddrMode == IPStatic && cfg.DefaultGateway == nil {
return ErrMissingGateway
}
return nil
}
func checkNetworkInterfaces(cfg *Config) error {
if cfg.NetworkInterfaces != nil {
if len(*cfg.NetworkInterfaces) == 0 {
return ErrEmptyNetworkInterfaces
}
}
return nil
}
func checkOSPkgPointer(cfg *Config) error {
if cfg.OSPkgPointer == nil {
return ErrMissingOSPkgPointer
} else if *cfg.OSPkgPointer == "" {
return ErrMissingOSPkgPointer
}
return nil
}
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
}
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
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) {
}
func (n *netlinkAddr) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
if err != nil {
return &json.UnmarshalTypeError{
Value: fmt.Sprintf("string %q", str),
}
}
return nil
}
type netIP net.IP
func (n netIP) MarshalJSON() ([]byte, error) {
}
func (n *netIP) UnmarshalJSON(data []byte) error {
*n = nil
} else {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
return &json.UnmarshalTypeError{
Value: fmt.Sprintf("string %q", str),
}
return nil
}
type netHardwareAddr net.HardwareAddr
func (n netHardwareAddr) MarshalJSON() ([]byte, error) {
}
func (n *netHardwareAddr) UnmarshalJSON(data []byte) error {
*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) {
}
func (u *urlURL) UnmarshalJSON(data []byte) error {
*u = urlURL{}
} else {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
if err != nil {
return &json.UnmarshalTypeError{
Value: fmt.Sprintf("string %q", str),
return nil
}