package common import ( "fmt" "io" "log" "math" "net" "os" "reflect" "sort" "strconv" "strings" ) type sentinelSignaller chan struct{} /** low-level parsing */ // strip the comments and extraneous newlines from a byte channel func uncomment(eof sentinelSignaller, in <-chan byte) (chan byte, sentinelSignaller) { out := make(chan byte) eoc := make(sentinelSignaller) go func(in <-chan byte, out chan byte) { var endofline bool for stillReading := true; stillReading; { select { case <-eof: stillReading = false case ch := <-in: switch ch { case '#': endofline = true case '\n': if endofline { endofline = false } } if !endofline { out <- ch } } } close(eoc) }(in, out) return out, eoc } // convert a byte channel into a channel of pseudo-tokens func tokenizeDhcpConfig(eof sentinelSignaller, in chan byte) (chan string, sentinelSignaller) { var ch byte var state string var quote bool eot := make(sentinelSignaller) out := make(chan string) go func(out chan string) { for stillReading := true; stillReading; { select { case <-eof: stillReading = false case ch = <-in: if quote { if ch == '"' { out <- state + string(ch) state, quote = "", false continue } state += string(ch) continue } switch ch { case '"': quote = true state += string(ch) continue case '\r': fallthrough case '\n': fallthrough case '\t': fallthrough case ' ': if len(state) == 0 { continue } out <- state state = "" case '{': fallthrough case '}': fallthrough case ';': if len(state) > 0 { out <- state } out <- string(ch) state = "" default: state += string(ch) } } } if len(state) > 0 { out <- state } close(eot) }(out) return out, eot } /** mid-level parsing */ type tkParameter struct { name string operand []string } func (e *tkParameter) String() string { var values []string for _, val := range e.operand { values = append(values, val) } return fmt.Sprintf("%s [%s]", e.name, strings.Join(values, ",")) } type tkGroup struct { parent *tkGroup id tkParameter groups []*tkGroup params []tkParameter } func (e *tkGroup) String() string { var id []string id = append(id, e.id.name) for _, val := range e.id.operand { id = append(id, val) } var config []string for _, val := range e.params { config = append(config, val.String()) } return fmt.Sprintf("%s {\n%s\n}", strings.Join(id, " "), strings.Join(config, "\n")) } // convert a channel of pseudo-tokens into an tkParameter struct func parseTokenParameter(in chan string) tkParameter { var result tkParameter for { token := <-in if result.name == "" { result.name = token continue } switch token { case "{": fallthrough case "}": fallthrough case ";": goto leave default: result.operand = append(result.operand, token) } } leave: return result } // convert a channel of pseudo-tokens into an tkGroup tree */ func parseDhcpConfig(eof sentinelSignaller, in chan string) (tkGroup, error) { var tokens []string var result tkGroup toParameter := func(tokens []string) tkParameter { out := make(chan string) go func(out chan string) { for _, v := range tokens { out <- v } out <- ";" }(out) return parseTokenParameter(out) } for stillReading, currentgroup := true, &result; stillReading; { select { case <-eof: stillReading = false case tk := <-in: switch tk { case "{": grp := &tkGroup{parent: currentgroup} grp.id = toParameter(tokens) currentgroup.groups = append(currentgroup.groups, grp) currentgroup = grp case "}": if currentgroup.parent == nil { return tkGroup{}, fmt.Errorf("Unable to close the global declaration") } if len(tokens) > 0 { return tkGroup{}, fmt.Errorf("List of tokens was left unterminated : %v", tokens) } currentgroup = currentgroup.parent case ";": arg := toParameter(tokens) currentgroup.params = append(currentgroup.params, arg) default: tokens = append(tokens, tk) continue } tokens = []string{} } } return result, nil } func tokenizeNetworkMapConfig(eof sentinelSignaller, in chan byte) (chan string, sentinelSignaller) { var ch byte var state string var quote bool var lastnewline bool eot := make(sentinelSignaller) out := make(chan string) go func(out chan string) { for stillReading := true; stillReading; { select { case <-eof: stillReading = false case ch = <-in: if quote { if ch == '"' { out <- state + string(ch) state, quote = "", false continue } state += string(ch) continue } switch ch { case '"': quote = true state += string(ch) continue case '\r': fallthrough case '\t': fallthrough case ' ': if len(state) == 0 { continue } out <- state state = "" case '\n': if lastnewline { continue } if len(state) > 0 { out <- state } out <- string(ch) state = "" lastnewline = true continue case '.': fallthrough case '=': if len(state) > 0 { out <- state } out <- string(ch) state = "" default: state += string(ch) } lastnewline = false } } if len(state) > 0 { out <- state } close(eot) }(out) return out, eot } func parseNetworkMapConfig(eof sentinelSignaller, in chan string) (NetworkMap, error) { var unsorted map[string]map[string]string var state []string addResult := func(network string, attribute string, value string) error { _, ok := unsorted[network] if !ok { unsorted[network] = make(map[string]string) } val, err := strconv.Unquote(value) if err != nil { return err } current := unsorted[network] current[attribute] = val return nil } stillReading := true for unsorted = make(map[string]map[string]string); stillReading; { select { case <-eof: if len(state) == 3 { err := addResult(state[0], state[1], state[2]) if err != nil { return nil, err } } stillReading = false case tk := <-in: switch tk { case ".": if len(state) != 1 { return nil, fmt.Errorf("Missing network index") } case "=": if len(state) != 2 { return nil, fmt.Errorf("Assignment to empty attribute") } case "\n": if len(state) == 0 { continue } if len(state) != 3 { return nil, fmt.Errorf("Invalid attribute assignment : %v", state) } err := addResult(state[0], state[1], state[2]) if err != nil { return nil, err } state = make([]string, 0) default: state = append(state, tk) } } } result := make([]map[string]string, 0) var keys []string for k := range unsorted { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { result = append(result, unsorted[k]) } return result, nil } /** higher-level parsing */ /// parameters type pParameter interface { repr() string } type pParameterInclude struct { filename string } func (e pParameterInclude) repr() string { return fmt.Sprintf("include-file:filename=%s", e.filename) } type pParameterOption struct { name string value string } func (e pParameterOption) repr() string { return fmt.Sprintf("option:%s=%s", e.name, e.value) } // allow some-kind-of-something type pParameterGrant struct { verb string // allow,deny,ignore attribute string } func (e pParameterGrant) repr() string { return fmt.Sprintf("grant:%s,%s", e.verb, e.attribute) } type pParameterAddress4 []string func (e pParameterAddress4) repr() string { return fmt.Sprintf("fixed-address4:%s", strings.Join(e, ",")) } type pParameterAddress6 []string func (e pParameterAddress6) repr() string { return fmt.Sprintf("fixed-address6:%s", strings.Join(e, ",")) } // hardware address 00:00:00:00:00:00 type pParameterHardware struct { class string address []byte } func (e pParameterHardware) repr() string { res := make([]string, 0) for _, v := range e.address { res = append(res, fmt.Sprintf("%02x", v)) } return fmt.Sprintf("hardware-address:%s[%s]", e.class, strings.Join(res, ":")) } type pParameterBoolean struct { parameter string truancy bool } func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%v", e.parameter, e.truancy) } type pParameterClientMatch struct { name string data string } func (e pParameterClientMatch) repr() string { return fmt.Sprintf("match-client:%s=%s", e.name, e.data) } // range 127.0.0.1 127.0.0.255 type pParameterRange4 struct { min net.IP max net.IP } func (e pParameterRange4) repr() string { return fmt.Sprintf("range4:%s-%s", e.min.String(), e.max.String()) } type pParameterRange6 struct { min net.IP max net.IP } func (e pParameterRange6) repr() string { return fmt.Sprintf("range6:%s-%s", e.min.String(), e.max.String()) } type pParameterPrefix6 struct { min net.IP max net.IP bits int } func (e pParameterPrefix6) repr() string { return fmt.Sprintf("prefix6:/%d:%s-%s", e.bits, e.min.String(), e.max.String()) } // some-kind-of-parameter 1024 type pParameterOther struct { parameter string value string } func (e pParameterOther) repr() string { return fmt.Sprintf("parameter:%s=%s", e.parameter, e.value) } type pParameterExpression struct { parameter string expression string } func (e pParameterExpression) repr() string { return fmt.Sprintf("parameter-expression:%s=\"%s\"", e.parameter, e.expression) } type pDeclarationIdentifier interface { repr() string } type pDeclaration struct { id pDeclarationIdentifier parent *pDeclaration parameters []pParameter declarations []pDeclaration } func (e *pDeclaration) short() string { return e.id.repr() } func (e *pDeclaration) repr() string { res := e.short() var parameters []string for _, v := range e.parameters { parameters = append(parameters, v.repr()) } var groups []string for _, v := range e.declarations { groups = append(groups, fmt.Sprintf("-> %s", v.short())) } if e.parent != nil { res = fmt.Sprintf("%s parent:%s", res, e.parent.short()) } return fmt.Sprintf("%s\n%s\n%s\n", res, strings.Join(parameters, "\n"), strings.Join(groups, "\n")) } type pDeclarationGlobal struct{} func (e pDeclarationGlobal) repr() string { return fmt.Sprintf("{global}") } type pDeclarationShared struct{ name string } func (e pDeclarationShared) repr() string { return fmt.Sprintf("{shared-network %s}", e.name) } type pDeclarationSubnet4 struct{ net.IPNet } func (e pDeclarationSubnet4) repr() string { return fmt.Sprintf("{subnet4 %s}", e.String()) } type pDeclarationSubnet6 struct{ net.IPNet } func (e pDeclarationSubnet6) repr() string { return fmt.Sprintf("{subnet6 %s}", e.String()) } type pDeclarationHost struct{ name string } func (e pDeclarationHost) repr() string { return fmt.Sprintf("{host name:%s}", e.name) } type pDeclarationPool struct{} func (e pDeclarationPool) repr() string { return fmt.Sprintf("{pool}") } type pDeclarationGroup struct{} func (e pDeclarationGroup) repr() string { return fmt.Sprintf("{group}") } type pDeclarationClass struct{ name string } func (e pDeclarationClass) repr() string { return fmt.Sprintf("{class}") } /** parsers */ func parseParameter(val tkParameter) (pParameter, error) { switch val.name { case "include": if len(val.operand) != 2 { return nil, fmt.Errorf("Invalid number of parameters for pParameterInclude : %v", val.operand) } name := val.operand[0] return pParameterInclude{filename: name}, nil case "option": if len(val.operand) != 2 { return nil, fmt.Errorf("Invalid number of parameters for pParameterOption : %v", val.operand) } name, value := val.operand[0], val.operand[1] return pParameterOption{name: name, value: value}, nil case "allow": fallthrough case "deny": fallthrough case "ignore": if len(val.operand) < 1 { return nil, fmt.Errorf("Invalid number of parameters for pParameterGrant : %v", val.operand) } attribute := strings.Join(val.operand, " ") return pParameterGrant{verb: strings.ToLower(val.name), attribute: attribute}, nil case "range": if len(val.operand) < 1 { return nil, fmt.Errorf("Invalid number of parameters for pParameterRange4 : %v", val.operand) } idxAddress := map[bool]int{true: 1, false: 0}[strings.ToLower(val.operand[0]) == "bootp"] if len(val.operand) > 2+idxAddress { return nil, fmt.Errorf("Invalid number of parameters for pParameterRange : %v", val.operand) } if idxAddress+1 > len(val.operand) { res := net.ParseIP(val.operand[idxAddress]) return pParameterRange4{min: res, max: res}, nil } addr1 := net.ParseIP(val.operand[idxAddress]) addr2 := net.ParseIP(val.operand[idxAddress+1]) return pParameterRange4{min: addr1, max: addr2}, nil case "range6": if len(val.operand) == 1 { address := val.operand[0] if strings.Contains(address, "/") { cidr := strings.SplitN(address, "/", 2) if len(cidr) != 2 { return nil, fmt.Errorf("Unknown ipv6 format : %v", address) } address := net.ParseIP(cidr[0]) bits, err := strconv.Atoi(cidr[1]) if err != nil { return nil, err } mask := net.CIDRMask(bits, net.IPv6len*8) // figure out the network address network := address.Mask(mask) // make a broadcast address broadcast := network networkSize, totalSize := mask.Size() hostSize := totalSize - networkSize for i := networkSize / 8; i < totalSize/8; i++ { broadcast[i] = byte(0xff) } octetIndex := network[networkSize/8] bitsLeft := (uint32)(hostSize % 8) broadcast[octetIndex] = network[octetIndex] | ((1 << bitsLeft) - 1) // FIXME: check that the broadcast address was made correctly return pParameterRange6{min: network, max: broadcast}, nil } res := net.ParseIP(address) return pParameterRange6{min: res, max: res}, nil } if len(val.operand) == 2 { addr := net.ParseIP(val.operand[0]) if strings.ToLower(val.operand[1]) == "temporary" { return pParameterRange6{min: addr, max: addr}, nil } other := net.ParseIP(val.operand[1]) return pParameterRange6{min: addr, max: other}, nil } return nil, fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v", val.operand) case "prefix6": if len(val.operand) != 3 { return nil, fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v", val.operand) } bits, err := strconv.Atoi(val.operand[2]) if err != nil { return nil, fmt.Errorf("Invalid bits for pParameterPrefix6 : %v", val.operand[2]) } minaddr := net.ParseIP(val.operand[0]) maxaddr := net.ParseIP(val.operand[1]) return pParameterPrefix6{min: minaddr, max: maxaddr, bits: bits}, nil case "hardware": if len(val.operand) != 2 { return nil, fmt.Errorf("Invalid number of parameters for pParameterHardware : %v", val.operand) } class := val.operand[0] octets := strings.Split(val.operand[1], ":") address := make([]byte, 0) for _, v := range octets { b, err := strconv.ParseInt(v, 16, 0) if err != nil { return nil, err } address = append(address, byte(b)) } return pParameterHardware{class: class, address: address}, nil case "fixed-address": ip4addrs := make(pParameterAddress4, len(val.operand)) copy(ip4addrs, val.operand) return ip4addrs, nil case "fixed-address6": ip6addrs := make(pParameterAddress6, len(val.operand)) copy(ip6addrs, val.operand) return ip6addrs, nil case "host-identifier": if len(val.operand) != 3 { return nil, fmt.Errorf("Invalid number of parameters for pParameterClientMatch : %v", val.operand) } if val.operand[0] != "option" { return nil, fmt.Errorf("Invalid match parameter : %v", val.operand[0]) } optionName := val.operand[1] optionData := val.operand[2] return pParameterClientMatch{name: optionName, data: optionData}, nil default: length := len(val.operand) if length < 1 { return pParameterBoolean{parameter: val.name, truancy: true}, nil } else if length > 1 { if val.operand[0] == "=" { return pParameterExpression{parameter: val.name, expression: strings.Join(val.operand[1:], "")}, nil } } if length != 1 { return nil, fmt.Errorf("Invalid number of parameters for pParameterOther : %v", val.operand) } if strings.ToLower(val.name) == "not" { return pParameterBoolean{parameter: val.operand[0], truancy: false}, nil } return pParameterOther{parameter: val.name, value: val.operand[0]}, nil } } func parseTokenGroup(val tkGroup) (*pDeclaration, error) { params := val.id.operand switch val.id.name { case "group": return &pDeclaration{id: pDeclarationGroup{}}, nil case "pool": return &pDeclaration{id: pDeclarationPool{}}, nil case "host": if len(params) == 1 { return &pDeclaration{id: pDeclarationHost{name: params[0]}}, nil } case "subnet": if len(params) == 3 && strings.ToLower(params[1]) == "netmask" { addr := make([]byte, 4) for i, v := range strings.SplitN(params[2], ".", 4) { res, err := strconv.ParseInt(v, 10, 0) if err != nil { return nil, err } addr[i] = byte(res) } oc1, oc2, oc3, oc4 := addr[0], addr[1], addr[2], addr[3] if subnet, mask := net.ParseIP(params[0]), net.IPv4Mask(oc1, oc2, oc3, oc4); subnet != nil && mask != nil { return &pDeclaration{id: pDeclarationSubnet4{net.IPNet{IP: subnet, Mask: mask}}}, nil } } case "subnet6": if len(params) == 1 { ip6 := strings.SplitN(params[0], "/", 2) if len(ip6) == 2 && strings.Contains(ip6[0], ":") { address := net.ParseIP(ip6[0]) prefix, err := strconv.Atoi(ip6[1]) if err != nil { return nil, err } return &pDeclaration{id: pDeclarationSubnet6{net.IPNet{IP: address, Mask: net.CIDRMask(prefix, net.IPv6len*8)}}}, nil } } case "shared-network": if len(params) == 1 { return &pDeclaration{id: pDeclarationShared{name: params[0]}}, nil } case "": return &pDeclaration{id: pDeclarationGlobal{}}, nil } return nil, fmt.Errorf("Invalid pDeclaration : %v : %v", val.id.name, params) } func flattenDhcpConfig(root tkGroup) (*pDeclaration, error) { var result *pDeclaration result, err := parseTokenGroup(root) if err != nil { return nil, err } for _, p := range root.params { param, err := parseParameter(p) if err != nil { return nil, err } result.parameters = append(result.parameters, param) } for _, p := range root.groups { group, err := flattenDhcpConfig(*p) if err != nil { return nil, err } group.parent = result result.declarations = append(result.declarations, *group) } return result, nil } /** reduce the tree into the things that we care about */ type grant uint const ( ALLOW grant = iota IGNORE grant = iota DENY grant = iota ) type configDeclaration struct { id []pDeclarationIdentifier composites []pDeclaration address []pParameter options map[string]string grants map[string]grant attributes map[string]bool parameters map[string]string expressions map[string]string hostid []pParameterClientMatch } func createDeclaration(node pDeclaration) configDeclaration { var hierarchy []pDeclaration for n := &node; n != nil; n = n.parent { hierarchy = append(hierarchy, *n) } var result configDeclaration result.address = make([]pParameter, 0) result.options = make(map[string]string) result.grants = make(map[string]grant) result.attributes = make(map[string]bool) result.parameters = make(map[string]string) result.expressions = make(map[string]string) result.hostid = make([]pParameterClientMatch, 0) // walk from globals to pDeclaration collecting all parameters for i := len(hierarchy) - 1; i >= 0; i-- { result.composites = append(result.composites, hierarchy[(len(hierarchy)-1)-i]) result.id = append(result.id, hierarchy[(len(hierarchy)-1)-i].id) // update configDeclaration parameters for _, p := range hierarchy[i].parameters { switch p.(type) { case pParameterOption: result.options[p.(pParameterOption).name] = p.(pParameterOption).value case pParameterGrant: Grant := map[string]grant{"ignore": IGNORE, "allow": ALLOW, "deny": DENY} result.grants[p.(pParameterGrant).attribute] = Grant[p.(pParameterGrant).verb] case pParameterBoolean: result.attributes[p.(pParameterBoolean).parameter] = p.(pParameterBoolean).truancy case pParameterClientMatch: result.hostid = append(result.hostid, p.(pParameterClientMatch)) case pParameterExpression: result.expressions[p.(pParameterExpression).parameter] = p.(pParameterExpression).expression case pParameterOther: result.parameters[p.(pParameterOther).parameter] = p.(pParameterOther).value default: result.address = append(result.address, p) } } } return result } func (e *configDeclaration) repr() string { var result []string var res []string res = make([]string, 0) for _, v := range e.id { res = append(res, v.repr()) } result = append(result, strings.Join(res, ",")) if len(e.address) > 0 { res = make([]string, 0) for _, v := range e.address { res = append(res, v.repr()) } result = append(result, fmt.Sprintf("address : %v", strings.Join(res, ","))) } if len(e.options) > 0 { result = append(result, fmt.Sprintf("options : %v", e.options)) } if len(e.grants) > 0 { result = append(result, fmt.Sprintf("grants : %v", e.grants)) } if len(e.attributes) > 0 { result = append(result, fmt.Sprintf("attributes : %v", e.attributes)) } if len(e.parameters) > 0 { result = append(result, fmt.Sprintf("parameters : %v", e.parameters)) } if len(e.expressions) > 0 { result = append(result, fmt.Sprintf("parameter-expressions : %v", e.expressions)) } if len(e.hostid) > 0 { res = make([]string, 0) for _, v := range e.hostid { res = append(res, v.repr()) } result = append(result, fmt.Sprintf("hostid : %v", strings.Join(res, " "))) } return strings.Join(result, "\n") + "\n" } func (e *configDeclaration) IP4() (net.IP, error) { var result []string for _, entry := range e.address { switch entry.(type) { case pParameterAddress4: for _, s := range entry.(pParameterAddress4) { result = append(result, s) } } } if len(result) > 1 { return nil, fmt.Errorf("More than one address4 returned : %v", result) } else if len(result) == 0 { return nil, fmt.Errorf("No IP4 addresses found") } if res := net.ParseIP(result[0]); res != nil { return res, nil } res, err := net.ResolveIPAddr("ip4", result[0]) if err != nil { return nil, err } return res.IP, nil } func (e *configDeclaration) IP6() (net.IP, error) { var result []string for _, entry := range e.address { switch entry.(type) { case pParameterAddress6: for _, s := range entry.(pParameterAddress6) { result = append(result, s) } } } if len(result) > 1 { return nil, fmt.Errorf("More than one address6 returned : %v", result) } else if len(result) == 0 { return nil, fmt.Errorf("No IP6 addresses found") } if res := net.ParseIP(result[0]); res != nil { return res, nil } res, err := net.ResolveIPAddr("ip6", result[0]) if err != nil { return nil, err } return res.IP, nil } func (e *configDeclaration) Hardware() (net.HardwareAddr, error) { var result []pParameterHardware for _, addr := range e.address { switch addr.(type) { case pParameterHardware: result = append(result, addr.(pParameterHardware)) } } if len(result) > 0 { return nil, fmt.Errorf("More than one hardware address returned : %v", result) } res := make(net.HardwareAddr, 0) for _, by := range result[0].address { res = append(res, by) } return res, nil } /*** Dhcp Configuration */ type DhcpConfiguration []configDeclaration func ReadDhcpConfiguration(fd *os.File) (DhcpConfiguration, error) { fromfile, eof := consumeFile(fd) uncommented, eoc := uncomment(eof, fromfile) tokenized, eot := tokenizeDhcpConfig(eoc, uncommented) parsetree, err := parseDhcpConfig(eot, tokenized) if err != nil { return nil, err } global, err := flattenDhcpConfig(parsetree) if err != nil { return nil, err } var walkDeclarations func(root pDeclaration, out chan *configDeclaration) walkDeclarations = func(root pDeclaration, out chan *configDeclaration) { res := createDeclaration(root) out <- &res for _, p := range root.declarations { walkDeclarations(p, out) } } each := make(chan *configDeclaration) go func(out chan *configDeclaration) { walkDeclarations(*global, out) out <- nil }(each) var result DhcpConfiguration for decl := <-each; decl != nil; decl = <-each { result = append(result, *decl) } return result, nil } func (e *DhcpConfiguration) Global() configDeclaration { result := (*e)[0] if len(result.id) != 1 { panic(fmt.Errorf("Something that can't happen happened")) } return result } func (e *DhcpConfiguration) SubnetByAddress(address net.IP) (configDeclaration, error) { var result []configDeclaration for _, entry := range *e { switch entry.id[0].(type) { case pDeclarationSubnet4: id := entry.id[0].(pDeclarationSubnet4) if id.Contains(address) { result = append(result, entry) } case pDeclarationSubnet6: id := entry.id[0].(pDeclarationSubnet6) if id.Contains(address) { result = append(result, entry) } } } if len(result) == 0 { return configDeclaration{}, fmt.Errorf("No network declarations containing %s found", address.String()) } if len(result) > 1 { return configDeclaration{}, fmt.Errorf("More than 1 network declaration found : %v", result) } return result[0], nil } func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) { var result []configDeclaration for _, entry := range *e { switch entry.id[0].(type) { case pDeclarationHost: id := entry.id[0].(pDeclarationHost) if strings.ToLower(id.name) == strings.ToLower(host) { result = append(result, entry) } } } if len(result) == 0 { return configDeclaration{}, fmt.Errorf("No host declarations containing %s found", host) } if len(result) > 1 { return configDeclaration{}, fmt.Errorf("More than 1 host declaration found : %v", result) } return result[0], nil } /*** Network Map */ type NetworkMap []map[string]string type NetworkNameMapper interface { NameIntoDevices(string) ([]string, error) DeviceIntoName(string) (string, error) } func ReadNetworkMap(fd *os.File) (NetworkMap, error) { fromfile, eof := consumeFile(fd) uncommented, eoc := uncomment(eof, fromfile) tokenized, eot := tokenizeNetworkMapConfig(eoc, uncommented) result, err := parseNetworkMapConfig(eot, tokenized) if err != nil { return nil, err } return result, nil } func (e NetworkMap) NameIntoDevices(name string) ([]string, error) { var devices []string for _, val := range e { if strings.ToLower(val["name"]) == strings.ToLower(name) { devices = append(devices, val["device"]) } } if len(devices) > 0 { return devices, nil } return make([]string, 0), fmt.Errorf("Network name not found : %v", name) } func (e NetworkMap) DeviceIntoName(device string) (string, error) { for _, val := range e { if strings.ToLower(val["device"]) == strings.ToLower(device) { return val["name"], nil } } return "", fmt.Errorf("Device name not found : %v", device) } func (e *NetworkMap) repr() string { var result []string for idx, val := range *e { result = append(result, fmt.Sprintf("network%d.name = \"%s\"", idx, val["name"])) result = append(result, fmt.Sprintf("network%d.device = \"%s\"", idx, val["device"])) } return strings.Join(result, "\n") } /*** parser for VMware Fusion's networking file */ func tokenizeNetworkingConfig(eof sentinelSignaller, in chan byte) (chan string, sentinelSignaller) { var ch byte var state string var repeat_newline bool eot := make(sentinelSignaller) out := make(chan string) go func(out chan string) { for reading := true; reading; { select { case <-eof: reading = false case ch = <-in: switch ch { case '\r': fallthrough case '\t': fallthrough case ' ': if len(state) == 0 { continue } out <- state state = "" case '\n': if repeat_newline { continue } if len(state) > 0 { out <- state } out <- string(ch) state = "" repeat_newline = true continue default: state += string(ch) } repeat_newline = false } } if len(state) > 0 { out <- state } close(eot) }(out) return out, eot } func splitNetworkingConfig(eof sentinelSignaller, in chan string) (chan []string, sentinelSignaller) { var out chan []string eos := make(sentinelSignaller) out = make(chan []string) go func(out chan []string) { row := make([]string, 0) for reading := true; reading; { select { case <-eof: reading = false case tk := <-in: switch tk { case "\n": if len(row) > 0 { out <- row } row = make([]string, 0) default: row = append(row, tk) } } } if len(row) > 0 { out <- row } close(eos) }(out) return out, eos } /// All token types in networking file. // VERSION token type networkingVERSION struct { value string } func networkingReadVersion(row []string) (*networkingVERSION, error) { if len(row) != 1 { return nil, fmt.Errorf("Unexpected format for VERSION entry : %v", row) } res := &networkingVERSION{value: row[0]} if !res.Valid() { return nil, fmt.Errorf("Unexpected format for VERSION entry : %v", row) } return res, nil } func (s networkingVERSION) Repr() string { if !s.Valid() { return fmt.Sprintf("VERSION{INVALID=\"%v\"}", s.value) } return fmt.Sprintf("VERSION{%f}", s.Number()) } func (s networkingVERSION) Valid() bool { tokens := strings.SplitN(s.value, "=", 2) if len(tokens) != 2 || tokens[0] != "VERSION" { return false } tokens = strings.Split(tokens[1], ",") if len(tokens) != 2 { return false } for _, t := range tokens { _, err := strconv.ParseUint(t, 10, 64) if err != nil { return false } } return true } func (s networkingVERSION) Number() float64 { var result float64 tokens := strings.SplitN(s.value, "=", 2) tokens = strings.Split(tokens[1], ",") integer, err := strconv.ParseUint(tokens[0], 10, 64) if err != nil { integer = 0 } result = float64(integer) mantissa, err := strconv.ParseUint(tokens[1], 10, 64) if err != nil { return result } denomination := math.Pow(10.0, float64(len(tokens[1]))) return result + (float64(mantissa) / denomination) } // VNET_X token type networkingVNET struct { value string } func (s networkingVNET) Valid() bool { if strings.ToUpper(s.value) != s.value { return false } tokens := strings.SplitN(s.value, "_", 3) if len(tokens) != 3 || tokens[0] != "VNET" { return false } _, err := strconv.ParseUint(tokens[1], 10, 64) if err != nil { return false } return true } func (s networkingVNET) Number() int { tokens := strings.SplitN(s.value, "_", 3) res, err := strconv.Atoi(tokens[1]) if err != nil { return ^int(0) } return res } func (s networkingVNET) Option() string { tokens := strings.SplitN(s.value, "_", 3) if len(tokens) == 3 { return tokens[2] } return "" } func (s networkingVNET) Repr() string { if !s.Valid() { tokens := strings.SplitN(s.value, "_", 3) return fmt.Sprintf("VNET{INVALID=%v}", tokens) } return fmt.Sprintf("VNET{%d} %s", s.Number(), s.Option()) } // Interface name type networkingInterface struct { name string } func (s networkingInterface) Interface() (*net.Interface, error) { return net.InterfaceByName(s.name) } // networking command entry types type networkingCommandEntry_answer struct { vnet networkingVNET value string } type networkingCommandEntry_remove_answer struct { vnet networkingVNET } type networkingCommandEntry_add_nat_portfwd struct { vnet int protocol string port int target_host net.IP target_port int } type networkingCommandEntry_remove_nat_portfwd struct { vnet int protocol string port int } type networkingCommandEntry_add_dhcp_mac_to_ip struct { vnet int mac net.HardwareAddr ip net.IP } type networkingCommandEntry_remove_dhcp_mac_to_ip struct { vnet int mac net.HardwareAddr } type networkingCommandEntry_add_bridge_mapping struct { intf networkingInterface vnet int } type networkingCommandEntry_remove_bridge_mapping struct { intf networkingInterface } type networkingCommandEntry_add_nat_prefix struct { vnet int prefix int } type networkingCommandEntry_remove_nat_prefix struct { vnet int prefix int } type networkingCommandEntry struct { entry interface{} answer *networkingCommandEntry_answer remove_answer *networkingCommandEntry_remove_answer add_nat_portfwd *networkingCommandEntry_add_nat_portfwd remove_nat_portfwd *networkingCommandEntry_remove_nat_portfwd add_dhcp_mac_to_ip *networkingCommandEntry_add_dhcp_mac_to_ip remove_dhcp_mac_to_ip *networkingCommandEntry_remove_dhcp_mac_to_ip add_bridge_mapping *networkingCommandEntry_add_bridge_mapping remove_bridge_mapping *networkingCommandEntry_remove_bridge_mapping add_nat_prefix *networkingCommandEntry_add_nat_prefix remove_nat_prefix *networkingCommandEntry_remove_nat_prefix } func (e networkingCommandEntry) Name() string { switch e.entry.(type) { case networkingCommandEntry_answer: return "answer" case networkingCommandEntry_remove_answer: return "remove_answer" case networkingCommandEntry_add_nat_portfwd: return "add_nat_portfwd" case networkingCommandEntry_remove_nat_portfwd: return "remove_nat_portfwd" case networkingCommandEntry_add_dhcp_mac_to_ip: return "add_dhcp_mac_to_ip" case networkingCommandEntry_remove_dhcp_mac_to_ip: return "remove_dhcp_mac_to_ip" case networkingCommandEntry_add_bridge_mapping: return "add_bridge_mapping" case networkingCommandEntry_remove_bridge_mapping: return "remove_bridge_mapping" case networkingCommandEntry_add_nat_prefix: return "add_nat_prefix" case networkingCommandEntry_remove_nat_prefix: return "remove_nat_prefix" } return "" } func (e networkingCommandEntry) Entry() reflect.Value { this := reflect.ValueOf(e) switch e.entry.(type) { case networkingCommandEntry_answer: return reflect.Indirect(this.FieldByName("answer")) case networkingCommandEntry_remove_answer: return reflect.Indirect(this.FieldByName("remove_answer")) case networkingCommandEntry_add_nat_portfwd: return reflect.Indirect(this.FieldByName("add_nat_portfwd")) case networkingCommandEntry_remove_nat_portfwd: return reflect.Indirect(this.FieldByName("remove_nat_portfwd")) case networkingCommandEntry_add_dhcp_mac_to_ip: return reflect.Indirect(this.FieldByName("add_dhcp_mac_to_ip")) case networkingCommandEntry_remove_dhcp_mac_to_ip: return reflect.Indirect(this.FieldByName("remove_dhcp_mac_to_ip")) case networkingCommandEntry_add_bridge_mapping: return reflect.Indirect(this.FieldByName("add_bridge_mapping")) case networkingCommandEntry_remove_bridge_mapping: return reflect.Indirect(this.FieldByName("remove_bridge_mapping")) case networkingCommandEntry_add_nat_prefix: return reflect.Indirect(this.FieldByName("add_nat_prefix")) case networkingCommandEntry_remove_nat_prefix: return reflect.Indirect(this.FieldByName("remove_nat_prefix")) } return reflect.Value{} } func (e networkingCommandEntry) Repr() string { var result map[string]interface{} result = make(map[string]interface{}) entryN, entry := e.Name(), e.Entry() entryT := entry.Type() for i := 0; i < entry.NumField(); i++ { fld, fldT := entry.Field(i), entryT.Field(i) result[fldT.Name] = fld } return fmt.Sprintf("%s -> %v", entryN, result) } // networking command entry parsers func parseNetworkingCommand_answer(row []string) (*networkingCommandEntry, error) { if len(row) != 2 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) } vnet := networkingVNET{value: row[0]} if !vnet.Valid() { return nil, fmt.Errorf("Invalid format for VNET.") } value := row[1] result := networkingCommandEntry_answer{vnet: vnet, value: value} return &networkingCommandEntry{entry: result, answer: &result}, nil } func parseNetworkingCommand_remove_answer(row []string) (*networkingCommandEntry, error) { if len(row) != 1 { return nil, fmt.Errorf("Expected %d argument. Received %d.", 1, len(row)) } vnet := networkingVNET{value: row[0]} if !vnet.Valid() { return nil, fmt.Errorf("Invalid format for VNET.") } result := networkingCommandEntry_remove_answer{vnet: vnet} return &networkingCommandEntry{entry: result, remove_answer: &result}, nil } func parseNetworkingCommand_add_nat_portfwd(row []string) (*networkingCommandEntry, error) { if len(row) != 5 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 5, len(row)) } vnet, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } protocol := strings.ToLower(row[1]) if !(protocol == "tcp" || protocol == "udp") { return nil, fmt.Errorf("Expected \"tcp\" or \"udp\" for second argument. : %v", row[1]) } sport, err := strconv.Atoi(row[2]) if err != nil { return nil, fmt.Errorf("Unable to parse third argument as an integer. : %v", row[2]) } dest := net.ParseIP(row[3]) if dest == nil { return nil, fmt.Errorf("Unable to parse fourth argument as an IPv4 address. : %v", row[2]) } dport, err := strconv.Atoi(row[4]) if err != nil { return nil, fmt.Errorf("Unable to parse fifth argument as an integer. : %v", row[4]) } result := networkingCommandEntry_add_nat_portfwd{vnet: vnet - 1, protocol: protocol, port: sport, target_host: dest, target_port: dport} return &networkingCommandEntry{entry: result, add_nat_portfwd: &result}, nil } func parseNetworkingCommand_remove_nat_portfwd(row []string) (*networkingCommandEntry, error) { if len(row) != 3 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 3, len(row)) } vnet, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } protocol := strings.ToLower(row[1]) if !(protocol == "tcp" || protocol == "udp") { return nil, fmt.Errorf("Expected \"tcp\" or \"udp\" for second argument. : %v", row[1]) } sport, err := strconv.Atoi(row[2]) if err != nil { return nil, fmt.Errorf("Unable to parse third argument as an integer. : %v", row[2]) } result := networkingCommandEntry_remove_nat_portfwd{vnet: vnet - 1, protocol: protocol, port: sport} return &networkingCommandEntry{entry: result, remove_nat_portfwd: &result}, nil } func parseNetworkingCommand_add_dhcp_mac_to_ip(row []string) (*networkingCommandEntry, error) { if len(row) != 3 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 3, len(row)) } vnet, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } mac, err := net.ParseMAC(row[1]) if err != nil { return nil, fmt.Errorf("Unable to parse second argument as hwaddr. : %v", row[1]) } ip := net.ParseIP(row[2]) if ip != nil { return nil, fmt.Errorf("Unable to parse third argument as ipv4. : %v", row[2]) } result := networkingCommandEntry_add_dhcp_mac_to_ip{vnet: vnet - 1, mac: mac, ip: ip} return &networkingCommandEntry{entry: result, add_dhcp_mac_to_ip: &result}, nil } func parseNetworkingCommand_remove_dhcp_mac_to_ip(row []string) (*networkingCommandEntry, error) { if len(row) != 2 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) } vnet, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } mac, err := net.ParseMAC(row[1]) if err != nil { return nil, fmt.Errorf("Unable to parse second argument as hwaddr. : %v", row[1]) } result := networkingCommandEntry_remove_dhcp_mac_to_ip{vnet: vnet - 1, mac: mac} return &networkingCommandEntry{entry: result, remove_dhcp_mac_to_ip: &result}, nil } func parseNetworkingCommand_add_bridge_mapping(row []string) (*networkingCommandEntry, error) { if len(row) != 2 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) } intf := networkingInterface{name: row[0]} vnet, err := strconv.Atoi(row[1]) if err != nil { return nil, fmt.Errorf("Unable to parse second argument as an integer. : %v", row[2]) } result := networkingCommandEntry_add_bridge_mapping{intf: intf, vnet: vnet - 1} return &networkingCommandEntry{entry: result, add_bridge_mapping: &result}, nil } func parseNetworkingCommand_remove_bridge_mapping(row []string) (*networkingCommandEntry, error) { if len(row) != 1 { return nil, fmt.Errorf("Expected %d argument. Received %d.", 1, len(row)) } intf := networkingInterface{name: row[0]} /* number, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } */ result := networkingCommandEntry_remove_bridge_mapping{intf: intf} return &networkingCommandEntry{entry: result, remove_bridge_mapping: &result}, nil } func parseNetworkingCommand_add_nat_prefix(row []string) (*networkingCommandEntry, error) { if len(row) != 2 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) } vnet, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } if !strings.HasPrefix(row[1], "/") { return nil, fmt.Errorf("Expected second argument to begin with \"/\". : %v", row[1]) } prefix, err := strconv.Atoi(row[1][1:]) if err != nil { return nil, fmt.Errorf("Unable to parse prefix out of second argument. : %v", row[1]) } result := networkingCommandEntry_add_nat_prefix{vnet: vnet - 1, prefix: prefix} return &networkingCommandEntry{entry: result, add_nat_prefix: &result}, nil } func parseNetworkingCommand_remove_nat_prefix(row []string) (*networkingCommandEntry, error) { if len(row) != 2 { return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row)) } vnet, err := strconv.Atoi(row[0]) if err != nil { return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0]) } if !strings.HasPrefix(row[1], "/") { return nil, fmt.Errorf("Expected second argument to begin with \"/\". : %v", row[1]) } prefix, err := strconv.Atoi(row[1][1:]) if err != nil { return nil, fmt.Errorf("Unable to parse prefix out of second argument. : %v", row[1]) } result := networkingCommandEntry_remove_nat_prefix{vnet: vnet - 1, prefix: prefix} return &networkingCommandEntry{entry: result, remove_nat_prefix: &result}, nil } type networkingCommandParser struct { command string callback func([]string) (*networkingCommandEntry, error) } var NetworkingCommandParsers = []networkingCommandParser{ /* DictRecordParseFunct */ {command: "answer", callback: parseNetworkingCommand_answer}, /* DictRecordParseFunct */ {command: "remove_answer", callback: parseNetworkingCommand_remove_answer}, /* NatFwdRecordParseFunct */ {command: "add_nat_portfwd", callback: parseNetworkingCommand_add_nat_portfwd}, /* NatFwdRecordParseFunct */ {command: "remove_nat_portfwd", callback: parseNetworkingCommand_remove_nat_portfwd}, /* DhcpMacRecordParseFunct */ {command: "add_dhcp_mac_to_ip", callback: parseNetworkingCommand_add_dhcp_mac_to_ip}, /* DhcpMacRecordParseFunct */ {command: "remove_dhcp_mac_to_ip", callback: parseNetworkingCommand_remove_dhcp_mac_to_ip}, /* BridgeMappingRecordParseFunct */ {command: "add_bridge_mapping", callback: parseNetworkingCommand_add_bridge_mapping}, /* BridgeMappingRecordParseFunct */ {command: "remove_bridge_mapping", callback: parseNetworkingCommand_remove_bridge_mapping}, /* NatPrefixRecordParseFunct */ {command: "add_nat_prefix", callback: parseNetworkingCommand_add_nat_prefix}, /* NatPrefixRecordParseFunct */ {command: "remove_nat_prefix", callback: parseNetworkingCommand_remove_nat_prefix}, } func NetworkingParserByCommand(command string) *func([]string) (*networkingCommandEntry, error) { for _, p := range NetworkingCommandParsers { if p.command == command { return &p.callback } } return nil } func parseNetworkingConfig(eof sentinelSignaller, rows chan []string) (chan networkingCommandEntry, sentinelSignaller) { var out chan networkingCommandEntry eop := make(sentinelSignaller) out = make(chan networkingCommandEntry) go func(in chan []string, out chan networkingCommandEntry) { for reading := true; reading; { select { case <-eof: reading = false case row := <-in: if len(row) >= 1 { parser := NetworkingParserByCommand(row[0]) if parser == nil { log.Printf("Invalid command : %v", row) continue } callback := *parser entry, err := callback(row[1:]) if err != nil { log.Printf("Unable to parse command : %v %v", err, row) continue } out <- *entry } } } close(eop) }(rows, out) return out, eop } type NetworkingConfig struct { answer map[int]map[string]string nat_portfwd map[int]map[string]string dhcp_mac_to_ip map[int]map[string]net.IP //bridge_mapping map[net.Interface]uint64 // XXX: we don't need the actual interface for anything but informing the user. bridge_mapping map[string]int nat_prefix map[int][]int } func (c NetworkingConfig) repr() string { return fmt.Sprintf("answer -> %v\nnat_portfwd -> %v\ndhcp_mac_to_ip -> %v\nbridge_mapping -> %v\nnat_prefix -> %v", c.answer, c.nat_portfwd, c.dhcp_mac_to_ip, c.bridge_mapping, c.nat_prefix) } func flattenNetworkingConfig(eof sentinelSignaller, in chan networkingCommandEntry) NetworkingConfig { var result NetworkingConfig var vmnet int result.answer = make(map[int]map[string]string) result.nat_portfwd = make(map[int]map[string]string) result.dhcp_mac_to_ip = make(map[int]map[string]net.IP) result.bridge_mapping = make(map[string]int) result.nat_prefix = make(map[int][]int) for reading := true; reading; { select { case <-eof: reading = false case e := <-in: switch e.entry.(type) { case networkingCommandEntry_answer: vnet := e.answer.vnet answers, exists := result.answer[vnet.Number()] if !exists { answers = make(map[string]string) result.answer[vnet.Number()] = answers } answers[vnet.Option()] = e.answer.value case networkingCommandEntry_remove_answer: vnet := e.remove_answer.vnet answers, exists := result.answer[vnet.Number()] if exists { delete(answers, vnet.Option()) } else { log.Printf("Unable to remove answer %s as specified by `remove_answer`.\n", vnet.Repr()) } case networkingCommandEntry_add_nat_portfwd: vmnet = e.add_nat_portfwd.vnet protoport := fmt.Sprintf("%s/%d", e.add_nat_portfwd.protocol, e.add_nat_portfwd.port) target := fmt.Sprintf("%s:%d", e.add_nat_portfwd.target_host, e.add_nat_portfwd.target_port) portfwds, exists := result.nat_portfwd[vmnet] if !exists { portfwds = make(map[string]string) result.nat_portfwd[vmnet] = portfwds } portfwds[protoport] = target case networkingCommandEntry_remove_nat_portfwd: vmnet = e.remove_nat_portfwd.vnet protoport := fmt.Sprintf("%s/%d", e.remove_nat_portfwd.protocol, e.remove_nat_portfwd.port) portfwds, exists := result.nat_portfwd[vmnet] if exists { delete(portfwds, protoport) } else { log.Printf("Unable to remove nat port-forward %s from interface %s%d as requested by `remove_nat_portfwd`.\n", protoport, NetworkingInterfacePrefix, vmnet) } case networkingCommandEntry_add_dhcp_mac_to_ip: vmnet = e.add_dhcp_mac_to_ip.vnet dhcpmacs, exists := result.dhcp_mac_to_ip[vmnet] if !exists { dhcpmacs = make(map[string]net.IP) result.dhcp_mac_to_ip[vmnet] = dhcpmacs } dhcpmacs[e.add_dhcp_mac_to_ip.mac.String()] = e.add_dhcp_mac_to_ip.ip case networkingCommandEntry_remove_dhcp_mac_to_ip: vmnet = e.remove_dhcp_mac_to_ip.vnet dhcpmacs, exists := result.dhcp_mac_to_ip[vmnet] if exists { delete(dhcpmacs, e.remove_dhcp_mac_to_ip.mac.String()) } else { log.Printf("Unable to remove dhcp_mac_to_ip entry %v from interface %s%d as specified by `remove_dhcp_mac_to_ip`.\n", e.remove_dhcp_mac_to_ip, NetworkingInterfacePrefix, vmnet) } case networkingCommandEntry_add_bridge_mapping: intf := e.add_bridge_mapping.intf if _, err := intf.Interface(); err != nil { log.Printf("Interface \"%s\" as specified by `add_bridge_mapping` was not found on the current platform. This is a non-critical error. Ignoring.", intf.name) } result.bridge_mapping[intf.name] = e.add_bridge_mapping.vnet case networkingCommandEntry_remove_bridge_mapping: intf := e.remove_bridge_mapping.intf if _, err := intf.Interface(); err != nil { log.Printf("Interface \"%s\" as specified by `remove_bridge_mapping` was not found on the current platform. This is a non-critical error. Ignoring.", intf.name) } delete(result.bridge_mapping, intf.name) case networkingCommandEntry_add_nat_prefix: vmnet = e.add_nat_prefix.vnet _, exists := result.nat_prefix[vmnet] if exists { result.nat_prefix[vmnet] = append(result.nat_prefix[vmnet], e.add_nat_prefix.prefix) } else { result.nat_prefix[vmnet] = []int{e.add_nat_prefix.prefix} } case networkingCommandEntry_remove_nat_prefix: vmnet = e.remove_nat_prefix.vnet prefixes, exists := result.nat_prefix[vmnet] if exists { for index := 0; index < len(prefixes); index++ { if prefixes[index] == e.remove_nat_prefix.prefix { result.nat_prefix[vmnet] = append(prefixes[:index], prefixes[index+1:]...) break } } } else { log.Printf("Unable to remove nat prefix /%d from interface %s%d as specified by `remove_nat_prefix`.\n", e.remove_nat_prefix.prefix, NetworkingInterfacePrefix, vmnet) } } } } return result } // Constructor for networking file func ReadNetworkingConfig(fd *os.File) (NetworkingConfig, error) { // start piecing together different parts of the file fromfile, eof := consumeFile(fd) tokenized, eot := tokenizeNetworkingConfig(eof, fromfile) rows, eos := splitNetworkingConfig(eot, tokenized) entries, eop := parseNetworkingConfig(eos, rows) // parse the version parsed_version, err := networkingReadVersion(<-rows) if err != nil { return NetworkingConfig{}, err } // verify that it's 1.0 since that's all we support. version := parsed_version.Number() if version != 1.0 { return NetworkingConfig{}, fmt.Errorf("Expected version %f of networking file. Received version %f.", 1.0, version) } // convert to a configuration result := flattenNetworkingConfig(eop, entries) return result, nil } // netmapper interface type NetworkingType int const ( NetworkingType_HOSTONLY = iota + 1 NetworkingType_NAT NetworkingType_BRIDGED ) func networkingConfig_InterfaceTypes(config NetworkingConfig) map[int]NetworkingType { var result map[int]NetworkingType result = make(map[int]NetworkingType) // defaults result[0] = NetworkingType_BRIDGED result[1] = NetworkingType_HOSTONLY result[8] = NetworkingType_NAT // walk through config collecting bridged interfaces for _, vmnet := range config.bridge_mapping { result[vmnet] = NetworkingType_BRIDGED } // walk through answers finding out which ones are nat versus hostonly for vmnet, table := range config.answer { // everything should be defined as a virtual adapter... if table["VIRTUAL_ADAPTER"] == "yes" { // validate that the VNET entry contains everything we expect it to _, subnetQ := table["HOSTONLY_SUBNET"] _, netmaskQ := table["HOSTONLY_NETMASK"] if !(subnetQ && netmaskQ) { log.Printf("Interface %s%d is missing some expected keys (HOSTONLY_SUBNET, HOSTONLY_NETMASK). This is non-critical. Ignoring..", NetworkingInterfacePrefix, vmnet) } // distinguish between nat or hostonly if table["NAT"] == "yes" { result[vmnet] = NetworkingType_NAT } else { result[vmnet] = NetworkingType_HOSTONLY } // if it's not a virtual_adapter, then it must be an alias (really a bridge). } else { result[vmnet] = NetworkingType_BRIDGED } } return result } func networkingConfig_NamesToVmnet(config NetworkingConfig) map[NetworkingType][]int { types := networkingConfig_InterfaceTypes(config) // now sort the keys var keys []int for vmnet := range types { keys = append(keys, vmnet) } sort.Ints(keys) // build result dictionary var result map[NetworkingType][]int result = make(map[NetworkingType][]int) for i := 0; i < len(keys); i++ { t := types[keys[i]] result[t] = append(result[t], keys[i]) } return result } const NetworkingInterfacePrefix = "vmnet" func (e NetworkingConfig) NameIntoDevices(name string) ([]string, error) { netmapper := networkingConfig_NamesToVmnet(e) name = strings.ToLower(name) var vmnets []string var networkingType NetworkingType if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 { networkingType = NetworkingType_HOSTONLY } else if name == "nat" && len(netmapper[NetworkingType_NAT]) > 0 { networkingType = NetworkingType_NAT } else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 { networkingType = NetworkingType_BRIDGED } else { return make([]string, 0), fmt.Errorf("Network name not found: %v", name) } for i := 0; i < len(netmapper[networkingType]); i++ { vmnets = append(vmnets, fmt.Sprintf("%s%d", NetworkingInterfacePrefix, netmapper[networkingType][i])) } return vmnets, nil } func (e NetworkingConfig) DeviceIntoName(device string) (string, error) { types := networkingConfig_InterfaceTypes(e) lowerdevice := strings.ToLower(device) if !strings.HasPrefix(lowerdevice, NetworkingInterfacePrefix) { return device, nil } vmnet, err := strconv.Atoi(lowerdevice[len(NetworkingInterfacePrefix):]) if err != nil { return "", err } network := types[vmnet] switch network { case NetworkingType_HOSTONLY: return "hostonly", nil case NetworkingType_NAT: return "nat", nil case NetworkingType_BRIDGED: return "bridged", nil } return "", fmt.Errorf("Unable to determine network type for device %s%d.", NetworkingInterfacePrefix, vmnet) } /** generic async file reader */ func consumeFile(fd *os.File) (chan byte, sentinelSignaller) { fromfile := make(chan byte) eof := make(sentinelSignaller) go func() { b := make([]byte, 1) for { _, err := fd.Read(b) if err == io.EOF { break } fromfile <- b[0] } close(eof) }() return fromfile, eof }