diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 3e38442a2..6b5cfde1f 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -274,7 +274,10 @@ type VmwareDriver struct { DhcpLeasesPath func(string) string DhcpConfPath func(string) string VmnetnatConfPath func(string) string - NetmapConfPath func() string + + /// This method returns an object with the NetworkNameMapper interface + /// that maps network to device and vice-versa. + NetworkMapper func() (NetworkNameMapper, error) } func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) { @@ -304,12 +307,8 @@ func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) { func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { - // read netmap config - pathNetmap := d.NetmapConfPath() - if _, err := os.Stat(pathNetmap); err != nil { - return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) - } - netmap, err := ReadNetmapConfig(pathNetmap) + // grab network mapper + netmap, err := d.NetworkMapper() if err != nil { return "", err } @@ -401,12 +400,8 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { - // parse network<->device mapping - pathNetmap := d.NetmapConfPath() - if _, err := os.Stat(pathNetmap); err != nil { - return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) - } - netmap, err := ReadNetmapConfig(pathNetmap) + // grab mapper for converting network<->device + netmap, err := d.NetworkMapper() if err != nil { return "", err } @@ -471,12 +466,8 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { - // parse network<->device mapping - pathNetmap := d.NetmapConfPath() - if _, err := os.Stat(pathNetmap); err != nil { - return "", fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) - } - netmap, err := ReadNetmapConfig(pathNetmap) + // grab mapper for converting network<->device + netmap, err := d.NetworkMapper() if err != nil { return "", err } diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go index c6b3780dc..c450e1f61 100644 --- a/builder/vmware/common/driver_fusion5.go +++ b/builder/vmware/common/driver_fusion5.go @@ -152,9 +152,19 @@ func (d *Fusion5Driver) Verify() error { d.VmwareDriver.VmnetnatConfPath = func(device string) string { return filepath.Join(libpath, device, "nat.conf") } - d.VmwareDriver.NetmapConfPath = func() string { - // FIXME: Suggested by @phekmat. This will need another parser to be implemented. - return filepath.Join(libpath, "networking") + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetworking := filepath.Join(libpath, "networking") + if _, err := os.Stat(pathNetworking); err != nil { + return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking) + } + + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkingConfig(fd) } return nil diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go index e68bb0de4..62dd7b218 100644 --- a/builder/vmware/common/driver_fusion6.go +++ b/builder/vmware/common/driver_fusion6.go @@ -78,9 +78,19 @@ func (d *Fusion6Driver) Verify() error { d.VmwareDriver.VmnetnatConfPath = func(device string) string { return filepath.Join(libpath, device, "nat.conf") } - d.VmwareDriver.NetmapConfPath = func() string { - // FIXME: Suggested by @phekmat. This will need another parser to be implemented. - return filepath.Join(libpath, "networking") + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetworking := filepath.Join(libpath, "networking") + if _, err := os.Stat(pathNetworking); err != nil { + return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking) + } + + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkingConfig(fd) } return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional") diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index 9c63a7bf9..f857f4adc 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -92,6 +92,20 @@ type DriverMock struct { VerifyErr error } +type NetworkMapperMock struct { + NameIntoDeviceCalled int + DeviceIntoNameCalled int +} + +func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) { + m.NameIntoDeviceCalled += 1 + return "", nil +} +func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) { + m.DeviceIntoNameCalled += 1 + return "", nil +} + func (d *DriverMock) Clone(dst string, src string) error { d.CloneCalled = true d.CloneDst = dst @@ -247,8 +261,8 @@ func (d *DriverMock) GetVmwareDriver() VmwareDriver { state.VmnetnatConfPath = func(string) string { return "/path/to/vmnetnat.conf" } - state.NetmapConfPath = func() string { - return "/path/to/netmap.conf" + state.NetworkMapper = func() (NetworkNameMapper, error) { + return NetworkMapperMock{}, nil } return state } diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go index dbf15b5b5..7a026fea8 100644 --- a/builder/vmware/common/driver_parser.go +++ b/builder/vmware/common/driver_parser.go @@ -3,8 +3,11 @@ package common import ( "fmt" "io" + "log" + "math" "net" "os" + "reflect" "sort" "strconv" "strings" @@ -1053,6 +1056,11 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) { /*** Network Map */ type NetworkMap []map[string]string +type NetworkNameMapper interface { + NameIntoDevice(string) (string, error) + DeviceIntoName(string) (string, error) +} + func ReadNetworkMap(fd *os.File) (NetworkMap, error) { fromfile, eof := consumeFile(fd) @@ -1066,16 +1074,16 @@ func ReadNetworkMap(fd *os.File) (NetworkMap, error) { return result, nil } -func (e *NetworkMap) NameIntoDevice(name string) (string, error) { - for _, val := range *e { +func (e NetworkMap) NameIntoDevice(name string) (string, error) { + for _, val := range e { if strings.ToLower(val["name"]) == strings.ToLower(name) { return val["device"], nil } } return "", fmt.Errorf("Network name not found : %v", name) } -func (e *NetworkMap) DeviceIntoName(device string) (string, error) { - for _, val := range *e { +func (e NetworkMap) DeviceIntoName(device string) (string, error) { + for _, val := range e { if strings.ToLower(val["device"]) == strings.ToLower(device) { return val["name"], nil } @@ -1091,7 +1099,836 @@ func (e *NetworkMap) repr() string { return strings.Join(result, "\n") } -/** main */ +/*** parser for VMware Fusion's networking file */ +func tokenizeNetworkingConfig(eof sentinelSignaller, in chan byte) chan string { + var ch byte + var state string + var repeat_newline bool + + 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 + } + }(out) + return out +} + +func splitNetworkingConfig(eof sentinelSignaller, in chan string) chan []string { + var out chan []string + + 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 + } + }(out) + return out +} + +/// 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 { + var out chan networkingCommandEntry + + 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 + } + } + } + }(rows, out) + return out +} + +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 := tokenizeNetworkingConfig(eof, fromfile) + rows := splitNetworkingConfig(eof, tokenized) + entries := parseNetworkingConfig(eof, 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(eof, 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) NameIntoDevice(name string) (string, error) { + netmapper := networkingConfig_NamesToVmnet(e) + name = strings.ToLower(name) + + var vmnet int + if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 { + vmnet = netmapper[NetworkingType_HOSTONLY][0] + } else if name == "nat" && len(netmapper[NetworkingType_NAT]) > 0 { + vmnet = netmapper[NetworkingType_NAT][0] + } else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 { + vmnet = netmapper[NetworkingType_BRIDGED][0] + } else { + return "", fmt.Errorf("Network name not found : %v", name) + } + return fmt.Sprintf("%s%d", NetworkingInterfacePrefix, vmnet), 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) diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go index 04fb4e897..2814cbad3 100644 --- a/builder/vmware/common/driver_player5.go +++ b/builder/vmware/common/driver_player5.go @@ -196,8 +196,19 @@ func (d *Player5Driver) Verify() error { return playerVmnetnatConfPath(device) } - d.VmwareDriver.NetmapConfPath = func() string { - return playerNetmapConfPath() + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetmap := playerNetmapConfPath() + if _, err := os.Stat(pathNetmap); err != nil { + return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) + } + + fd, err := os.Open(pathNetmap) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkMap(fd) } return nil } diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index af2d7df35..c2c905922 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -157,8 +157,19 @@ func (d *Workstation9Driver) Verify() error { return workstationVmnetnatConfPath(device) } - d.VmwareDriver.NetmapConfPath = func() string { - return workstationNetmapConfPath() + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetmap := workstationNetmapConfPath() + if _, err := os.Stat(pathNetmap); err != nil { + return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) + } + + fd, err := os.Open(pathNetmap) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkMap(fd) } return nil } diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index 137b0c736..7728260ad 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -373,16 +373,8 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver() // read netmap config - pathNetmap := driver.NetmapConfPath() - if _, err := os.Stat(pathNetmap); err != nil { - err := fmt.Errorf("Could not find netmap conf file: %s", pathNetmap) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - netmap, res := vmwcommon.ReadNetmapConfig(pathNetmap) - if res != nil { - err := fmt.Errorf("Unable to read netmap conf file: %s: %v", pathNetmap, res) + netmap, err := driver.NetworkMapper() + if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt