diff --git a/network/interface.go b/network/interface.go index 81d3803..070c4ea 100644 --- a/network/interface.go +++ b/network/interface.go @@ -48,6 +48,10 @@ func (i *logicalInterface) Network() string { return config } +func (i *logicalInterface) Link() string { + return "" +} + type physicalInterface struct { logicalInterface } @@ -60,10 +64,6 @@ func (p *physicalInterface) Netdev() string { return "" } -func (p *physicalInterface) Link() string { - return "" -} - type bondInterface struct { logicalInterface slaves []string @@ -77,10 +77,6 @@ func (b *bondInterface) Netdev() string { return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name) } -func (b *bondInterface) Link() string { - return "" -} - type vlanInterface struct { logicalInterface id int @@ -92,11 +88,19 @@ func (v *vlanInterface) Name() string { } func (v *vlanInterface) Netdev() string { - return fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n\n[VLAN]\nId=%d\n", v.name, v.id) -} - -func (v *vlanInterface) Link() string { - return "" + 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 buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator { diff --git a/network/interface_test.go b/network/interface_test.go index b986656..25b4c3b 100644 --- a/network/interface_test.go +++ b/network/interface_test.go @@ -143,16 +143,26 @@ func TestVLANInterfaceName(t *testing.T) { } func TestVLANInterfaceNetdev(t *testing.T) { - v := vlanInterface{logicalInterface{name: "testname"}, 1, ""} - netdev := `[NetDev] -Kind=vlan -Name=testname - -[VLAN] -Id=1 -` - if v.Netdev() != netdev { - t.FailNow() + for _, tt := range []struct { + i vlanInterface + l string + }{ + { + vlanInterface{logicalInterface{name: "testname"}, 1, ""}, + "[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n", + }, + { + vlanInterface{logicalInterface{name: "testname", config: configMethodStatic{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""}, + "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n", + }, + { + vlanInterface{logicalInterface{name: "testname", config: configMethodDHCP{hwaddress: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5})}}, 1, ""}, + "[NetDev]\nKind=vlan\nName=testname\nMACAddress=00:01:02:03:04:05\n\n[VLAN]\nId=1\n", + }, + } { + if tt.i.Netdev() != tt.l { + t.Fatalf("bad netdev config (%q): got %q, want %q", tt.i, tt.i.Netdev(), tt.l) + } } } diff --git a/network/stanza.go b/network/stanza.go index a6db8c4..005231b 100644 --- a/network/stanza.go +++ b/network/stanza.go @@ -40,13 +40,16 @@ type configMethodStatic struct { address net.IPNet nameservers []net.IP routes []route + hwaddress net.HardwareAddr } type configMethodLoopback struct{} type configMethodManual struct{} -type configMethodDHCP struct{} +type configMethodDHCP struct { + hwaddress net.HardwareAddr +} func parseStanzas(lines []string) (stanzas []stanza, err error) { rawStanzas, err := splitStanzas(lines) @@ -217,6 +220,11 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa }) } } + 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)) } @@ -245,7 +253,13 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa case "manual": conf = configMethodManual{} case "dhcp": - conf = configMethodDHCP{} + 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) } @@ -265,6 +279,19 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa 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) { options["slaves"] = options["bond-slaves"] return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil diff --git a/network/stanza_test.go b/network/stanza_test.go index bddd292..440dcf8 100644 --- a/network/stanza_test.go +++ b/network/stanza_test.go @@ -42,6 +42,8 @@ func TestBadParseInterfaceStanza(t *testing.T) { {[]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) { @@ -407,7 +409,46 @@ func TestParseInterfaceStanzaOptions(t *testing.T) { } } -func TestParseInterfaceStazaBond(t *testing.T) { +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() @@ -417,7 +458,7 @@ func TestParseInterfaceStazaBond(t *testing.T) { } } -func TestParseInterfaceStazaVLANName(t *testing.T) { +func TestParseInterfaceStanzaVLANName(t *testing.T) { iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil) if err != nil { t.FailNow() @@ -427,7 +468,7 @@ func TestParseInterfaceStazaVLANName(t *testing.T) { } } -func TestParseInterfaceStazaVLANOption(t *testing.T) { +func TestParseInterfaceStanzaVLANOption(t *testing.T) { iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"}) if err != nil { t.FailNow()