make packer compatible MacOS BigSur by making vmware fusion drivers able to lookup the VM IP address in apple dhcpd leases instead of vmware leases.

This commit is contained in:
gla 2020-12-13 23:26:53 +01:00
parent d8277aa455
commit 7201ce9248
4 changed files with 384 additions and 0 deletions

View File

@ -430,6 +430,56 @@ func (d *VmwareDriver) PotentialGuestIP(state multistep.StateBag) ([]string, err
return addrs, nil return addrs, nil
} }
if runtime.GOOS == "darwin" {
// We have match no vmware DHCP lease for this MAC. We'll try to match it in Apple DHCP leases.
// As a remember, VMWare is no longer able to rely on its own dhcpd server on MacOS BigSur and is
// forced to use Apple DHCPD server instead.
// https://communities.vmware.com/t5/VMware-Fusion-Discussions/Big-Sur-hosts-with-Fusion-Is-vmnet-dhcpd-vmnet8-leases-file/m-p/2298927/highlight/true#M140003
// set the apple dhcp leases path
appleDhcpLeasesPath := "/var/db/dhcpd_leases"
log.Printf("Trying Apple DHCP leases path: %s", appleDhcpLeasesPath)
// open up the path to the apple dhcpd leases
fh, err := os.Open(appleDhcpLeasesPath)
if err != nil {
log.Printf("Error while reading apple DHCP lease path file %s: %s", appleDhcpLeasesPath, err.Error())
} else {
defer fh.Close()
// and then read its contents
leaseEntries, err := ReadAppleDhcpdLeaseEntries(fh)
if err != nil {
return []string{}, err
}
// Parse our MAC address again. There's no need to check for an
// error because we've already parsed this successfully.
hwaddr, _ := net.ParseMAC(MACAddress)
// Go through our available lease entries and see which ones are within
// scope, and that match to our hardware address.
available_lease_entries := make([]appleDhcpLeaseEntry, 0)
for _, entry := range leaseEntries {
// Next check for any where the hardware address matches.
if bytes.Equal(hwaddr, []byte(entry.hwAddress)) {
available_lease_entries = append(available_lease_entries, entry)
}
}
// Check if we found any lease entries that correspond to us. If so, then we
// need to map() them in order to extract the address field to return to the
// caller.
if len(available_lease_entries) > 0 {
addrs := make([]string, 0)
for _, entry := range available_lease_entries {
addrs = append(addrs, entry.ipAddress)
}
return addrs, nil
}
}
}
return []string{}, fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress) return []string{}, fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress)
} }

View File

@ -2430,3 +2430,144 @@ func ReadDhcpdLeaseEntries(fd *os.File) ([]dhcpLeaseEntry, error) {
} }
return result, nil return result, nil
} }
/*** Apple Dhcp Leases */
// Here is what an Apple DHCPD lease entry looks like:
// {
// ip_address=192.168.111.2
// hw_address=1,0:50:56:20:ac:33
// identifier=1,0:50:56:20:ac:33
// lease=0x5fd72edc
// name=vagrant-2019
// }
type appleDhcpLeaseEntry struct {
ipAddress string
hwAddress, id []byte
lease string
name string
extra map[string]string
}
func readAppleDhcpdLeaseEntry(in chan byte) (entry *appleDhcpLeaseEntry, err error) {
entry = &appleDhcpLeaseEntry{extra: map[string]string{}}
validFieldCount := 0
// Read up to the lease item and validate that it actually matches
_, ch := consumeOpenClosePair('{', '}', in)
for insideBraces := true; insideBraces; {
item, ok := consumeUntilSentinel('\n', ch)
item_s := strings.TrimSpace(string(item))
if !ok {
insideBraces = false
}
if item_s == "{" || item_s == "}" {
continue
}
splittedLine := strings.Split(item_s, "=")
var key, val string
switch len(splittedLine) {
case 0:
// should never happens as Split always returns at least 1 item
fallthrough
case 1:
log.Printf("Error parsing invalid line: `%s`", item_s)
continue
case 2:
key = strings.TrimSpace(splittedLine[0])
val = strings.TrimSpace(splittedLine[1])
default:
// There were more than one '=' on this line, we'll keep the part before the first '=' as the key and
// the rest will be the value
key = strings.TrimSpace(splittedLine[0])
val = strings.TrimSpace(strings.Join(splittedLine[1:], "="))
}
switch key {
case "ip_address":
entry.ipAddress = val
validFieldCount++
case "identifier":
fallthrough
case "hw_address":
if strings.Count(val, ",") != 1 {
log.Printf("Error %s `%s` is not properly formatted for entry %s", key, val, entry.name)
break
}
splittedVal := strings.Split(val, ",")
mac := splittedVal[1]
splittedMac := strings.Split(mac, ":")
// Pad the retrieved hw address with '0' when necessary
for idx := range splittedMac {
if len(splittedMac[idx]) == 1 {
splittedMac[idx] = "0" + splittedMac[idx]
}
}
mac = strings.Join(splittedMac, ":")
decodedLease, err := decodeDhcpdLeaseBytes(mac)
if err != nil {
log.Printf("Error trying to parse %s (%v) for entry %s - %v", key, val, entry.name, mac)
break
}
if key == "identifier" {
entry.id = decodedLease
} else {
entry.hwAddress = decodedLease
}
validFieldCount++
case "lease":
entry.lease = val
validFieldCount++
case "name":
entry.name = val
validFieldCount++
default:
// Just stash it for now because we have no idea what it is.
entry.extra[key] = val
}
}
// we have most likely parsed the whole file
if validFieldCount == 0 {
return nil, nil
}
// an entry is composed of 5 mandatory fields, we'll check that they all have been set during the parsing
if validFieldCount < 5 {
return entry, fmt.Errorf("Error entry `%v` is missing mandatory information", entry)
}
return entry, nil
}
func ReadAppleDhcpdLeaseEntries(fd *os.File) ([]appleDhcpLeaseEntry, error) {
fch := consumeFile(fd)
uncommentedch := uncomment(fch)
wch := filterOutCharacters([]byte{'\r', '\v'}, uncommentedch)
result := make([]appleDhcpLeaseEntry, 0)
errors := make([]error, 0)
// Consume apple dhcpd lease entries from the channel until we just plain run out.
for i := 0; ; i++ {
if entry, err := readAppleDhcpdLeaseEntry(wch); entry == nil {
// If our entry is nil, then we've run out of input and finished
// parsing the file to completion.
break
} else if err != nil {
// If we received an error, then log it and keep track of it. This
// way we can warn the user later which entries we had issues with.
log.Printf("Error parsing apple dhcpd lease entry #%d: %s", 1+i, err)
errors = append(errors, err)
} else {
// If we've parsed an entry successfully, then aggregate it to
// our slice of results.
result = append(result, *entry)
}
}
// If we received any errors then include alongside our results.
if len(errors) > 0 {
return result, fmt.Errorf("Errors found while parsing apple dhcpd lease entries: %v", errors)
}
return result, nil
}

View File

@ -865,6 +865,166 @@ func TestParserReadDhcpdLeases(t *testing.T) {
} }
} }
func consumeAppleLeaseString(s string) chan byte {
sch := consumeString(s)
uncommentedch := uncomment(sch)
return filterOutCharacters([]byte{'\r', '\v'}, uncommentedch)
}
func TestParserReadAppleDhcpdLeaseEntry(t *testing.T) {
test_1 := `{
ip_address=192.168.111.3
hw_address=1,0:c:56:3c:e7:22
identifier=1,0:c:56:3c:e7:22
lease=0x5fd78ae2
name=vagrant-2019
fake=field
}`
expected_1 := map[string]string{
"ipAddress": "192.168.111.3",
"hwAddress": "000c563ce722",
"id": "000c563ce722",
"lease": "0x5fd78ae2",
"name": "vagrant-2019",
}
expected_extra_1 := map[string]string{
"fake": "field",
}
result, err := readAppleDhcpdLeaseEntry(consumeAppleLeaseString(test_1))
if err != nil {
t.Errorf("error parsing entry: %v", err)
}
if result.ipAddress != expected_1["ipAddress"] {
t.Errorf("expected ipAddress %v, got %v", expected_1["ipAddress"], result.ipAddress)
}
if hex.EncodeToString(result.hwAddress) != expected_1["hwAddress"] {
t.Errorf("expected hwAddress %v, got %v", expected_1["hwAddress"], hex.EncodeToString(result.hwAddress))
}
if hex.EncodeToString(result.id) != expected_1["id"] {
t.Errorf("expected id %v, got %v", expected_1["id"], hex.EncodeToString(result.id))
}
if result.lease != expected_1["lease"] {
t.Errorf("expected lease %v, got %v", expected_1["lease"], result.lease)
}
if result.name != expected_1["name"] {
t.Errorf("expected name %v, got %v", expected_1["name"], result.name)
}
if result.extra["fake"] != expected_extra_1["fake"] {
t.Errorf("expected extra %v, got %v", expected_extra_1["fake"], result.extra["fake"])
}
}
func TestParserReadAppleDhcpdLeases(t *testing.T) {
f, err := os.Open(filepath.Join("testdata", "apple-dhcpd-example.leases"))
if err != nil {
t.Fatalf("Unable to open dhcpd.leases sample: %s", err)
}
defer f.Close()
results, err := ReadAppleDhcpdLeaseEntries(f)
if err != nil {
t.Fatalf("Error reading lease: %s", err)
}
// some simple utilities
filter_ipAddr := func(ipAddress string, items []appleDhcpLeaseEntry) (result []appleDhcpLeaseEntry) {
for _, item := range items {
if item.ipAddress == ipAddress {
result = append(result, item)
}
}
return
}
find_id := func(id string, items []appleDhcpLeaseEntry) *appleDhcpLeaseEntry {
for _, item := range items {
if id == hex.EncodeToString(item.id) {
return &item
}
}
return nil
}
find_hwAddr := func(hwAddr string, items []appleDhcpLeaseEntry) *appleDhcpLeaseEntry {
for _, item := range items {
if hwAddr == hex.EncodeToString(item.hwAddress) {
return &item
}
}
return nil
}
// actual unit tests
test_1 := map[string]string{
"ipAddress": "127.0.0.19",
"id": "0dead099aabb",
"hwAddress": "0dead099aabb",
}
test_1_findings := filter_ipAddr(test_1["ipAddress"], results)
if len(test_1_findings) != 2 {
t.Errorf("expected %d matching entries, got %d", 2, len(test_1_findings))
} else {
res := find_hwAddr(test_1["hwAddress"], test_1_findings)
if res == nil {
t.Errorf("unable to find item with hwAddress %v", test_1["hwAddress"])
} else if hex.EncodeToString(res.id) != test_1["id"] {
t.Errorf("expected id %s, got %s", test_1["id"], hex.EncodeToString(res.id))
}
}
test_2 := map[string]string{
"ipAddress": "127.0.0.19",
"id": "0dead0667788",
"hwAddress": "0dead0667788",
}
test_2_findings := filter_ipAddr(test_2["ipAddress"], results)
if len(test_2_findings) != 2 {
t.Errorf("expected %d matching entries, got %d", 2, len(test_2_findings))
} else {
res := find_hwAddr(test_2["hwAddress"], test_2_findings)
if res == nil {
t.Errorf("unable to find item with hwAddress %v", test_2["hwAddress"])
} else if hex.EncodeToString(res.id) != test_2["id"] {
t.Errorf("expected id %s, got %s", test_2["id"], hex.EncodeToString(res.id))
}
}
test_3 := map[string]string{
"ipAddress": "127.0.0.17",
"id": "0dead0334455",
"hwAddress": "0dead0667788",
}
test_3_findings := filter_ipAddr(test_3["ipAddress"], results)
if len(test_3_findings) != 2 {
t.Errorf("expected %d matching entries, got %d", 2, len(test_3_findings))
} else {
res := find_id(test_3["id"], test_3_findings)
if res == nil {
t.Errorf("unable to find item with id %v", test_3["id"])
} else if hex.EncodeToString(res.hwAddress) != test_3["hwAddress"] {
t.Errorf("expected hardware address %s, got %s", test_3["hwAddress"], hex.EncodeToString(res.hwAddress))
}
}
test_4 := map[string]string{
"ipAddress": "127.0.0.17",
"id": "0dead0001122",
"hwAddress": "0dead0667788",
}
test_4_findings := filter_ipAddr(test_4["ipAddress"], results)
if len(test_4_findings) != 2 {
t.Errorf("expected %d matching entries, got %d", 2, len(test_4_findings))
} else {
res := find_id(test_4["id"], test_4_findings)
if res == nil {
t.Errorf("unable to find item with id %v", test_4["id"])
} else if hex.EncodeToString(res.hwAddress) != test_4["hwAddress"] {
t.Errorf("expected hardware address %s, got %s", test_4["hwAddress"], hex.EncodeToString(res.hwAddress))
}
}
}
func TestParserTokenizeNetworkingConfig(t *testing.T) { func TestParserTokenizeNetworkingConfig(t *testing.T) {
tests := []string{ tests := []string{
"words words words", "words words words",

View File

@ -0,0 +1,33 @@
# this entry is normal
{
ip_address=127.0.0.17
hw_address=1,d:ea:d0:66:77:88
identifier=1,d:ea:d0:0:11:22
lease=0x5fd78ae2
name=vagrant-2019
}
# this entry has tabs
{
ip_address=127.0.0.17
hw_address=1,d:ea:d0:66:77:88
identifier=1,d:ea:d0:33:44:55
lease=0x5fd7b4e5
name=vagrant-2019
}
# These next two entries have the same address, but different uids
{
ip_address=127.0.0.19
hw_address=1,d:ea:d0:66:77:88
identifier=1,d:ea:d0:66:77:88
lease=0x5fd72edc
name=vagrant-2019
}
{
ip_address=127.0.0.19
hw_address=1,d:ea:d0:99:aa:bb
identifier=1,d:ea:d0:99:aa:bb
lease=0x5fd72edc
name=vagrant-2019
}