1
0
mirror of https://github.com/rancher/os.git synced 2025-09-16 06:59:12 +00:00

move coreos-cloudinit into config/cloudinit

Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
This commit is contained in:
Sven Dowideit
2017-02-22 23:55:32 +00:00
parent 9b793b5d7c
commit e2ed97648a
260 changed files with 132 additions and 25281 deletions

View File

@@ -0,0 +1,63 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"log"
"strings"
)
func ProcessDebianNetconf(config []byte) ([]InterfaceGenerator, error) {
log.Println("Processing Debian network config")
lines := formatConfig(string(config))
stanzas, err := parseStanzas(lines)
if err != nil {
return nil, err
}
interfaces := make([]*stanzaInterface, 0, len(stanzas))
for _, stanza := range stanzas {
switch s := stanza.(type) {
case *stanzaInterface:
interfaces = append(interfaces, s)
}
}
log.Printf("Parsed %d network interfaces\n", len(interfaces))
log.Println("Processed Debian network config")
return buildInterfaces(interfaces), nil
}
func formatConfig(config string) []string {
lines := []string{}
config = strings.Replace(config, "\\\n", "", -1)
for config != "" {
split := strings.SplitN(config, "\n", 2)
line := strings.TrimSpace(split[0])
if len(split) == 2 {
config = split[1]
} else {
config = ""
}
if strings.HasPrefix(line, "#") || line == "" {
continue
}
lines = append(lines, line)
}
return lines
}

View File

@@ -0,0 +1,56 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"testing"
)
func TestFormatConfigs(t *testing.T) {
for in, n := range map[string]int{
"": 0,
"line1\\\nis long": 1,
"#comment": 0,
"#comment\\\ncomment": 0,
" #comment \\\n comment\nline 1\nline 2\\\n is long": 2,
} {
lines := formatConfig(in)
if len(lines) != n {
t.Fatalf("bad number of lines for config %q: got %d, want %d", in, len(lines), n)
}
}
}
func TestProcessDebianNetconf(t *testing.T) {
for _, tt := range []struct {
in string
fail bool
n int
}{
{"", false, 0},
{"iface", true, -1},
{"auto eth1\nauto eth2", false, 0},
{"iface eth1 inet manual", false, 1},
} {
interfaces, err := ProcessDebianNetconf([]byte(tt.in))
failed := err != nil
if tt.fail != failed {
t.Fatalf("bad failure state for %q: got %t, want %t", tt.in, failed, tt.fail)
}
if tt.n != -1 && tt.n != len(interfaces) {
t.Fatalf("bad number of interfaces for %q: got %d, want %q", tt.in, len(interfaces), tt.n)
}
}
}

View File

@@ -0,0 +1,343 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"fmt"
"net"
"sort"
"strconv"
"strings"
)
type InterfaceGenerator interface {
Name() string
Filename() string
Netdev() string
Link() string
Network() string
Type() string
ModprobeParams() string
}
type networkInterface interface {
InterfaceGenerator
Children() []networkInterface
setConfigDepth(int)
}
type logicalInterface struct {
name string
hwaddr net.HardwareAddr
config configMethod
children []networkInterface
configDepth int
}
func (i *logicalInterface) Name() string {
return i.name
}
func (i *logicalInterface) Network() string {
config := fmt.Sprintln("[Match]")
if i.name != "" {
config += fmt.Sprintf("Name=%s\n", i.name)
}
if i.hwaddr != nil {
config += fmt.Sprintf("MACAddress=%s\n", i.hwaddr)
}
config += "\n[Network]\n"
for _, child := range i.children {
switch iface := child.(type) {
case *vlanInterface:
config += fmt.Sprintf("VLAN=%s\n", iface.name)
case *bondInterface:
config += fmt.Sprintf("Bond=%s\n", iface.name)
}
}
switch conf := i.config.(type) {
case configMethodStatic:
if len(conf.domains) > 0 {
config += fmt.Sprintf("Domains=%s\n", strings.Join(conf.domains, " "))
}
for _, nameserver := range conf.nameservers {
config += fmt.Sprintf("DNS=%s\n", nameserver)
}
for _, addr := range conf.addresses {
config += fmt.Sprintf("\n[Address]\nAddress=%s\n", addr.String())
}
for _, route := range conf.routes {
config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
}
case configMethodDHCP:
config += "DHCP=true\n"
}
return config
}
func (i *logicalInterface) Link() string {
return ""
}
func (i *logicalInterface) Netdev() string {
return ""
}
func (i *logicalInterface) Filename() string {
name := i.name
if name == "" {
name = i.hwaddr.String()
}
return fmt.Sprintf("%02x-%s", i.configDepth, name)
}
func (i *logicalInterface) Children() []networkInterface {
return i.children
}
func (i *logicalInterface) ModprobeParams() string {
return ""
}
func (i *logicalInterface) setConfigDepth(depth int) {
i.configDepth = depth
}
type physicalInterface struct {
logicalInterface
}
func (p *physicalInterface) Type() string {
return "physical"
}
type bondInterface struct {
logicalInterface
slaves []string
options map[string]string
}
func (b *bondInterface) Netdev() string {
config := fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
if b.hwaddr != nil {
config += fmt.Sprintf("MACAddress=%s\n", b.hwaddr.String())
}
config += fmt.Sprintf("\n[Bond]\n")
for _, name := range sortedKeys(b.options) {
config += fmt.Sprintf("%s=%s\n", name, b.options[name])
}
return config
}
func (b *bondInterface) Type() string {
return "bond"
}
func (b *bondInterface) ModprobeParams() string {
params := ""
for _, name := range sortedKeys(b.options) {
params += fmt.Sprintf("%s=%s ", name, b.options[name])
}
params = strings.TrimSuffix(params, " ")
return params
}
type vlanInterface struct {
logicalInterface
id int
rawDevice string
}
func (v *vlanInterface) Netdev() string {
config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
switch c := v.config.(type) {
case configMethodStatic:
if c.hwaddress != nil {
config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress)
}
case configMethodDHCP:
if c.hwaddress != nil {
config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress)
}
}
config += fmt.Sprintf("\n[VLAN]\nId=%d\n", v.id)
return config
}
func (v *vlanInterface) Type() string {
return "vlan"
}
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
interfaceMap := createInterfaces(stanzas)
linkAncestors(interfaceMap)
markConfigDepths(interfaceMap)
interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
for _, name := range sortedInterfaces(interfaceMap) {
interfaces = append(interfaces, interfaceMap[name])
}
return interfaces
}
func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
interfaceMap := make(map[string]networkInterface)
for _, iface := range stanzas {
switch iface.kind {
case interfaceBond:
bondOptions := make(map[string]string)
for _, k := range []string{"mode", "miimon", "lacp-rate"} {
if v, ok := iface.options["bond-"+k]; ok && len(v) > 0 {
bondOptions[k] = v[0]
}
}
interfaceMap[iface.name] = &bondInterface{
logicalInterface{
name: iface.name,
config: iface.configMethod,
children: []networkInterface{},
},
iface.options["bond-slaves"],
bondOptions,
}
for _, slave := range iface.options["bond-slaves"] {
if _, ok := interfaceMap[slave]; !ok {
interfaceMap[slave] = &physicalInterface{
logicalInterface{
name: slave,
config: configMethodManual{},
children: []networkInterface{},
},
}
}
}
case interfacePhysical:
if _, ok := iface.configMethod.(configMethodLoopback); ok {
continue
}
interfaceMap[iface.name] = &physicalInterface{
logicalInterface{
name: iface.name,
config: iface.configMethod,
children: []networkInterface{},
},
}
case interfaceVLAN:
var rawDevice string
id, _ := strconv.Atoi(iface.options["id"][0])
if device := iface.options["raw_device"]; len(device) == 1 {
rawDevice = device[0]
if _, ok := interfaceMap[rawDevice]; !ok {
interfaceMap[rawDevice] = &physicalInterface{
logicalInterface{
name: rawDevice,
config: configMethodManual{},
children: []networkInterface{},
},
}
}
}
interfaceMap[iface.name] = &vlanInterface{
logicalInterface{
name: iface.name,
config: iface.configMethod,
children: []networkInterface{},
},
id,
rawDevice,
}
}
}
return interfaceMap
}
func linkAncestors(interfaceMap map[string]networkInterface) {
for _, name := range sortedInterfaces(interfaceMap) {
iface := interfaceMap[name]
switch i := iface.(type) {
case *vlanInterface:
if parent, ok := interfaceMap[i.rawDevice]; ok {
switch p := parent.(type) {
case *physicalInterface:
p.children = append(p.children, iface)
case *bondInterface:
p.children = append(p.children, iface)
}
}
case *bondInterface:
for _, slave := range i.slaves {
if parent, ok := interfaceMap[slave]; ok {
switch p := parent.(type) {
case *physicalInterface:
p.children = append(p.children, iface)
case *bondInterface:
p.children = append(p.children, iface)
}
}
}
}
}
}
func markConfigDepths(interfaceMap map[string]networkInterface) {
rootInterfaceMap := make(map[string]networkInterface)
for k, v := range interfaceMap {
rootInterfaceMap[k] = v
}
for _, iface := range interfaceMap {
for _, child := range iface.Children() {
delete(rootInterfaceMap, child.Name())
}
}
for _, iface := range rootInterfaceMap {
setDepth(iface)
}
}
func setDepth(iface networkInterface) int {
maxDepth := 0
for _, child := range iface.Children() {
if depth := setDepth(child); depth > maxDepth {
maxDepth = depth
}
}
iface.setConfigDepth(maxDepth)
return (maxDepth + 1)
}
func sortedKeys(m map[string]string) (keys []string) {
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return
}
func sortedInterfaces(m map[string]networkInterface) (keys []string) {
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return
}

View File

@@ -0,0 +1,369 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"net"
"reflect"
"testing"
)
func TestInterfaceGenerators(t *testing.T) {
for _, tt := range []struct {
name string
netdev string
link string
network string
kind string
iface InterfaceGenerator
}{
{
name: "",
network: "[Match]\nMACAddress=00:01:02:03:04:05\n\n[Network]\n",
kind: "physical",
iface: &physicalInterface{logicalInterface{
hwaddr: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
}},
},
{
name: "testname",
network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\n",
kind: "physical",
iface: &physicalInterface{logicalInterface{
name: "testname",
children: []networkInterface{
&bondInterface{logicalInterface: logicalInterface{name: "testbond1"}},
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1},
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1},
},
}},
},
{
name: "testname",
netdev: "[NetDev]\nKind=bond\nName=testname\n\n[Bond]\n",
network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\nDHCP=true\n",
kind: "bond",
iface: &bondInterface{logicalInterface: logicalInterface{
name: "testname",
config: configMethodDHCP{},
children: []networkInterface{
&bondInterface{logicalInterface: logicalInterface{name: "testbond1"}},
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan1"}, id: 1},
&vlanInterface{logicalInterface: logicalInterface{name: "testvlan2"}, id: 1},
},
}},
},
{
name: "testname",
netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n",
network: "[Match]\nName=testname\n\n[Network]\n",
kind: "vlan",
iface: &vlanInterface{logicalInterface{name: "testname"}, 1, ""},
},
{
name: "testname",
netdev: "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
network: "[Match]\nName=testname\n\n[Network]\n",
kind: "vlan",
iface: &vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
},
{
name: "testname",
netdev: "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n",
network: "[Match]\nName=testname\n\n[Network]\nDHCP=true\n",
kind: "vlan",
iface: &vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""},
},
{
name: "testname",
netdev: "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=0\n",
network: "[Match]\nName=testname\n\n[Network]\nDomains=coreos.com example.com\nDNS=8.8.8.8\n\n[Address]\nAddress=192.168.1.100/24\n\n[Route]\nDestination=0.0.0.0/0\nGateway=1.2.3.4\n",
kind: "vlan",
iface: &vlanInterface{logicalInterface: logicalInterface{
name: "testname",
config: configMethodStatic{
addresses: []net.IPNet{{IP: []byte{192, 168, 1, 100}, Mask: []byte{255, 255, 255, 0}}},
nameservers: []net.IP{[]byte{8, 8, 8, 8}},
domains: []string{"coreos.com", "example.com"},
routes: []route{{destination: net.IPNet{IP: []byte{0, 0, 0, 0}, Mask: []byte{0, 0, 0, 0}}, gateway: []byte{1, 2, 3, 4}}},
},
}},
},
} {
if name := tt.iface.Name(); name != tt.name {
t.Fatalf("bad name (%q): want %q, got %q", tt.iface, tt.name, name)
}
if netdev := tt.iface.Netdev(); netdev != tt.netdev {
t.Fatalf("bad netdev (%q): want %q, got %q", tt.iface, tt.netdev, netdev)
}
if link := tt.iface.Link(); link != tt.link {
t.Fatalf("bad link (%q): want %q, got %q", tt.iface, tt.link, link)
}
if network := tt.iface.Network(); network != tt.network {
t.Fatalf("bad network (%q): want %q, got %q", tt.iface, tt.network, network)
}
if kind := tt.iface.Type(); kind != tt.kind {
t.Fatalf("bad type (%q): want %q, got %q", tt.iface, tt.kind, kind)
}
}
}
func TestModprobeParams(t *testing.T) {
for _, tt := range []struct {
i InterfaceGenerator
p string
}{
{
i: &physicalInterface{},
p: "",
},
{
i: &vlanInterface{},
p: "",
},
{
i: &bondInterface{
logicalInterface{},
nil,
map[string]string{
"a": "1",
"b": "2",
},
},
p: "a=1 b=2",
},
} {
if p := tt.i.ModprobeParams(); p != tt.p {
t.Fatalf("bad params (%q): got %s, want %s", tt.i, p, tt.p)
}
}
}
func TestBuildInterfacesLo(t *testing.T) {
stanzas := []*stanzaInterface{
{
name: "lo",
kind: interfacePhysical,
auto: false,
configMethod: configMethodLoopback{},
options: map[string][]string{},
},
}
interfaces := buildInterfaces(stanzas)
if len(interfaces) != 0 {
t.FailNow()
}
}
func TestBuildInterfacesBlindBond(t *testing.T) {
stanzas := []*stanzaInterface{
{
name: "bond0",
kind: interfaceBond,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"bond-slaves": {"eth0"},
},
},
}
interfaces := buildInterfaces(stanzas)
bond0 := &bondInterface{
logicalInterface{
name: "bond0",
config: configMethodManual{},
children: []networkInterface{},
configDepth: 0,
},
[]string{"eth0"},
map[string]string{},
}
eth0 := &physicalInterface{
logicalInterface{
name: "eth0",
config: configMethodManual{},
children: []networkInterface{bond0},
configDepth: 1,
},
}
expect := []InterfaceGenerator{bond0, eth0}
if !reflect.DeepEqual(interfaces, expect) {
t.FailNow()
}
}
func TestBuildInterfacesBlindVLAN(t *testing.T) {
stanzas := []*stanzaInterface{
{
name: "vlan0",
kind: interfaceVLAN,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"id": {"0"},
"raw_device": {"eth0"},
},
},
}
interfaces := buildInterfaces(stanzas)
vlan0 := &vlanInterface{
logicalInterface{
name: "vlan0",
config: configMethodManual{},
children: []networkInterface{},
configDepth: 0,
},
0,
"eth0",
}
eth0 := &physicalInterface{
logicalInterface{
name: "eth0",
config: configMethodManual{},
children: []networkInterface{vlan0},
configDepth: 1,
},
}
expect := []InterfaceGenerator{eth0, vlan0}
if !reflect.DeepEqual(interfaces, expect) {
t.FailNow()
}
}
func TestBuildInterfaces(t *testing.T) {
stanzas := []*stanzaInterface{
{
name: "eth0",
kind: interfacePhysical,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{},
},
{
name: "bond0",
kind: interfaceBond,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"bond-slaves": {"eth0"},
"bond-mode": {"4"},
"bond-miimon": {"100"},
},
},
{
name: "bond1",
kind: interfaceBond,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"bond-slaves": {"bond0"},
},
},
{
name: "vlan0",
kind: interfaceVLAN,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"id": {"0"},
"raw_device": {"eth0"},
},
},
{
name: "vlan1",
kind: interfaceVLAN,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{
"id": {"1"},
"raw_device": {"bond0"},
},
},
}
interfaces := buildInterfaces(stanzas)
vlan1 := &vlanInterface{
logicalInterface{
name: "vlan1",
config: configMethodManual{},
children: []networkInterface{},
configDepth: 0,
},
1,
"bond0",
}
vlan0 := &vlanInterface{
logicalInterface{
name: "vlan0",
config: configMethodManual{},
children: []networkInterface{},
configDepth: 0,
},
0,
"eth0",
}
bond1 := &bondInterface{
logicalInterface{
name: "bond1",
config: configMethodManual{},
children: []networkInterface{},
configDepth: 0,
},
[]string{"bond0"},
map[string]string{},
}
bond0 := &bondInterface{
logicalInterface{
name: "bond0",
config: configMethodManual{},
children: []networkInterface{bond1, vlan1},
configDepth: 1,
},
[]string{"eth0"},
map[string]string{
"mode": "4",
"miimon": "100",
},
}
eth0 := &physicalInterface{
logicalInterface{
name: "eth0",
config: configMethodManual{},
children: []networkInterface{bond0, vlan0},
configDepth: 2,
},
}
expect := []InterfaceGenerator{bond0, bond1, eth0, vlan0, vlan1}
if !reflect.DeepEqual(interfaces, expect) {
t.FailNow()
}
}
func TestFilename(t *testing.T) {
for _, tt := range []struct {
i logicalInterface
f string
}{
{logicalInterface{name: "iface", configDepth: 0}, "00-iface"},
{logicalInterface{name: "iface", configDepth: 9}, "09-iface"},
{logicalInterface{name: "iface", configDepth: 10}, "0a-iface"},
{logicalInterface{name: "iface", configDepth: 53}, "35-iface"},
{logicalInterface{hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-01:23:45:67:89:ab"},
{logicalInterface{name: "iface", hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}), configDepth: 1}, "01-iface"},
} {
if tt.i.Filename() != tt.f {
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
}
}
}

View File

@@ -0,0 +1,5 @@
// +build !go1.5
package network
const isGo15 = false

View File

@@ -0,0 +1,5 @@
// +build go1.5
package network
const isGo15 = true

View File

@@ -0,0 +1,115 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"net"
"github.com/rancher/os/config/cloudinit/datasource/metadata/packet"
)
func ProcessPacketNetconf(netdata packet.NetworkData) ([]InterfaceGenerator, error) {
var nameservers []net.IP
if netdata.DNS != nil {
nameservers = netdata.DNS
} else {
nameservers = append(nameservers, net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4"))
}
generators, err := parseNetwork(netdata, nameservers)
if err != nil {
return nil, err
}
return generators, nil
}
func parseNetwork(netdata packet.NetworkData, nameservers []net.IP) ([]InterfaceGenerator, error) {
var interfaces []InterfaceGenerator
var addresses []net.IPNet
var routes []route
for _, netblock := range netdata.Netblocks {
addresses = append(addresses, net.IPNet{
IP: netblock.Address,
Mask: net.IPMask(netblock.Netmask),
})
if netblock.Public == false {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv4(10, 0, 0, 0),
Mask: net.IPv4Mask(255, 0, 0, 0),
},
gateway: netblock.Gateway,
})
} else {
if netblock.AddressFamily == 4 {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv4zero,
Mask: net.IPMask(net.IPv4zero),
},
gateway: netblock.Gateway,
})
} else {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv6zero,
Mask: net.IPMask(net.IPv6zero),
},
gateway: netblock.Gateway,
})
}
}
}
bond := bondInterface{
logicalInterface: logicalInterface{
name: "bond0",
config: configMethodStatic{
addresses: addresses,
nameservers: nameservers,
routes: routes,
},
},
options: map[string]string{
"Mode": "802.3ad",
"LACPTransmitRate": "fast",
"MIIMonitorSec": ".2",
"UpDelaySec": ".2",
"DownDelaySec": ".2",
},
}
bond.hwaddr, _ = net.ParseMAC(netdata.Interfaces[0].Mac)
for index, iface := range netdata.Interfaces {
bond.slaves = append(bond.slaves, iface.Name)
interfaces = append(interfaces, &physicalInterface{
logicalInterface: logicalInterface{
name: iface.Name,
config: configMethodStatic{
nameservers: nameservers,
},
children: []networkInterface{&bond},
configDepth: index,
},
})
}
interfaces = append(interfaces, &bond)
return interfaces, nil
}

View File

@@ -0,0 +1,341 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"fmt"
"net"
"strconv"
"strings"
)
type stanza interface{}
type stanzaAuto struct {
interfaces []string
}
type stanzaInterface struct {
name string
kind interfaceKind
auto bool
configMethod configMethod
options map[string][]string
}
type interfaceKind int
const (
interfaceBond = interfaceKind(iota)
interfacePhysical
interfaceVLAN
)
type route struct {
destination net.IPNet
gateway net.IP
}
type configMethod interface{}
type configMethodStatic struct {
addresses []net.IPNet
nameservers []net.IP
domains []string
routes []route
hwaddress net.HardwareAddr
}
type configMethodLoopback struct{}
type configMethodManual struct{}
type configMethodDHCP struct {
hwaddress net.HardwareAddr
}
func parseStanzas(lines []string) (stanzas []stanza, err error) {
rawStanzas, err := splitStanzas(lines)
if err != nil {
return nil, err
}
stanzas = make([]stanza, 0, len(rawStanzas))
for _, rawStanza := range rawStanzas {
if stanza, err := parseStanza(rawStanza); err == nil {
stanzas = append(stanzas, stanza)
} else {
return nil, err
}
}
autos := make([]string, 0)
interfaceMap := make(map[string]*stanzaInterface)
for _, stanza := range stanzas {
switch c := stanza.(type) {
case *stanzaAuto:
autos = append(autos, c.interfaces...)
case *stanzaInterface:
interfaceMap[c.name] = c
}
}
// Apply the auto attribute
for _, auto := range autos {
if iface, ok := interfaceMap[auto]; ok {
iface.auto = true
}
}
return stanzas, nil
}
func splitStanzas(lines []string) ([][]string, error) {
var curStanza []string
stanzas := make([][]string, 0)
for _, line := range lines {
if isStanzaStart(line) {
if curStanza != nil {
stanzas = append(stanzas, curStanza)
}
curStanza = []string{line}
} else if curStanza != nil {
curStanza = append(curStanza, line)
} else {
return nil, fmt.Errorf("missing stanza start %q", line)
}
}
if curStanza != nil {
stanzas = append(stanzas, curStanza)
}
return stanzas, nil
}
func isStanzaStart(line string) bool {
switch strings.Split(line, " ")[0] {
case "auto":
fallthrough
case "iface":
fallthrough
case "mapping":
return true
}
if strings.HasPrefix(line, "allow-") {
return true
}
return false
}
func parseStanza(rawStanza []string) (stanza, error) {
if len(rawStanza) == 0 {
panic("empty stanza")
}
tokens := strings.Fields(rawStanza[0])
if len(tokens) < 2 {
return nil, fmt.Errorf("malformed stanza start %q", rawStanza[0])
}
kind := tokens[0]
attributes := tokens[1:]
switch kind {
case "auto":
return parseAutoStanza(attributes, rawStanza[1:])
case "iface":
return parseInterfaceStanza(attributes, rawStanza[1:])
default:
return nil, fmt.Errorf("unknown stanza %q", kind)
}
}
func parseAutoStanza(attributes []string, options []string) (*stanzaAuto, error) {
return &stanzaAuto{interfaces: attributes}, nil
}
func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterface, error) {
if len(attributes) != 3 {
return nil, fmt.Errorf("incorrect number of attributes")
}
iface := attributes[0]
confMethod := attributes[2]
optionMap := make(map[string][]string, 0)
for _, option := range options {
if strings.HasPrefix(option, "post-up") {
tokens := strings.SplitAfterN(option, " ", 2)
if len(tokens) != 2 {
continue
}
if v, ok := optionMap["post-up"]; ok {
optionMap["post-up"] = append(v, tokens[1])
} else {
optionMap["post-up"] = []string{tokens[1]}
}
} else if strings.HasPrefix(option, "pre-down") {
tokens := strings.SplitAfterN(option, " ", 2)
if len(tokens) != 2 {
continue
}
if v, ok := optionMap["pre-down"]; ok {
optionMap["pre-down"] = append(v, tokens[1])
} else {
optionMap["pre-down"] = []string{tokens[1]}
}
} else {
tokens := strings.Fields(option)
optionMap[tokens[0]] = tokens[1:]
}
}
var conf configMethod
switch confMethod {
case "static":
config := configMethodStatic{
addresses: make([]net.IPNet, 1),
routes: make([]route, 0),
nameservers: make([]net.IP, 0),
}
if addresses, ok := optionMap["address"]; ok {
if len(addresses) == 1 {
config.addresses[0].IP = net.ParseIP(addresses[0])
}
}
if netmasks, ok := optionMap["netmask"]; ok {
if len(netmasks) == 1 {
config.addresses[0].Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
}
}
if config.addresses[0].IP == nil || config.addresses[0].Mask == nil {
return nil, fmt.Errorf("malformed static network config for %q", iface)
}
if gateways, ok := optionMap["gateway"]; ok {
if len(gateways) == 1 {
config.routes = append(config.routes, route{
destination: net.IPNet{
IP: net.IPv4(0, 0, 0, 0),
Mask: net.IPv4Mask(0, 0, 0, 0),
},
gateway: net.ParseIP(gateways[0]),
})
}
}
if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
config.hwaddress = hwaddress
} else {
return nil, err
}
for _, nameserver := range optionMap["dns-nameservers"] {
config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
}
for _, postup := range optionMap["post-up"] {
if strings.HasPrefix(postup, "route add") {
route := route{}
fields := strings.Fields(postup)
for i, field := range fields[:len(fields)-1] {
switch field {
case "-net":
if _, dst, err := net.ParseCIDR(fields[i+1]); err == nil {
route.destination = *dst
} else {
route.destination.IP = net.ParseIP(fields[i+1])
}
case "netmask":
route.destination.Mask = net.IPMask(net.ParseIP(fields[i+1]).To4())
case "gw":
route.gateway = net.ParseIP(fields[i+1])
}
}
if route.destination.IP != nil && route.destination.Mask != nil && route.gateway != nil {
config.routes = append(config.routes, route)
}
}
}
conf = config
case "loopback":
conf = configMethodLoopback{}
case "manual":
conf = configMethodManual{}
case "dhcp":
config := configMethodDHCP{}
if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
config.hwaddress = hwaddress
} else {
return nil, err
}
conf = config
default:
return nil, fmt.Errorf("invalid config method %q", confMethod)
}
if _, ok := optionMap["vlan_raw_device"]; ok {
return parseVLANStanza(iface, conf, attributes, optionMap)
}
if strings.Contains(iface, ".") {
return parseVLANStanza(iface, conf, attributes, optionMap)
}
if _, ok := optionMap["bond-slaves"]; ok {
return parseBondStanza(iface, conf, attributes, optionMap)
}
return parsePhysicalStanza(iface, conf, attributes, optionMap)
}
func parseHwaddress(options map[string][]string, iface string) (net.HardwareAddr, error) {
if hwaddress, ok := options["hwaddress"]; ok && len(hwaddress) == 2 {
switch hwaddress[0] {
case "ether":
if address, err := net.ParseMAC(hwaddress[1]); err == nil {
return address, nil
}
return nil, fmt.Errorf("malformed hwaddress option for %q", iface)
}
}
return nil, nil
}
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
}
func parsePhysicalStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
return &stanzaInterface{name: iface, kind: interfacePhysical, configMethod: conf, options: options}, nil
}
func parseVLANStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
var id string
if strings.Contains(iface, ".") {
tokens := strings.Split(iface, ".")
id = tokens[len(tokens)-1]
} else if strings.HasPrefix(iface, "vlan") {
id = strings.TrimPrefix(iface, "vlan")
} else {
return nil, fmt.Errorf("malformed vlan name %q", iface)
}
if _, err := strconv.Atoi(id); err != nil {
return nil, fmt.Errorf("malformed vlan name %q", iface)
}
options["id"] = []string{id}
options["raw_device"] = options["vlan_raw_device"]
return &stanzaInterface{name: iface, kind: interfaceVLAN, configMethod: conf, options: options}, nil
}

View File

@@ -0,0 +1,582 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"net"
"reflect"
"strings"
"testing"
)
func TestSplitStanzasNoParent(t *testing.T) {
in := []string{"test"}
e := "missing stanza start"
_, err := splitStanzas(in)
if err == nil || !strings.HasPrefix(err.Error(), e) {
t.Fatalf("bad error for splitStanzas(%q): got %q, want %q", in, err, e)
}
}
func TestBadParseStanzas(t *testing.T) {
for in, e := range map[string]string{
"": "missing stanza start",
"iface": "malformed stanza start",
"allow-?? unknown": "unknown stanza",
} {
_, err := parseStanzas([]string{in})
if err == nil || !strings.HasPrefix(err.Error(), e) {
t.Fatalf("bad error for parseStanzas(%q): got %q, want %q", in, err, e)
}
}
}
func TestBadParseInterfaceStanza(t *testing.T) {
for _, tt := range []struct {
in []string
opts []string
e string
}{
{[]string{}, nil, "incorrect number of attributes"},
{[]string{"eth", "inet", "invalid"}, nil, "invalid config method"},
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100"}, "malformed static network config"},
{[]string{"eth", "inet", "static"}, []string{"netmask 255.255.255.0"}, "malformed static network config"},
{[]string{"eth", "inet", "static"}, []string{"address invalid", "netmask 255.255.255.0"}, "malformed static network config"},
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask invalid"}, "malformed static network config"},
{[]string{"eth", "inet", "static"}, []string{"address 192.168.1.100", "netmask 255.255.255.0", "hwaddress ether NotAnAddress"}, "malformed hwaddress option"},
{[]string{"eth", "inet", "dhcp"}, []string{"hwaddress ether NotAnAddress"}, "malformed hwaddress option"},
} {
_, err := parseInterfaceStanza(tt.in, tt.opts)
if err == nil || !strings.HasPrefix(err.Error(), tt.e) {
t.Fatalf("bad error parsing interface stanza %q: got %q, want %q", tt.in, err.Error(), tt.e)
}
}
}
func TestBadParseVLANStanzas(t *testing.T) {
conf := configMethodManual{}
options := map[string][]string{}
for _, in := range []string{"myvlan", "eth.vlan"} {
_, err := parseVLANStanza(in, conf, nil, options)
if err == nil || !strings.HasPrefix(err.Error(), "malformed vlan name") {
t.Fatalf("did not error on bad vlan %q", in)
}
}
}
func TestSplitStanzas(t *testing.T) {
expect := [][]string{
{"auto lo"},
{"iface eth1", "option: 1"},
{"mapping"},
{"allow-"},
}
lines := make([]string, 0, 5)
for _, stanza := range expect {
for _, line := range stanza {
lines = append(lines, line)
}
}
stanzas, err := splitStanzas(lines)
if err != nil {
t.FailNow()
}
for i, stanza := range stanzas {
if len(stanza) != len(expect[i]) {
t.FailNow()
}
for j, line := range stanza {
if line != expect[i][j] {
t.FailNow()
}
}
}
}
func TestParseStanzaNil(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("parseStanza(nil) did not panic")
}
}()
parseStanza(nil)
}
func TestParseStanzaSuccess(t *testing.T) {
for _, in := range []string{
"auto a",
"iface a inet manual",
} {
if _, err := parseStanza([]string{in}); err != nil {
t.Fatalf("unexpected error parsing stanza %q: %s", in, err)
}
}
}
func TestParseAutoStanza(t *testing.T) {
interfaces := []string{"test", "attribute"}
stanza, err := parseAutoStanza(interfaces, nil)
if err != nil {
t.Fatalf("unexpected error parsing auto stanza %q: %s", interfaces, err)
}
if !reflect.DeepEqual(stanza.interfaces, interfaces) {
t.FailNow()
}
}
func TestParseBondStanzaNoSlaves(t *testing.T) {
bond, err := parseBondStanza("", nil, nil, map[string][]string{})
if err != nil {
t.FailNow()
}
if bond.options["bond-slaves"] != nil {
t.FailNow()
}
}
func TestParseBondStanza(t *testing.T) {
conf := configMethodManual{}
options := map[string][]string{
"bond-slaves": {"1", "2"},
}
bond, err := parseBondStanza("test", conf, nil, options)
if err != nil {
t.FailNow()
}
if bond.name != "test" {
t.FailNow()
}
if bond.kind != interfaceBond {
t.FailNow()
}
if bond.configMethod != conf {
t.FailNow()
}
}
func TestParsePhysicalStanza(t *testing.T) {
conf := configMethodManual{}
options := map[string][]string{
"a": {"1", "2"},
"b": {"1"},
}
physical, err := parsePhysicalStanza("test", conf, nil, options)
if err != nil {
t.FailNow()
}
if physical.name != "test" {
t.FailNow()
}
if physical.kind != interfacePhysical {
t.FailNow()
}
if physical.configMethod != conf {
t.FailNow()
}
if !reflect.DeepEqual(physical.options, options) {
t.FailNow()
}
}
func TestParseVLANStanzas(t *testing.T) {
conf := configMethodManual{}
options := map[string][]string{}
for _, in := range []string{"vlan25", "eth.25"} {
vlan, err := parseVLANStanza(in, conf, nil, options)
if err != nil {
t.Fatalf("unexpected error from parseVLANStanza(%q): %s", in, err)
}
if !reflect.DeepEqual(vlan.options["id"], []string{"25"}) {
t.FailNow()
}
}
}
func TestParseInterfaceStanzaStaticAddress(t *testing.T) {
options := []string{"address 192.168.1.100", "netmask 255.255.255.0"}
expect := []net.IPNet{
{
IP: net.IPv4(192, 168, 1, 100),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
if err != nil {
t.FailNow()
}
static, ok := iface.configMethod.(configMethodStatic)
if !ok {
t.FailNow()
}
if !reflect.DeepEqual(static.addresses, expect) {
t.FailNow()
}
}
func TestParseInterfaceStanzaStaticGateway(t *testing.T) {
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "gateway 192.168.1.1"}
expect := []route{
{
destination: net.IPNet{
IP: net.IPv4(0, 0, 0, 0),
Mask: net.IPv4Mask(0, 0, 0, 0),
},
gateway: net.IPv4(192, 168, 1, 1),
},
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
if err != nil {
t.FailNow()
}
static, ok := iface.configMethod.(configMethodStatic)
if !ok {
t.FailNow()
}
if !reflect.DeepEqual(static.routes, expect) {
t.FailNow()
}
}
func TestParseInterfaceStanzaStaticDNS(t *testing.T) {
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", "dns-nameservers 192.168.1.10 192.168.1.11 192.168.1.12"}
expect := []net.IP{
net.IPv4(192, 168, 1, 10),
net.IPv4(192, 168, 1, 11),
net.IPv4(192, 168, 1, 12),
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
if err != nil {
t.FailNow()
}
static, ok := iface.configMethod.(configMethodStatic)
if !ok {
t.FailNow()
}
if !reflect.DeepEqual(static.nameservers, expect) {
t.FailNow()
}
}
func TestBadParseInterfaceStanzasStaticPostUp(t *testing.T) {
for _, in := range []string{
"post-up invalid",
"post-up route add",
"post-up route add -net",
"post-up route add gw",
"post-up route add netmask",
"gateway",
"gateway 192.168.1.1 192.168.1.2",
} {
options := []string{"address 192.168.1.100", "netmask 255.255.255.0", in}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, options)
if err != nil {
t.Fatalf("parseInterfaceStanza with options %s got unexpected error", options)
}
static, ok := iface.configMethod.(configMethodStatic)
if !ok {
t.Fatalf("parseInterfaceStanza with options %s did not return configMethodStatic", options)
}
if len(static.routes) != 0 {
t.Fatalf("parseInterfaceStanza with options %s did not return zero-length static routes", options)
}
}
}
func TestParseInterfaceStanzaStaticPostUp(t *testing.T) {
for _, tt := range []struct {
options []string
expect []route
}{
{
options: []string{
"address 192.168.1.100",
"netmask 255.255.255.0",
"post-up route add gw 192.168.1.1 -net 192.168.1.0 netmask 255.255.255.0",
},
expect: []route{
{
destination: net.IPNet{
IP: net.IPv4(192, 168, 1, 0),
Mask: net.IPv4Mask(255, 255, 255, 0),
},
gateway: net.IPv4(192, 168, 1, 1),
},
},
},
{
options: []string{
"address 192.168.1.100",
"netmask 255.255.255.0",
"post-up route add gw 192.168.1.1 -net 192.168.1.0/24 || true",
},
expect: []route{
{
destination: func() net.IPNet {
if _, net, err := net.ParseCIDR("192.168.1.0/24"); err == nil {
return *net
} else {
panic(err)
}
}(),
gateway: net.IPv4(192, 168, 1, 1),
},
},
},
} {
iface, err := parseInterfaceStanza([]string{"eth", "inet", "static"}, tt.options)
if err != nil {
t.Fatalf("bad error (%+v): want nil, got %s\n", tt, err)
}
static, ok := iface.configMethod.(configMethodStatic)
if !ok {
t.Fatalf("bad config method (%+v): want configMethodStatic, got %T\n", tt, iface.configMethod)
}
if !reflect.DeepEqual(static.routes, tt.expect) {
t.Fatalf("bad routes (%+v): want %#v, got %#v\n", tt, tt.expect, static.routes)
}
}
}
func TestParseInterfaceStanzaLoopback(t *testing.T) {
iface, err := parseInterfaceStanza([]string{"eth", "inet", "loopback"}, nil)
if err != nil {
t.FailNow()
}
if _, ok := iface.configMethod.(configMethodLoopback); !ok {
t.FailNow()
}
}
func TestParseInterfaceStanzaManual(t *testing.T) {
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, nil)
if err != nil {
t.FailNow()
}
if _, ok := iface.configMethod.(configMethodManual); !ok {
t.FailNow()
}
}
func TestParseInterfaceStanzaDHCP(t *testing.T) {
iface, err := parseInterfaceStanza([]string{"eth", "inet", "dhcp"}, nil)
if err != nil {
t.FailNow()
}
if _, ok := iface.configMethod.(configMethodDHCP); !ok {
t.FailNow()
}
}
func TestParseInterfaceStanzaPostUpOption(t *testing.T) {
options := []string{
"post-up",
"post-up 1 2",
"post-up 3 4",
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
if err != nil {
t.FailNow()
}
if !reflect.DeepEqual(iface.options["post-up"], []string{"1 2", "3 4"}) {
t.Log(iface.options["post-up"])
t.FailNow()
}
}
func TestParseInterfaceStanzaPreDownOption(t *testing.T) {
options := []string{
"pre-down",
"pre-down 3",
"pre-down 4",
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
if err != nil {
t.FailNow()
}
if !reflect.DeepEqual(iface.options["pre-down"], []string{"3", "4"}) {
t.Log(iface.options["pre-down"])
t.FailNow()
}
}
func TestParseInterfaceStanzaEmptyOption(t *testing.T) {
options := []string{
"test",
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
if err != nil {
t.FailNow()
}
if !reflect.DeepEqual(iface.options["test"], []string{}) {
t.FailNow()
}
}
func TestParseInterfaceStanzaOptions(t *testing.T) {
options := []string{
"test1 1",
"test2 2 3",
"test1 5 6",
}
iface, err := parseInterfaceStanza([]string{"eth", "inet", "manual"}, options)
if err != nil {
t.FailNow()
}
if !reflect.DeepEqual(iface.options["test1"], []string{"5", "6"}) {
t.Log(iface.options["test1"])
t.FailNow()
}
if !reflect.DeepEqual(iface.options["test2"], []string{"2", "3"}) {
t.Log(iface.options["test2"])
t.FailNow()
}
}
func TestParseInterfaceStanzaHwaddress(t *testing.T) {
for _, tt := range []struct {
attr []string
opt []string
hw net.HardwareAddr
}{
{
[]string{"mybond", "inet", "dhcp"},
[]string{},
nil,
},
{
[]string{"mybond", "inet", "dhcp"},
[]string{"hwaddress ether 00:01:02:03:04:05"},
net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
},
{
[]string{"mybond", "inet", "static"},
[]string{"hwaddress ether 00:01:02:03:04:05", "address 192.168.1.100", "netmask 255.255.255.0"},
net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}),
},
} {
iface, err := parseInterfaceStanza(tt.attr, tt.opt)
if err != nil {
t.Fatalf("error in parseInterfaceStanza (%q, %q): %q", tt.attr, tt.opt, err)
}
switch c := iface.configMethod.(type) {
case configMethodStatic:
if !reflect.DeepEqual(c.hwaddress, tt.hw) {
t.Fatalf("bad hwaddress (%q, %q): got %q, want %q", tt.attr, tt.opt, c.hwaddress, tt.hw)
}
case configMethodDHCP:
if !reflect.DeepEqual(c.hwaddress, tt.hw) {
t.Fatalf("bad hwaddress (%q, %q): got %q, want %q", tt.attr, tt.opt, c.hwaddress, tt.hw)
}
}
}
}
func TestParseInterfaceStanzaBond(t *testing.T) {
iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"})
if err != nil {
t.FailNow()
}
if iface.kind != interfaceBond {
t.FailNow()
}
}
func TestParseInterfaceStanzaVLANName(t *testing.T) {
iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil)
if err != nil {
t.FailNow()
}
if iface.kind != interfaceVLAN {
t.FailNow()
}
}
func TestParseInterfaceStanzaVLANOption(t *testing.T) {
iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"})
if err != nil {
t.FailNow()
}
if iface.kind != interfaceVLAN {
t.FailNow()
}
}
func TestParseStanzasNone(t *testing.T) {
stanzas, err := parseStanzas(nil)
if err != nil {
t.FailNow()
}
if len(stanzas) != 0 {
t.FailNow()
}
}
func TestParseStanzas(t *testing.T) {
lines := []string{
"auto lo",
"iface lo inet loopback",
"iface eth1 inet manual",
"iface eth2 inet manual",
"iface eth3 inet manual",
"auto eth1 eth3",
}
expect := []stanza{
&stanzaAuto{
interfaces: []string{"lo"},
},
&stanzaInterface{
name: "lo",
kind: interfacePhysical,
auto: true,
configMethod: configMethodLoopback{},
options: map[string][]string{},
},
&stanzaInterface{
name: "eth1",
kind: interfacePhysical,
auto: true,
configMethod: configMethodManual{},
options: map[string][]string{},
},
&stanzaInterface{
name: "eth2",
kind: interfacePhysical,
auto: false,
configMethod: configMethodManual{},
options: map[string][]string{},
},
&stanzaInterface{
name: "eth3",
kind: interfacePhysical,
auto: true,
configMethod: configMethodManual{},
options: map[string][]string{},
},
&stanzaAuto{
interfaces: []string{"eth1", "eth3"},
},
}
stanzas, err := parseStanzas(lines)
if err != err {
t.FailNow()
}
if !reflect.DeepEqual(stanzas, expect) {
t.FailNow()
}
}

View File

@@ -0,0 +1,186 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"fmt"
"log"
"net"
)
func ProcessVMwareNetconf(config map[string]string) ([]InterfaceGenerator, error) {
log.Println("Processing VMware network config")
log.Println("Parsing nameservers")
var nameservers []net.IP
for i := 0; ; i++ {
if ipStr, ok := config[fmt.Sprintf("dns.server.%d", i)]; ok {
if ip := net.ParseIP(ipStr); ip != nil {
nameservers = append(nameservers, ip)
} else {
return nil, fmt.Errorf("invalid nameserver: %q", ipStr)
}
} else {
break
}
}
log.Printf("Parsed %d nameservers", len(nameservers))
log.Println("Parsing search domains")
var domains []string
for i := 0; ; i++ {
if domain, ok := config[fmt.Sprintf("dns.domain.%d", i)]; ok {
domains = append(domains, domain)
} else {
break
}
}
log.Printf("Parsed %d search domains", len(domains))
var interfaces []InterfaceGenerator
for i := 0; ; i++ {
var addresses []net.IPNet
var routes []route
var err error
var dhcp bool
iface := &physicalInterface{}
log.Printf("Proccessing interface %d", i)
log.Println("Processing DHCP")
if dhcp, err = processDHCPConfig(config, fmt.Sprintf("interface.%d.", i)); err != nil {
return nil, err
}
log.Println("Processing addresses")
if as, err := processAddressConfig(config, fmt.Sprintf("interface.%d.", i)); err == nil {
addresses = append(addresses, as...)
} else {
return nil, err
}
log.Println("Processing routes")
if rs, err := processRouteConfig(config, fmt.Sprintf("interface.%d.", i)); err == nil {
routes = append(routes, rs...)
} else {
return nil, err
}
if mac, ok := config[fmt.Sprintf("interface.%d.mac", i)]; ok {
log.Printf("Parsing interface %d MAC address: %q", i, mac)
if hwaddr, err := net.ParseMAC(mac); err == nil {
iface.hwaddr = hwaddr
} else {
return nil, fmt.Errorf("error while parsing MAC address: %v", err)
}
}
if name, ok := config[fmt.Sprintf("interface.%d.name", i)]; ok {
log.Printf("Parsing interface %d name: %q", i, name)
iface.name = name
}
if len(addresses) > 0 || len(routes) > 0 {
iface.config = configMethodStatic{
hwaddress: iface.hwaddr,
addresses: addresses,
nameservers: nameservers,
domains: domains,
routes: routes,
}
} else if dhcp {
iface.config = configMethodDHCP{
hwaddress: iface.hwaddr,
}
} else {
break
}
interfaces = append(interfaces, iface)
}
return interfaces, nil
}
func processAddressConfig(config map[string]string, prefix string) (addresses []net.IPNet, err error) {
for a := 0; ; a++ {
prefix := fmt.Sprintf("%sip.%d.", prefix, a)
addressStr, ok := config[prefix+"address"]
if !ok {
break
}
ip, network, err := net.ParseCIDR(addressStr)
if err != nil {
return nil, fmt.Errorf("invalid address: %q", addressStr)
}
addresses = append(addresses, net.IPNet{
IP: ip,
Mask: network.Mask,
})
}
return
}
func processRouteConfig(config map[string]string, prefix string) (routes []route, err error) {
for r := 0; ; r++ {
prefix := fmt.Sprintf("%sroute.%d.", prefix, r)
gatewayStr, gok := config[prefix+"gateway"]
destinationStr, dok := config[prefix+"destination"]
if gok && !dok {
return nil, fmt.Errorf("missing destination key")
} else if !gok && dok {
return nil, fmt.Errorf("missing gateway key")
} else if !gok && !dok {
break
}
gateway := net.ParseIP(gatewayStr)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %q", gatewayStr)
}
_, destination, err := net.ParseCIDR(destinationStr)
if err != nil {
return nil, err
}
routes = append(routes, route{
destination: *destination,
gateway: gateway,
})
}
return
}
func processDHCPConfig(config map[string]string, prefix string) (dhcp bool, err error) {
dhcpStr, ok := config[prefix+"dhcp"]
if !ok {
return false, nil
}
switch dhcpStr {
case "yes":
return true, nil
case "no":
return false, nil
default:
return false, fmt.Errorf("invalid DHCP option: %q", dhcpStr)
}
}

View File

@@ -0,0 +1,365 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package network
import (
"errors"
"net"
"reflect"
"testing"
)
func mustParseMac(mac net.HardwareAddr, err error) net.HardwareAddr {
if err != nil {
panic(err)
}
return mac
}
func TestProcessVMwareNetconf(t *testing.T) {
tests := []struct {
config map[string]string
interfaces []InterfaceGenerator
err error
}{
{},
{
config: map[string]string{
"interface.0.dhcp": "yes",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
config: configMethodDHCP{},
}},
},
},
{
config: map[string]string{
"interface.0.mac": "00:11:22:33:44:55",
"interface.0.dhcp": "yes",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55"))},
}},
},
},
{
config: map[string]string{
"interface.0.name": "eth0",
"interface.0.dhcp": "yes",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
name: "eth0",
config: configMethodDHCP{},
}},
},
},
{
config: map[string]string{
"interface.0.mac": "00:11:22:33:44:55",
"interface.0.ip.0.address": "10.0.0.100/24",
"interface.0.route.0.gateway": "10.0.0.1",
"interface.0.route.0.destination": "0.0.0.0/0",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodStatic{
hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
// I realize how upset you must be that I am shoving an IPMask into an IP. This is because net.IPv4zero is
// actually a magic IPv6 address which ruins our equality check. What's that? Just use IP::Equal()? I'd rather
// DeepEqual just handle that for me, but until Go gets operator overloading, we are stuck with this.
routes: []route{{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.0.1")},
},
},
}},
},
},
{
config: map[string]string{
"dns.server.0": "1.2.3.4",
"dns.server.1": "5.6.7.8",
"dns.domain.0": "coreos.com",
"dns.domain.1": "example.com",
"interface.0.mac": "00:11:22:33:44:55",
"interface.0.ip.0.address": "10.0.0.100/24",
"interface.0.ip.1.address": "10.0.0.101/24",
"interface.0.route.0.gateway": "10.0.0.1",
"interface.0.route.0.destination": "0.0.0.0/0",
"interface.1.name": "eth0",
"interface.1.ip.0.address": "10.0.1.100/24",
"interface.1.route.0.gateway": "10.0.1.1",
"interface.1.route.0.destination": "0.0.0.0/0",
"interface.2.dhcp": "yes",
"interface.2.mac": "00:11:22:33:44:77",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodStatic{
hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
addresses: []net.IPNet{
{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
},
routes: []route{{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.0.1")},
},
nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")},
domains: []string{"coreos.com", "example.com"},
},
}},
&physicalInterface{logicalInterface{
name: "eth0",
config: configMethodStatic{
addresses: []net.IPNet{{IP: net.ParseIP("10.0.1.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
routes: []route{{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.1.1")},
},
nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")},
domains: []string{"coreos.com", "example.com"},
},
}},
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:77")),
config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:77"))},
}},
},
},
{
config: map[string]string{"dns.server.0": "test dns"},
err: errors.New(`invalid nameserver: "test dns"`),
},
}
for i, tt := range tests {
interfaces, err := ProcessVMwareNetconf(tt.config)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if !reflect.DeepEqual(tt.interfaces, interfaces) {
t.Errorf("bad interfaces (#%d): want %#v, got %#v", i, tt.interfaces, interfaces)
for _, iface := range tt.interfaces {
t.Logf(" want: %#v", iface)
}
for _, iface := range interfaces {
t.Logf(" got: %#v", iface)
}
}
}
}
func TestProcessAddressConfig(t *testing.T) {
tests := []struct {
config map[string]string
prefix string
addresses []net.IPNet
err error
}{
{},
// static - ipv4
{
config: map[string]string{
"ip.0.address": "10.0.0.100/24",
},
addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
},
{
config: map[string]string{
"this.is.a.prefix.ip.0.address": "10.0.0.100/24",
},
prefix: "this.is.a.prefix.",
addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
},
{
config: map[string]string{
"ip.0.address": "10.0.0.100/24",
"ip.1.address": "10.0.0.101/24",
"ip.2.address": "10.0.0.102/24",
},
addresses: []net.IPNet{
{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.102"), Mask: net.CIDRMask(24, net.IPv4len*8)},
},
},
// static - ipv6
{
config: map[string]string{
"ip.0.address": "fe00::100/64",
},
addresses: []net.IPNet{{IP: net.ParseIP("fe00::100"), Mask: net.IPMask(net.CIDRMask(64, net.IPv6len*8))}},
},
{
config: map[string]string{
"ip.0.address": "fe00::100/64",
"ip.1.address": "fe00::101/64",
"ip.2.address": "fe00::102/64",
},
addresses: []net.IPNet{
{IP: net.ParseIP("fe00::100"), Mask: net.CIDRMask(64, net.IPv6len*8)},
{IP: net.ParseIP("fe00::101"), Mask: net.CIDRMask(64, net.IPv6len*8)},
{IP: net.ParseIP("fe00::102"), Mask: net.CIDRMask(64, net.IPv6len*8)},
},
},
// invalid
{
config: map[string]string{
"ip.0.address": "test address",
},
err: errors.New(`invalid address: "test address"`),
},
}
for i, tt := range tests {
addresses, err := processAddressConfig(tt.config, tt.prefix)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if err != nil {
continue
}
if !reflect.DeepEqual(tt.addresses, addresses) {
t.Errorf("bad addresses (#%d): want %#v, got %#v", i, tt.addresses, addresses)
}
}
}
func TestProcessRouteConfig(t *testing.T) {
tests := []struct {
config map[string]string
prefix string
routes []route
err error
}{
{},
{
config: map[string]string{
"route.0.gateway": "10.0.0.1",
"route.0.destination": "0.0.0.0/0",
},
routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}},
},
{
config: map[string]string{
"this.is.a.prefix.route.0.gateway": "10.0.0.1",
"this.is.a.prefix.route.0.destination": "0.0.0.0/0",
},
prefix: "this.is.a.prefix.",
routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}},
},
{
config: map[string]string{
"route.0.gateway": "fe00::1",
"route.0.destination": "::/0",
},
routes: []route{{destination: net.IPNet{IP: net.IPv6zero, Mask: net.IPMask(net.IPv6zero)}, gateway: net.ParseIP("fe00::1")}},
},
// invalid
{
config: map[string]string{
"route.0.gateway": "test gateway",
"route.0.destination": "0.0.0.0/0",
},
err: errors.New(`invalid gateway: "test gateway"`),
},
{
config: map[string]string{
"route.0.gateway": "10.0.0.1",
"route.0.destination": "test destination",
},
err: &net.ParseError{Type: "CIDR address", Text: "test destination"},
},
}
for i, tt := range tests {
routes, err := processRouteConfig(tt.config, tt.prefix)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if err != nil {
continue
}
if !reflect.DeepEqual(tt.routes, routes) {
t.Errorf("bad routes (#%d): want %#v, got %#v", i, tt.routes, routes)
}
}
}
func TestProcessDHCPConfig(t *testing.T) {
tests := []struct {
config map[string]string
prefix string
dhcp bool
err error
}{
{},
// prefix
{config: map[string]string{"this.is.a.prefix.mac": ""}, prefix: "this.is.a.prefix.", dhcp: false},
{config: map[string]string{"this.is.a.prefix.dhcp": "yes"}, prefix: "this.is.a.prefix.", dhcp: true},
// dhcp
{config: map[string]string{"dhcp": "yes"}, dhcp: true},
{config: map[string]string{"dhcp": "no"}, dhcp: false},
// invalid
{config: map[string]string{"dhcp": "blah"}, err: errors.New(`invalid DHCP option: "blah"`)},
}
for i, tt := range tests {
dhcp, err := processDHCPConfig(tt.config, tt.prefix)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if err != nil {
continue
}
if tt.dhcp != dhcp {
t.Errorf("bad dhcp (#%d): want %v, got %v", i, tt.dhcp, dhcp)
}
}
}