summaryrefslogtreecommitdiffhomepage
path: root/netboot/netconf.go
blob: 84fb263a6257b84a972b3b4935352bddc440555c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
package netboot

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"strings"
	"time"

	"github.com/insomniacslk/dhcp/dhcpv6"
	"github.com/vishvananda/netlink"
)

// AddrConf holds a single IP address configuration for a NIC
type AddrConf struct {
	IPNet             net.IPNet
	PreferredLifetime int
	ValidLifetime     int
}

// NetConf holds multiple IP configuration for a NIC, and DNS configuration
type NetConf struct {
	Addresses     []AddrConf
	DNSServers    []net.IP
	DNSSearchList []string
}

// GetNetConfFromPacketv6 extracts network configuration information from a DHCPv6
// Reply packet and returns a populated NetConf structure
func GetNetConfFromPacketv6(d *dhcpv6.DHCPv6Message) (*NetConf, error) {
	opt := d.GetOneOption(dhcpv6.OptionIANA)
	if opt == nil {
		return nil, errors.New("No option IA NA found")
	}
	netconf := NetConf{}
	// get IP configuration
	oiana := opt.(*dhcpv6.OptIANA)
	iaaddrs := make([]*dhcpv6.OptIAAddress, 0)
	for _, o := range oiana.Options {
		if o.Code() == dhcpv6.OptionIAAddr {
			iaaddrs = append(iaaddrs, o.(*dhcpv6.OptIAAddress))
		}
	}
	netmask := net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))
	for _, iaaddr := range iaaddrs {
		netconf.Addresses = append(netconf.Addresses, AddrConf{
			IPNet: net.IPNet{
				IP:   iaaddr.IPv6Addr,
				Mask: netmask,
			},
			PreferredLifetime: int(iaaddr.PreferredLifetime),
			ValidLifetime:     int(iaaddr.ValidLifetime),
		})
	}
	// get DNS configuration
	opt = d.GetOneOption(dhcpv6.OptionDNSRecursiveNameServer)
	if opt == nil {
		return nil, errors.New("No option DNS Recursive Name Servers found ")
	}
	odnsserv := opt.(*dhcpv6.OptDNSRecursiveNameServer)
	// TODO should this be copied?
	netconf.DNSServers = odnsserv.NameServers

	opt = d.GetOneOption(dhcpv6.OptionDomainSearchList)
	if opt == nil {
		return nil, errors.New("No option DNS Domain Search List found")
	}
	odomains := opt.(*dhcpv6.OptDomainSearchList)
	// TODO should this be copied?
	netconf.DNSSearchList = odomains.DomainSearchList

	return &netconf, nil
}

// IfUp brings up an interface by name, and waits for it to come up until a timeout expires
func IfUp(ifname string, timeout time.Duration) (netlink.Link, error) {
	start := time.Now()
	for time.Since(start) < timeout {
		iface, err := netlink.LinkByName(ifname)
		if err != nil {
			return nil, fmt.Errorf("cannot get interface %q by name: %v", ifname, err)
		}

		// if the interface is up, return
		if iface.Attrs().OperState == netlink.OperUp {
			// XXX despite the OperUp state, upon the first attempt I
			// consistently get a "cannot assign requested address" error. This
			// may be a bug in the netlink library. Need to investigate more.
			time.Sleep(time.Second)
			return iface, nil
		}
		// otherwise try to bring it up
		if err := netlink.LinkSetUp(iface); err != nil {
			return nil, fmt.Errorf("interface %q: %v can't make it up: %v", ifname, iface, err)
		}
	}

	return nil, fmt.Errorf("timed out while waiting for %s to come up", ifname)

}

// ConfigureInterface configures a network interface with the configuration held by a
// NetConf structure
func ConfigureInterface(ifname string, netconf *NetConf) error {
	iface, err := netlink.LinkByName(ifname)
	if err != nil {
		return fmt.Errorf("error getting interface information for %s: %v", ifname, err)
	}
	// configure interfaces
	for _, addr := range netconf.Addresses {
		dest := &netlink.Addr{
			IPNet:       &addr.IPNet,
			PreferedLft: addr.PreferredLifetime,
			ValidLft:    addr.ValidLifetime,
		}
		if err := netlink.AddrReplace(iface, dest); err != nil {
			if os.IsExist(err) {
				return fmt.Errorf("cannot configure %s on %s,%d,%d: %v", ifname, addr.IPNet, addr.PreferredLifetime, addr.ValidLifetime, err)
			}
		}
	}
	// configure /etc/resolv.conf
	resolvconf := ""
	for _, ns := range netconf.DNSServers {
		resolvconf += fmt.Sprintf("nameserver %s\n", ns)
	}
	resolvconf += fmt.Sprintf("search %s\n", strings.Join(netconf.DNSSearchList, " "))
	return ioutil.WriteFile("/etc/resolv.conf", []byte(resolvconf), 0644)
}