github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/installer/configureLocalNetwork.go (about)

     1  // +build linux
     2  
     3  package main
     4  
     5  import (
     6  	"bytes"
     7  	"errors"
     8  	"io"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    16  	"github.com/Cloud-Foundations/Dominator/lib/json"
    17  	"github.com/Cloud-Foundations/Dominator/lib/log"
    18  	libnet "github.com/Cloud-Foundations/Dominator/lib/net"
    19  	"github.com/Cloud-Foundations/Dominator/lib/net/configurator"
    20  	fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager"
    21  	hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    22  	"github.com/d2g/dhcp4"
    23  	"github.com/d2g/dhcp4client"
    24  	"github.com/pin/tftp"
    25  )
    26  
    27  var (
    28  	tftpFiles = []string{
    29  		"config.json",
    30  		"imagename",
    31  		"imageserver",
    32  		"storage-layout.json",
    33  	}
    34  	zeroIP = net.IP(make([]byte, 4))
    35  )
    36  
    37  func configureLocalNetwork(logger log.DebugLogger) (
    38  	*fm_proto.GetMachineInfoResponse, map[string]net.Interface, error) {
    39  	if err := run("ifconfig", "", logger, "lo", "up"); err != nil {
    40  		return nil, nil, err
    41  	}
    42  	_, interfaces, err := libnet.ListBroadcastInterfaces(
    43  		libnet.InterfaceTypeEtherNet, logger)
    44  	if err != nil {
    45  		return nil, nil, err
    46  	}
    47  	// Raise interfaces so that by the time the OS is installed link status
    48  	// should be stable. This is how we discover connected interfaces.
    49  	if err := raiseInterfaces(interfaces, logger); err != nil {
    50  		return nil, nil, err
    51  	}
    52  	machineInfo, err := getConfiguration(interfaces, logger)
    53  	if err != nil {
    54  		return nil, nil, err
    55  	}
    56  	return machineInfo, interfaces, nil
    57  }
    58  
    59  func dhcpRequest(interfaces map[string]net.Interface,
    60  	logger log.DebugLogger) (string, dhcp4.Packet, error) {
    61  	clients := make(map[string]*dhcp4client.Client, len(interfaces))
    62  	for name, iface := range interfaces {
    63  		packetSocket, err := dhcp4client.NewPacketSock(iface.Index)
    64  		if err != nil {
    65  			return "", nil, err
    66  		}
    67  		defer packetSocket.Close()
    68  		client, err := dhcp4client.New(
    69  			dhcp4client.HardwareAddr(iface.HardwareAddr),
    70  			dhcp4client.Connection(packetSocket),
    71  			dhcp4client.Timeout(time.Second*5))
    72  		if err != nil {
    73  			return "", nil, err
    74  		}
    75  		defer client.Close()
    76  		clients[name] = client
    77  	}
    78  	stopTime := time.Now().Add(time.Minute * 5)
    79  	for ; time.Until(stopTime) > 0; time.Sleep(time.Second) {
    80  		for name, client := range clients {
    81  			if libnet.TestCarrier(name) {
    82  				logger.Debugf(1, "%s: DHCP attempt\n", name)
    83  				if ok, packet, err := client.Request(); err != nil {
    84  					logger.Debugf(1, "%s: DHCP failed: %s\n", name, err)
    85  				} else if ok {
    86  					return name, packet, nil
    87  				}
    88  			}
    89  		}
    90  	}
    91  	return "", nil, errors.New("timed out waiting for DHCP")
    92  }
    93  
    94  func findInterfaceToConfigure(interfaces map[string]net.Interface,
    95  	machineInfo fm_proto.GetMachineInfoResponse, logger log.DebugLogger) (
    96  	net.Interface, net.IP, *hyper_proto.Subnet, error) {
    97  	networkEntries := configurator.GetNetworkEntries(machineInfo)
    98  	hwAddrToInterface := make(map[string]net.Interface, len(interfaces))
    99  	for _, iface := range interfaces {
   100  		hwAddrToInterface[iface.HardwareAddr.String()] = iface
   101  	}
   102  	for _, networkEntry := range networkEntries {
   103  		if len(networkEntry.HostIpAddress) < 1 {
   104  			continue
   105  		}
   106  		iface, ok := hwAddrToInterface[networkEntry.HostMacAddress.String()]
   107  		if !ok {
   108  			continue
   109  		}
   110  		subnet := configurator.FindMatchingSubnet(machineInfo.Subnets,
   111  			networkEntry.HostIpAddress)
   112  		if subnet == nil {
   113  			logger.Printf("no matching subnet for ip=%s\n",
   114  				networkEntry.HostIpAddress)
   115  			continue
   116  		}
   117  		return iface, networkEntry.HostIpAddress, subnet, nil
   118  	}
   119  	return net.Interface{}, nil, nil,
   120  		errors.New("no network interfaces match injected configuration")
   121  }
   122  
   123  func getConfiguration(interfaces map[string]net.Interface,
   124  	logger log.DebugLogger) (*fm_proto.GetMachineInfoResponse, error) {
   125  	var machineInfo fm_proto.GetMachineInfoResponse
   126  	err := json.ReadFromFile(filepath.Join(*tftpDirectory, "config.json"),
   127  		&machineInfo)
   128  	if err == nil { // Configuration was injected.
   129  		err := setupNetworkFromConfig(interfaces, machineInfo, logger)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		return &machineInfo, nil
   134  	}
   135  	if !os.IsNotExist(err) {
   136  		return nil, err
   137  	}
   138  	if err := setupNetworkFromDhcp(interfaces, logger); err != nil {
   139  		return nil, err
   140  	}
   141  	err = json.ReadFromFile(filepath.Join(*tftpDirectory, "config.json"),
   142  		&machineInfo)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	return &machineInfo, nil
   147  }
   148  
   149  func injectRandomSeed(client *tftp.Client, logger log.DebugLogger) error {
   150  	randomSeed := &bytes.Buffer{}
   151  	if wt, err := client.Receive("random-seed", "octet"); err != nil {
   152  		if strings.Contains(err.Error(), os.ErrNotExist.Error()) {
   153  			return nil
   154  		}
   155  		return err
   156  	} else if _, err := wt.WriteTo(randomSeed); err != nil {
   157  		return err
   158  	}
   159  	if file, err := os.OpenFile("/dev/urandom", os.O_WRONLY, 0); err != nil {
   160  		return err
   161  	} else {
   162  		defer file.Close()
   163  		if nCopied, err := io.Copy(file, randomSeed); err != nil {
   164  			return err
   165  		} else {
   166  			logger.Printf("copied %d bytes of random data\n", nCopied)
   167  		}
   168  	}
   169  	return nil
   170  }
   171  
   172  func loadTftpFiles(tftpServer net.IP, logger log.DebugLogger) error {
   173  	client, err := tftp.NewClient(tftpServer.String() + ":69")
   174  	if err != nil {
   175  		return err
   176  	}
   177  	if err := os.MkdirAll(*tftpDirectory, fsutil.DirPerms); err != nil {
   178  		return err
   179  	}
   180  	for _, name := range tftpFiles {
   181  		logger.Debugf(1, "downloading: %s\n", name)
   182  		if wt, err := client.Receive(name, "octet"); err != nil {
   183  			return err
   184  		} else {
   185  			filename := filepath.Join(*tftpDirectory, name)
   186  			if file, err := create(filename); err != nil {
   187  				return err
   188  			} else {
   189  				defer file.Close()
   190  				if _, err := wt.WriteTo(file); err != nil {
   191  					return err
   192  				}
   193  			}
   194  		}
   195  	}
   196  	return injectRandomSeed(client, logger)
   197  }
   198  
   199  func raiseInterfaces(interfaces map[string]net.Interface,
   200  	logger log.DebugLogger) error {
   201  	for name := range interfaces {
   202  		if err := run("ifconfig", "", logger, name, "up"); err != nil {
   203  			return err
   204  		}
   205  	}
   206  	return nil
   207  }
   208  
   209  func setupNetwork(ifName string, ipAddr net.IP, subnet *hyper_proto.Subnet,
   210  	logger log.DebugLogger) error {
   211  	err := run("ifconfig", "", logger, ifName, ipAddr.String(), "netmask",
   212  		subnet.IpMask.String(), "up")
   213  	if err != nil {
   214  		return err
   215  	}
   216  	err = run("route", "", logger, "add", "default", "gw",
   217  		subnet.IpGateway.String())
   218  	if err != nil {
   219  		e := run("route", "", logger, "del", "default", "gw",
   220  			subnet.IpGateway.String())
   221  		if e != nil {
   222  			return err
   223  		}
   224  		err = run("route", "", logger, "add", "default", "gw",
   225  			subnet.IpGateway.String())
   226  		if err != nil {
   227  			return err
   228  		}
   229  	}
   230  	if !*dryRun {
   231  		if err := configurator.WriteResolvConf("", subnet); err != nil {
   232  			return err
   233  		}
   234  	}
   235  	return nil
   236  }
   237  
   238  func setupNetworkFromConfig(interfaces map[string]net.Interface,
   239  	machineInfo fm_proto.GetMachineInfoResponse, logger log.DebugLogger) error {
   240  	iface, ipAddr, subnet, err := findInterfaceToConfigure(interfaces,
   241  		machineInfo, logger)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	return setupNetwork(iface.Name, ipAddr, subnet, logger)
   246  }
   247  
   248  func setupNetworkFromDhcp(interfaces map[string]net.Interface,
   249  	logger log.DebugLogger) error {
   250  	ifName, packet, err := dhcpRequest(interfaces, logger)
   251  	if err != nil {
   252  		return err
   253  	}
   254  	ipAddr := packet.YIAddr()
   255  	options := packet.ParseOptions()
   256  	subnet := hyper_proto.Subnet{
   257  		IpGateway: net.IP(options[dhcp4.OptionRouter]),
   258  		IpMask:    net.IP(options[dhcp4.OptionSubnetMask]),
   259  	}
   260  	dnsServersBuffer := options[dhcp4.OptionDomainNameServer]
   261  	for len(dnsServersBuffer) > 0 {
   262  		if len(dnsServersBuffer) >= 4 {
   263  			subnet.DomainNameServers = append(subnet.DomainNameServers,
   264  				net.IP(dnsServersBuffer[:4]))
   265  			dnsServersBuffer = dnsServersBuffer[4:]
   266  		} else {
   267  			return errors.New("truncated DNS server address")
   268  		}
   269  	}
   270  	if domainName := options[dhcp4.OptionDomainName]; len(domainName) > 0 {
   271  		subnet.DomainName = string(domainName)
   272  	}
   273  	if err := setupNetwork(ifName, ipAddr, &subnet, logger); err != nil {
   274  		return err
   275  	}
   276  	tftpServer := packet.SIAddr()
   277  	if tftpServer.Equal(zeroIP) {
   278  		tftpServer = net.IP(options[dhcp4.OptionTFTPServerName])
   279  		if tftpServer.Equal(zeroIP) {
   280  			return errors.New("no TFTP server given")
   281  		}
   282  		logger.Printf("tftpServer from OptionTFTPServerName: %s\n", tftpServer)
   283  	} else {
   284  		logger.Printf("tftpServer from SIAddr: %s\n", tftpServer)
   285  	}
   286  	return loadTftpFiles(tftpServer, logger)
   287  }