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 }