Merge pull request #149 from crawford/network
feat(network): Add support for blind interfaces, support for hwaddress, and bug fixes
This commit is contained in:
commit
8eb0636034
27
Documentation/debian-interfaces.md
Normal file
27
Documentation/debian-interfaces.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#Debian Interfaces#
|
||||||
|
**WARNING**: This option is EXPERIMENTAL and may change or be removed at any
|
||||||
|
point.
|
||||||
|
There is basic support for converting from a Debian network configuration to
|
||||||
|
networkd unit files. The -convert-netconf=debian option is used to activate
|
||||||
|
this feature.
|
||||||
|
|
||||||
|
#convert-netconf#
|
||||||
|
Default: ""
|
||||||
|
Read the network config provided in cloud-drive and translate it from the
|
||||||
|
specified format into networkd unit files (requires the -from-configdrive
|
||||||
|
flag). Currently only supports "debian" which provides support for a small
|
||||||
|
subset of the [Debian network configuration]
|
||||||
|
(https://wiki.debian.org/NetworkConfiguration). These options include:
|
||||||
|
|
||||||
|
- interface config methods
|
||||||
|
- static
|
||||||
|
- address/netmask
|
||||||
|
- gateway
|
||||||
|
- hwaddress
|
||||||
|
- dns-nameservers
|
||||||
|
- dhcp
|
||||||
|
- hwaddress
|
||||||
|
- manual
|
||||||
|
- loopback
|
||||||
|
- vlan_raw_device
|
||||||
|
- bond-slaves
|
@ -7,15 +7,23 @@ import (
|
|||||||
|
|
||||||
type InterfaceGenerator interface {
|
type InterfaceGenerator interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
Filename() string
|
||||||
Netdev() string
|
Netdev() string
|
||||||
Link() string
|
Link() string
|
||||||
Network() string
|
Network() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type networkInterface interface {
|
||||||
|
InterfaceGenerator
|
||||||
|
Children() []networkInterface
|
||||||
|
setConfigDepth(int)
|
||||||
|
}
|
||||||
|
|
||||||
type logicalInterface struct {
|
type logicalInterface struct {
|
||||||
name string
|
name string
|
||||||
config configMethod
|
config configMethod
|
||||||
children []InterfaceGenerator
|
children []networkInterface
|
||||||
|
configDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *logicalInterface) Network() string {
|
func (i *logicalInterface) Network() string {
|
||||||
@ -48,6 +56,22 @@ func (i *logicalInterface) Network() string {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) Link() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) Filename() string {
|
||||||
|
return fmt.Sprintf("%02x-%s", i.configDepth, i.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) Children() []networkInterface {
|
||||||
|
return i.children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *logicalInterface) setConfigDepth(depth int) {
|
||||||
|
i.configDepth = depth
|
||||||
|
}
|
||||||
|
|
||||||
type physicalInterface struct {
|
type physicalInterface struct {
|
||||||
logicalInterface
|
logicalInterface
|
||||||
}
|
}
|
||||||
@ -60,10 +84,6 @@ func (p *physicalInterface) Netdev() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *physicalInterface) Link() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type bondInterface struct {
|
type bondInterface struct {
|
||||||
logicalInterface
|
logicalInterface
|
||||||
slaves []string
|
slaves []string
|
||||||
@ -77,10 +97,6 @@ func (b *bondInterface) Netdev() string {
|
|||||||
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bondInterface) Link() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type vlanInterface struct {
|
type vlanInterface struct {
|
||||||
logicalInterface
|
logicalInterface
|
||||||
id int
|
id int
|
||||||
@ -92,102 +108,146 @@ func (v *vlanInterface) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *vlanInterface) Netdev() string {
|
func (v *vlanInterface) Netdev() string {
|
||||||
return fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n\n[VLAN]\nId=%d\n", v.name, v.id)
|
config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
|
||||||
}
|
switch c := v.config.(type) {
|
||||||
|
case configMethodStatic:
|
||||||
func (v *vlanInterface) Link() string {
|
if c.hwaddress != nil {
|
||||||
return ""
|
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 {
|
func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
|
||||||
bondStanzas := make(map[string]*stanzaInterface)
|
interfaceMap := createInterfaces(stanzas)
|
||||||
physicalStanzas := make(map[string]*stanzaInterface)
|
linkAncestors(interfaceMap)
|
||||||
vlanStanzas := make(map[string]*stanzaInterface)
|
markConfigDepths(interfaceMap)
|
||||||
for _, iface := range stanzas {
|
|
||||||
switch iface.kind {
|
|
||||||
case interfaceBond:
|
|
||||||
bondStanzas[iface.name] = iface
|
|
||||||
case interfacePhysical:
|
|
||||||
physicalStanzas[iface.name] = iface
|
|
||||||
case interfaceVLAN:
|
|
||||||
vlanStanzas[iface.name] = iface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
physicals := make(map[string]*physicalInterface)
|
interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
|
||||||
for _, p := range physicalStanzas {
|
for _, iface := range interfaceMap {
|
||||||
if _, ok := p.configMethod.(configMethodLoopback); ok {
|
interfaces = append(interfaces, iface)
|
||||||
continue
|
|
||||||
}
|
|
||||||
physicals[p.name] = &physicalInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: p.name,
|
|
||||||
config: p.configMethod,
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bonds := make(map[string]*bondInterface)
|
|
||||||
for _, b := range bondStanzas {
|
|
||||||
bonds[b.name] = &bondInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: b.name,
|
|
||||||
config: b.configMethod,
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
b.options["slaves"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vlans := make(map[string]*vlanInterface)
|
|
||||||
for _, v := range vlanStanzas {
|
|
||||||
var rawDevice string
|
|
||||||
id, _ := strconv.Atoi(v.options["id"][0])
|
|
||||||
if device := v.options["raw_device"]; len(device) == 1 {
|
|
||||||
rawDevice = device[0]
|
|
||||||
}
|
|
||||||
vlans[v.name] = &vlanInterface{
|
|
||||||
logicalInterface{
|
|
||||||
name: v.name,
|
|
||||||
config: v.configMethod,
|
|
||||||
children: []InterfaceGenerator{},
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
rawDevice,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vlan := range vlans {
|
|
||||||
if physical, ok := physicals[vlan.rawDevice]; ok {
|
|
||||||
physical.children = append(physical.children, vlan)
|
|
||||||
}
|
|
||||||
if bond, ok := bonds[vlan.rawDevice]; ok {
|
|
||||||
bond.children = append(bond.children, vlan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bond := range bonds {
|
|
||||||
for _, slave := range bond.slaves {
|
|
||||||
if physical, ok := physicals[slave]; ok {
|
|
||||||
physical.children = append(physical.children, bond)
|
|
||||||
}
|
|
||||||
if pBond, ok := bonds[slave]; ok {
|
|
||||||
pBond.children = append(pBond.children, bond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaces := make([]InterfaceGenerator, 0, len(physicals)+len(bonds)+len(vlans))
|
|
||||||
for _, physical := range physicals {
|
|
||||||
interfaces = append(interfaces, physical)
|
|
||||||
}
|
|
||||||
for _, bond := range bonds {
|
|
||||||
interfaces = append(interfaces, bond)
|
|
||||||
}
|
|
||||||
for _, vlan := range vlans {
|
|
||||||
interfaces = append(interfaces, vlan)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return interfaces
|
return interfaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
|
||||||
|
interfaceMap := make(map[string]networkInterface)
|
||||||
|
for _, iface := range stanzas {
|
||||||
|
switch iface.kind {
|
||||||
|
case interfaceBond:
|
||||||
|
interfaceMap[iface.name] = &bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: iface.name,
|
||||||
|
config: iface.configMethod,
|
||||||
|
children: []networkInterface{},
|
||||||
|
},
|
||||||
|
iface.options["slaves"],
|
||||||
|
}
|
||||||
|
for _, slave := range iface.options["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 _, iface := range interfaceMap {
|
||||||
|
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, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDepth(iface networkInterface, depth int) {
|
||||||
|
iface.setConfigDepth(depth)
|
||||||
|
for _, child := range iface.Children() {
|
||||||
|
setDepth(child, depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ func TestPhysicalInterfaceLink(t *testing.T) {
|
|||||||
func TestPhysicalInterfaceNetwork(t *testing.T) {
|
func TestPhysicalInterfaceNetwork(t *testing.T) {
|
||||||
p := physicalInterface{logicalInterface{
|
p := physicalInterface{logicalInterface{
|
||||||
name: "testname",
|
name: "testname",
|
||||||
children: []InterfaceGenerator{
|
children: []networkInterface{
|
||||||
&bondInterface{
|
&bondInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "testbond1",
|
name: "testbond1",
|
||||||
@ -96,7 +96,7 @@ func TestBondInterfaceNetwork(t *testing.T) {
|
|||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "testname",
|
name: "testname",
|
||||||
config: configMethodDHCP{},
|
config: configMethodDHCP{},
|
||||||
children: []InterfaceGenerator{
|
children: []networkInterface{
|
||||||
&bondInterface{
|
&bondInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "testbond1",
|
name: "testbond1",
|
||||||
@ -143,16 +143,26 @@ func TestVLANInterfaceName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestVLANInterfaceNetdev(t *testing.T) {
|
func TestVLANInterfaceNetdev(t *testing.T) {
|
||||||
v := vlanInterface{logicalInterface{name: "testname"}, 1, ""}
|
for _, tt := range []struct {
|
||||||
netdev := `[NetDev]
|
i vlanInterface
|
||||||
Kind=vlan
|
l string
|
||||||
Name=testname
|
}{
|
||||||
|
{
|
||||||
[VLAN]
|
vlanInterface{logicalInterface{name: "testname"}, 1, ""},
|
||||||
Id=1
|
"[NetDev]\nKind=vlan\nName=testname\n\n[VLAN]\nId=1\n",
|
||||||
`
|
},
|
||||||
if v.Netdev() != netdev {
|
{
|
||||||
t.FailNow()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +234,80 @@ func TestBuildInterfacesLo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildInterfacesBlindBond(t *testing.T) {
|
||||||
|
stanzas := []*stanzaInterface{
|
||||||
|
{
|
||||||
|
name: "bond0",
|
||||||
|
kind: interfaceBond,
|
||||||
|
auto: false,
|
||||||
|
configMethod: configMethodManual{},
|
||||||
|
options: map[string][]string{
|
||||||
|
"slaves": []string{"eth0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
interfaces := buildInterfaces(stanzas)
|
||||||
|
bond0 := &bondInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "bond0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []networkInterface{},
|
||||||
|
configDepth: 1,
|
||||||
|
},
|
||||||
|
[]string{"eth0"},
|
||||||
|
}
|
||||||
|
eth0 := &physicalInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "eth0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []networkInterface{bond0},
|
||||||
|
configDepth: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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": []string{"0"},
|
||||||
|
"raw_device": []string{"eth0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
interfaces := buildInterfaces(stanzas)
|
||||||
|
vlan0 := &vlanInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "vlan0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []networkInterface{},
|
||||||
|
configDepth: 1,
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
"eth0",
|
||||||
|
}
|
||||||
|
eth0 := &physicalInterface{
|
||||||
|
logicalInterface{
|
||||||
|
name: "eth0",
|
||||||
|
config: configMethodManual{},
|
||||||
|
children: []networkInterface{vlan0},
|
||||||
|
configDepth: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect := []InterfaceGenerator{eth0, vlan0}
|
||||||
|
if !reflect.DeepEqual(interfaces, expect) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildInterfaces(t *testing.T) {
|
func TestBuildInterfaces(t *testing.T) {
|
||||||
stanzas := []*stanzaInterface{
|
stanzas := []*stanzaInterface{
|
||||||
&stanzaInterface{
|
&stanzaInterface{
|
||||||
@ -275,43 +359,48 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
interfaces := buildInterfaces(stanzas)
|
interfaces := buildInterfaces(stanzas)
|
||||||
vlan1 := &vlanInterface{
|
vlan1 := &vlanInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "vlan1",
|
name: "vlan1",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []InterfaceGenerator{},
|
children: []networkInterface{},
|
||||||
|
configDepth: 2,
|
||||||
},
|
},
|
||||||
1,
|
1,
|
||||||
"bond0",
|
"bond0",
|
||||||
}
|
}
|
||||||
vlan0 := &vlanInterface{
|
vlan0 := &vlanInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "vlan0",
|
name: "vlan0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []InterfaceGenerator{},
|
children: []networkInterface{},
|
||||||
|
configDepth: 1,
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
"eth0",
|
"eth0",
|
||||||
}
|
}
|
||||||
bond1 := &bondInterface{
|
bond1 := &bondInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "bond1",
|
name: "bond1",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []InterfaceGenerator{},
|
children: []networkInterface{},
|
||||||
|
configDepth: 2,
|
||||||
},
|
},
|
||||||
[]string{"bond0"},
|
[]string{"bond0"},
|
||||||
}
|
}
|
||||||
bond0 := &bondInterface{
|
bond0 := &bondInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "bond0",
|
name: "bond0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []InterfaceGenerator{vlan1, bond1},
|
children: []networkInterface{bond1, vlan1},
|
||||||
|
configDepth: 1,
|
||||||
},
|
},
|
||||||
[]string{"eth0"},
|
[]string{"eth0"},
|
||||||
}
|
}
|
||||||
eth0 := &physicalInterface{
|
eth0 := &physicalInterface{
|
||||||
logicalInterface{
|
logicalInterface{
|
||||||
name: "eth0",
|
name: "eth0",
|
||||||
config: configMethodManual{},
|
config: configMethodManual{},
|
||||||
children: []InterfaceGenerator{vlan0, bond0},
|
children: []networkInterface{bond0, vlan0},
|
||||||
|
configDepth: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
expect := []InterfaceGenerator{eth0, bond0, bond1, vlan0, vlan1}
|
||||||
@ -319,3 +408,19 @@ func TestBuildInterfaces(t *testing.T) {
|
|||||||
t.FailNow()
|
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"},
|
||||||
|
} {
|
||||||
|
if tt.i.Filename() != tt.f {
|
||||||
|
t.Fatalf("bad filename (%q): got %q, want %q", tt.i, tt.i.Filename(), tt.f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -40,13 +40,16 @@ type configMethodStatic struct {
|
|||||||
address net.IPNet
|
address net.IPNet
|
||||||
nameservers []net.IP
|
nameservers []net.IP
|
||||||
routes []route
|
routes []route
|
||||||
|
hwaddress net.HardwareAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
type configMethodLoopback struct{}
|
type configMethodLoopback struct{}
|
||||||
|
|
||||||
type configMethodManual struct{}
|
type configMethodManual struct{}
|
||||||
|
|
||||||
type configMethodDHCP struct{}
|
type configMethodDHCP struct {
|
||||||
|
hwaddress net.HardwareAddr
|
||||||
|
}
|
||||||
|
|
||||||
func parseStanzas(lines []string) (stanzas []stanza, err error) {
|
func parseStanzas(lines []string) (stanzas []stanza, err error) {
|
||||||
rawStanzas, err := splitStanzas(lines)
|
rawStanzas, err := splitStanzas(lines)
|
||||||
@ -96,7 +99,7 @@ func splitStanzas(lines []string) ([][]string, error) {
|
|||||||
} else if curStanza != nil {
|
} else if curStanza != nil {
|
||||||
curStanza = append(curStanza, line)
|
curStanza = append(curStanza, line)
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("missing stanza start '%s'", line)
|
return nil, fmt.Errorf("missing stanza start %q", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +145,7 @@ func parseStanza(rawStanza []string) (stanza, error) {
|
|||||||
case "iface":
|
case "iface":
|
||||||
return parseInterfaceStanza(attributes, rawStanza[1:])
|
return parseInterfaceStanza(attributes, rawStanza[1:])
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown stanza '%s'", kind)
|
return nil, fmt.Errorf("unknown stanza %q", kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +207,7 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.address.IP == nil || config.address.Mask == nil {
|
if config.address.IP == nil || config.address.Mask == nil {
|
||||||
return nil, fmt.Errorf("malformed static network config for '%s'", iface)
|
return nil, fmt.Errorf("malformed static network config for %q", iface)
|
||||||
}
|
}
|
||||||
if gateways, ok := optionMap["gateway"]; ok {
|
if gateways, ok := optionMap["gateway"]; ok {
|
||||||
if len(gateways) == 1 {
|
if len(gateways) == 1 {
|
||||||
@ -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"] {
|
for _, nameserver := range optionMap["dns-nameservers"] {
|
||||||
config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
|
config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
|
||||||
}
|
}
|
||||||
@ -245,9 +253,15 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
|||||||
case "manual":
|
case "manual":
|
||||||
conf = configMethodManual{}
|
conf = configMethodManual{}
|
||||||
case "dhcp":
|
case "dhcp":
|
||||||
conf = configMethodDHCP{}
|
config := configMethodDHCP{}
|
||||||
|
if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
|
||||||
|
config.hwaddress = hwaddress
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf = config
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid config method '%s'", confMethod)
|
return nil, fmt.Errorf("invalid config method %q", confMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := optionMap["vlan_raw_device"]; ok {
|
if _, ok := optionMap["vlan_raw_device"]; ok {
|
||||||
@ -265,6 +279,19 @@ func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterfa
|
|||||||
return parsePhysicalStanza(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) {
|
func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
|
||||||
options["slaves"] = options["bond-slaves"]
|
options["slaves"] = options["bond-slaves"]
|
||||||
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
|
||||||
@ -282,11 +309,11 @@ func parseVLANStanza(iface string, conf configMethod, attributes []string, optio
|
|||||||
} else if strings.HasPrefix(iface, "vlan") {
|
} else if strings.HasPrefix(iface, "vlan") {
|
||||||
id = strings.TrimPrefix(iface, "vlan")
|
id = strings.TrimPrefix(iface, "vlan")
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
return nil, fmt.Errorf("malformed vlan name %q", iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := strconv.Atoi(id); err != nil {
|
if _, err := strconv.Atoi(id); err != nil {
|
||||||
return nil, fmt.Errorf("malformed vlan name %s", iface)
|
return nil, fmt.Errorf("malformed vlan name %q", iface)
|
||||||
}
|
}
|
||||||
options["id"] = []string{id}
|
options["id"] = []string{id}
|
||||||
options["raw_device"] = options["vlan_raw_device"]
|
options["raw_device"] = options["vlan_raw_device"]
|
||||||
|
@ -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{"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 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 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)
|
_, err := parseInterfaceStanza(tt.in, tt.opts)
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), tt.e) {
|
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"})
|
iface, err := parseInterfaceStanza([]string{"mybond", "inet", "manual"}, []string{"bond-slaves eth"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.FailNow()
|
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)
|
iface, err := parseInterfaceStanza([]string{"eth0.1", "inet", "manual"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.FailNow()
|
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"})
|
iface, err := parseInterfaceStanza([]string{"vlan1", "inet", "manual"}, []string{"vlan_raw_device eth"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
@ -36,7 +36,9 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
|||||||
sysInterfaceMap := make(map[string]*net.Interface)
|
sysInterfaceMap := make(map[string]*net.Interface)
|
||||||
if systemInterfaces, err := net.Interfaces(); err == nil {
|
if systemInterfaces, err := net.Interfaces(); err == nil {
|
||||||
for _, iface := range systemInterfaces {
|
for _, iface := range systemInterfaces {
|
||||||
sysInterfaceMap[iface.Name] = &iface
|
// Need a copy of the interface so we can take the address
|
||||||
|
temp := iface
|
||||||
|
sysInterfaceMap[temp.Name] = &temp
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
@ -64,15 +66,15 @@ func restartNetworkd() error {
|
|||||||
|
|
||||||
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
func WriteNetworkdConfigs(interfaces []network.InterfaceGenerator) error {
|
||||||
for _, iface := range interfaces {
|
for _, iface := range interfaces {
|
||||||
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Name()))
|
filename := path.Join(runtimeNetworkPath, fmt.Sprintf("%s.netdev", iface.Filename()))
|
||||||
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
if err := writeConfig(filename, iface.Netdev()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Name()))
|
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.link", iface.Filename()))
|
||||||
if err := writeConfig(filename, iface.Link()); err != nil {
|
if err := writeConfig(filename, iface.Link()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Name()))
|
filename = path.Join(runtimeNetworkPath, fmt.Sprintf("%s.network", iface.Filename()))
|
||||||
if err := writeConfig(filename, iface.Network()); err != nil {
|
if err := writeConfig(filename, iface.Network()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user