Skip to content

Commit

Permalink
Add DHCPv6 support
Browse files Browse the repository at this point in the history
This is part of u-root#56. For this to be useful the MAC address needs to be
read from the interface as well.

Signed-off-by: Christian Svensson <bluecmd@google.com>
  • Loading branch information
bluecmd committed Sep 25, 2018
1 parent 698750b commit 21f7655
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 55 deletions.
131 changes: 131 additions & 0 deletions pkg/bmc/interface.go
Original file line number Diff line number Diff line change
@@ -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
}
81 changes: 26 additions & 55 deletions pkg/bmc/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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:"))
Expand Down

0 comments on commit 21f7655

Please sign in to comment.