4a2e417781
Logical Interfaces can be assigned a hardware address allowing them to match on MAC address. The static config method also needs to support specifying multiple addresses.
323 lines
8.0 KiB
Go
323 lines
8.0 KiB
Go
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
|
|
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":
|
|
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
|
|
}
|