// 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 }