go_agent/src/bosh/platform/net/ubuntu_net_manager.go
package net
import (
"bytes"
"path/filepath"
"regexp"
"strings"
"text/template"
bosherr "bosh/errors"
boshlog "bosh/logger"
bosharp "bosh/platform/net/arp"
boship "bosh/platform/net/ip"
boshsettings "bosh/settings"
boshsys "bosh/system"
)
const ubuntuNetManagerLogTag = "ubuntuNetManager"
var (
ifupVersion07Regex = regexp.MustCompile(`ifup version 0\.7`)
)
type ubuntuNetManager struct {
DefaultNetworkResolver
cmdRunner boshsys.CmdRunner
fs boshsys.FileSystem
ipResolver boship.IPResolver
addressBroadcaster bosharp.AddressBroadcaster
logger boshlog.Logger
}
func NewUbuntuNetManager(
fs boshsys.FileSystem,
cmdRunner boshsys.CmdRunner,
defaultNetworkResolver DefaultNetworkResolver,
ipResolver boship.IPResolver,
addressBroadcaster bosharp.AddressBroadcaster,
logger boshlog.Logger,
) ubuntuNetManager {
return ubuntuNetManager{
DefaultNetworkResolver: defaultNetworkResolver,
cmdRunner: cmdRunner,
fs: fs,
ipResolver: ipResolver,
addressBroadcaster: addressBroadcaster,
logger: logger,
}
}
func (net ubuntuNetManager) getDNSServers(networks boshsettings.Networks) []string {
dnsNetwork, _ := networks.DefaultNetworkFor("dns")
return dnsNetwork.DNS
}
func (net ubuntuNetManager) SetupDhcp(networks boshsettings.Networks, errCh chan error) error {
dnsServers := net.getDNSServers(networks)
dnsServersList := strings.Join(dnsServers, ", ")
buffer := bytes.NewBuffer([]byte{})
t := template.Must(template.New("dhcp-config").Parse(ubuntuDHCPConfigTemplate))
err := t.Execute(buffer, dnsServersList)
if err != nil {
return bosherr.WrapError(err, "Generating config from template")
}
dhclientConfigFile := net.dhclientConfigFile()
written, err := net.fs.ConvergeFileContents(dhclientConfigFile, buffer.Bytes())
if err != nil {
return bosherr.WrapError(err, "Writing to %s", dhclientConfigFile)
}
if written {
args := net.restartNetworkArguments()
_, _, _, err := net.cmdRunner.RunCommand("ifdown", args...)
if err != nil {
net.logger.Info(ubuntuNetManagerLogTag, "Ignoring ifdown failure: %#v", err)
}
_, _, _, err = net.cmdRunner.RunCommand("ifup", args...)
if err != nil {
net.logger.Info(ubuntuNetManagerLogTag, "Ignoring ifup failure: %#v", err)
}
}
addresses := []boship.InterfaceAddress{
// eth0 is hard coded in AWS and OpenStack stemcells.
// TODO: abstract hardcoded network interface name to the NetManager
boship.NewResolvingInterfaceAddress("eth0", net.ipResolver),
}
go func() {
net.addressBroadcaster.BroadcastMACAddresses(addresses)
if errCh != nil {
errCh <- nil
}
}()
return nil
}
// DHCP Config file - /etc/dhcp3/dhclient.conf
// Ubuntu 14.04 accepts several DNS as a list in a single prepend directive
const ubuntuDHCPConfigTemplate = `# Generated by bosh-agent
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
send host-name "<hostname>";
request subnet-mask, broadcast-address, time-offset, routers,
domain-name, domain-name-servers, domain-search, host-name,
netbios-name-servers, netbios-scope, interface-mtu,
rfc3442-classless-static-routes, ntp-servers;
prepend domain-name-servers {{ . }};
`
func (net ubuntuNetManager) SetupManualNetworking(networks boshsettings.Networks, errCh chan error) error {
modifiedNetworks, written, err := net.writeNetworkInterfaces(networks)
if err != nil {
return bosherr.WrapError(err, "Writing network interfaces")
}
if written {
net.restartNetworkingInterfaces(modifiedNetworks)
}
err = net.writeResolvConf(networks)
if err != nil {
return bosherr.WrapError(err, "Writing resolv.conf")
}
addresses := toInterfaceAddresses(modifiedNetworks)
go func() {
net.addressBroadcaster.BroadcastMACAddresses(addresses)
if errCh != nil {
errCh <- nil
}
}()
return nil
}
func (net ubuntuNetManager) writeNetworkInterfaces(networks boshsettings.Networks) ([]customNetwork, bool, error) {
var modifiedNetworks []customNetwork
macAddresses, err := net.detectMacAddresses()
if err != nil {
return modifiedNetworks, false, bosherr.WrapError(err, "Detecting mac addresses")
}
for _, aNet := range networks {
network, broadcast, err := boshsys.CalculateNetworkAndBroadcast(aNet.IP, aNet.Netmask)
if err != nil {
return modifiedNetworks, false, bosherr.WrapError(err, "Calculating network and broadcast")
}
newNet := customNetwork{
aNet,
macAddresses[aNet.Mac],
network,
broadcast,
true,
}
modifiedNetworks = append(modifiedNetworks, newNet)
}
buffer := bytes.NewBuffer([]byte{})
t := template.Must(template.New("network-interfaces").Parse(ubuntuNetworkInterfacesTemplate))
err = t.Execute(buffer, modifiedNetworks)
if err != nil {
return modifiedNetworks, false, bosherr.WrapError(err, "Generating config from template")
}
written, err := net.fs.ConvergeFileContents("/etc/network/interfaces", buffer.Bytes())
if err != nil {
return modifiedNetworks, false, bosherr.WrapError(err, "Writing to /etc/network/interfaces")
}
return modifiedNetworks, written, nil
}
const ubuntuNetworkInterfacesTemplate = `# Generated by bosh-agent
auto lo
iface lo inet loopback
{{ range . }}
auto {{ .Interface }}
iface {{ .Interface }} inet static
address {{ .IP }}
network {{ .NetworkIP }}
netmask {{ .Netmask }}
broadcast {{ .Broadcast }}
{{ if .HasDefaultGateway }} gateway {{ .Gateway }}{{ end }}{{ end }}`
func (net ubuntuNetManager) writeResolvConf(networks boshsettings.Networks) error {
buffer := bytes.NewBuffer([]byte{})
t := template.Must(template.New("resolv-conf").Parse(ubuntuResolvConfTemplate))
dnsServers := net.getDNSServers(networks)
dnsServersArg := dnsConfigArg{dnsServers}
err := t.Execute(buffer, dnsServersArg)
if err != nil {
return bosherr.WrapError(err, "Generating config from template")
}
err = net.fs.WriteFile("/etc/resolv.conf", buffer.Bytes())
if err != nil {
return bosherr.WrapError(err, "Writing to /etc/resolv.conf")
}
return nil
}
const ubuntuResolvConfTemplate = `# Generated by bosh-agent
{{ range .DNSServers }}nameserver {{ . }}
{{ end }}`
func (net ubuntuNetManager) detectMacAddresses() (map[string]string, error) {
addresses := map[string]string{}
filePaths, err := net.fs.Glob("/sys/class/net/*")
if err != nil {
return addresses, bosherr.WrapError(err, "Getting file list from /sys/class/net")
}
var macAddress string
for _, filePath := range filePaths {
macAddress, err = net.fs.ReadFileString(filepath.Join(filePath, "address"))
if err != nil {
return addresses, bosherr.WrapError(err, "Reading mac address from file")
}
macAddress = strings.Trim(macAddress, "\n")
interfaceName := filepath.Base(filePath)
addresses[macAddress] = interfaceName
}
return addresses, nil
}
func (net ubuntuNetManager) restartNetworkingInterfaces(networks []customNetwork) {
for _, network := range networks {
_, _, _, err := net.cmdRunner.RunCommand("service", "network-interface", "stop", "INTERFACE="+network.Interface)
if err != nil {
net.logger.Info(ubuntuNetManagerLogTag, "Ignoring network stop failure: %#v", err)
}
_, _, _, err = net.cmdRunner.RunCommand("service", "network-interface", "start", "INTERFACE="+network.Interface)
if err != nil {
net.logger.Info(ubuntuNetManagerLogTag, "Ignoring network start failure: %#v", err)
}
}
}
func (net ubuntuNetManager) dhclientConfigFile() string {
if net.cmdRunner.CommandExists("dhclient3") {
// Using dhclient3
return "/etc/dhcp3/dhclient.conf"
}
return "/etc/dhcp/dhclient.conf"
}
func (net ubuntuNetManager) restartNetworkArguments() []string {
stdout, _, _, err := net.cmdRunner.RunCommand("ifup", "--version")
if err != nil {
net.logger.Info(ubuntuNetManagerLogTag, "Ignoring ifup version failure: %#v", err)
}
// Check if command accepts --no-loopback argument
// --exclude does not work with ifup > 0.7 which comes in Ubuntu 14.04
if ifupVersion07Regex.MatchString(stdout) {
return []string{"-a", "--no-loopback"}
}
return []string{"-a", "--exclude=lo"}
}