be68a8e5cc
Build tags are not compatible with block comments. Also adds copyright header to a few places it was missing.
341 lines
8.6 KiB
Go
341 lines
8.6 KiB
Go
// 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
|
|
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
|
|
}
|