diff --git a/pkg/bmc/interface.go b/pkg/bmc/interface.go new file mode 100644 index 00000000..2127ab21 --- /dev/null +++ b/pkg/bmc/interface.go @@ -0,0 +1,131 @@ +// Copyright 2018 the u-root 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 bmc + +import ( + "fmt" + "log" + "math/rand" + "time" + + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/netboot" +) + +const ( + interfaceUpTimeout = 30 * time.Second + retryDelay = 10 * time.Second + retryDelaySecondsJitter = 10 +) + +func addIp(cidr string, iface string) error { + l, err := netlink.LinkByName(iface) + if err != nil { + return fmt.Errorf("Unable to get interface %s: %v", iface, err) + } + addr, err := netlink.ParseAddr(cidr) + if err != nil { + return fmt.Errorf("netlink.ParseAddr %v: %v", cidr, err) + } + h, err := netlink.NewHandle(unix.NETLINK_ROUTE) + if err != nil { + return fmt.Errorf("netlink.NewHandle: %v", err) + } + defer h.Delete() + if err := h.AddrReplace(l, addr); err != nil { + return fmt.Errorf("AddrReplace(%v): %v", addr, err) + } + return nil +} + +func setLinkUp(iface string) error { + l, err := netlink.LinkByName(iface) + if err != nil { + return fmt.Errorf("Unable to get interface %s: %v", iface, err) + } + h, err := netlink.NewHandle(unix.NETLINK_ROUTE) + if err != nil { + return fmt.Errorf("netlink.NewHandle: %v", err) + } + defer h.Delete() + if err := h.LinkSetUp(l); err != nil { + return fmt.Errorf("handle.LinkSetUp: %v", err) + } + return nil +} + +func doRetryDelay() { + delay := retryDelay + time.Duration(rand.Intn(retryDelaySecondsJitter)) * time.Second + log.Printf("Waiting %v before retrying", delay) + time.Sleep(delay) +} + +func applyDhcpv6(iface string, reply *dhcpv6.DHCPv6Message) error { + log.Printf("reply: %v", reply) + return nil +} + +func autoconfigInterface(iface string) { + for { + _, err := netboot.IfUp(iface, interfaceUpTimeout) + if err != nil { + log.Printf("Timeout waiting for link up on %s", iface) + continue + } + + client := dhcpv6.NewClient() + conv, err := client.Exchange(iface, nil) + if err != nil { + log.Printf("DHCPv6 exchange failed for %s: %v", iface, err) + doRetryDelay() + continue + } + + var reply dhcpv6.DHCPv6 + for _, m := range conv { + if m.Type() == dhcpv6.MessageTypeReply { + reply = m + break + } + } + if reply == nil { + log.Printf("No DHCPv6 reply found conversation for %s", iface) + doRetryDelay() + continue + } + if err := applyDhcpv6(iface, reply.(*dhcpv6.DHCPv6Message)); err != nil { + log.Printf("Failed to apply DHCPv6 configuration for %s: %v", iface, err) + doRetryDelay() + continue + } + } +} + +func ConfigureInterfaces() error { + unix.Sethostname([]byte("ubmc")) + + // Fun story: if you don't have both IPv4 and IPv6 loopback configured + // golang binaries will not bind to :: but to 0.0.0.0 instead. + // Isn't that surprising? + if err := addIp("127.0.0.1/8", "lo"); err != nil { + return err + } + if err := addIp("::1/32", "lo"); err != nil { + return err + } + if err := setLinkUp("lo"); err != nil { + return err + } + + // TODO(bluecmd): Read MAC address from NC-SI + + // TODO(bluecmd): Remove when DHCPv6 works + addIp("10.0.10.20/24", "eth0") + + go autoconfigInterface("eth0") + return nil +} diff --git a/pkg/bmc/system.go b/pkg/bmc/system.go index d605d151..d3516b68 100644 --- a/pkg/bmc/system.go +++ b/pkg/bmc/system.go @@ -5,19 +5,22 @@ package bmc import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" + crand "crypto/rand" "crypto/x509" + "encoding/binary" "encoding/pem" "io" "log" + "math/rand" "os" "os/exec" "os/signal" "strings" "syscall" - "github.com/vishvananda/netlink" "golang.org/x/sys/unix" ) @@ -57,50 +60,6 @@ func newSshKey() []byte { return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: asn1}) } -func addIp(cidr string, iface string) { - l, err := netlink.LinkByName(iface) - if err != nil { - log.Printf("Unable to get interface %s: %v", iface, err) - return - } - - addr, err := netlink.ParseAddr(cidr) - if err != nil { - log.Printf("netlink.ParseAddr %v: %v", cidr, err) - return - } - - h, err := netlink.NewHandle(unix.NETLINK_ROUTE) - if err != nil { - log.Printf("netlink.NewHandle: %v", err) - return - } - defer h.Delete() - if err := h.AddrReplace(l, addr); err != nil { - log.Printf("AddrReplace(%v): %v", addr, err) - return - } -} - -func setLinkUp(iface string) { - l, err := netlink.LinkByName(iface) - if err != nil { - log.Printf("Unable to get interface %s: %v", iface, err) - return - } - - h, err := netlink.NewHandle(unix.NETLINK_ROUTE) - if err != nil { - log.Printf("netlink.NewHandle: %v", err) - return - } - defer h.Delete() - if err := h.LinkSetUp(l); err != nil { - log.Printf("handle.LinkSetUp: %v", err) - return - } -} - func createFile(file string, mode os.FileMode, c []byte) { f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE, mode) if err != nil { @@ -140,7 +99,26 @@ func startSsh() { } } +func seedRandomGenerator() { + b := make([]byte, 8) + _, err := crand.Read(b) + if err != nil { + log.Fatalf("Unable to read random seed, cannot safely continue: %v", err) + } + buf := bytes.NewReader(b) + var seed int64 + if err := binary.Read(buf, binary.LittleEndian, &seed); err != nil { + log.Fatalf("Unable to convert random seed, cannot safely continue: %v", err) + } + rand.Seed(seed) +} + func Startup(p Platform) error { + // Seed the non-crypto random generator using the crypto one (which is + // hardware based). The non-crypto generator is used for random back-off + // timers and such, while the crypto one is used for crypto keys. + seedRandomGenerator() + loggers := []io.Writer{os.Stdout} lf, err := os.OpenFile("/tmp/u-bmc.log", os.O_RDWR|os.O_CREATE, 0600) if err != nil { @@ -151,16 +129,9 @@ func Startup(p Platform) error { } log.Printf("Loading system configuration") - syscall.Sethostname([]byte("ubmc")) - - // Fun story: if you don't have both IPv4 and IPv6 loopback configured - // golang binaries will not bind to :: but to 0.0.0.0 instead. - // Isn't that surprising? - addIp("127.0.0.1/8", "lo") - addIp("::1/32", "lo") - addIp("10.0.10.20/24", "eth0") - setLinkUp("lo") - setLinkUp("eth0") + if err := ConfigureInterfaces(); err != nil { + log.Printf("Failed to configure interfaces: %v", err) + } createFile("/etc/passwd", 0644, []byte("root:x:0:0:root:/root:/bbin/elvish")) createFile("/etc/group", 0644, []byte("root:x:0:"))