File: step_wait_guest_address.go

package info (click to toggle)
packer 1.6.6%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 32,016 kB
  • sloc: sh: 1,154; python: 619; makefile: 251; ruby: 205; xml: 97
file content (136 lines) | stat: -rw-r--r-- 3,431 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package qemu

import (
	"bufio"
	"context"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/hashicorp/packer/packer-plugin-sdk/multistep"
	packersdk "github.com/hashicorp/packer/packer-plugin-sdk/packer"

	"github.com/digitalocean/go-qemu/qmp"
)

// This step waits for the guest address to become available in the network
// bridge, then it sets the guestAddress state property.
type stepWaitGuestAddress struct {
	CommunicatorType string
	NetBridge        string

	timeout time.Duration
}

func (s *stepWaitGuestAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packersdk.Ui)

	if s.CommunicatorType == "none" {
		ui.Message("No communicator is configured -- skipping StepWaitGuestAddress")
		return multistep.ActionContinue
	}
	if s.NetBridge == "" {
		ui.Message("Not using a NetBridge -- skipping StepWaitGuestAddress")
		return multistep.ActionContinue
	}

	qmpMonitor := state.Get("qmp_monitor").(*qmp.SocketMonitor)
	ctx, cancel := context.WithTimeout(ctx, s.timeout)
	defer cancel()

	ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", s.NetBridge))
	for {
		guestAddress := getGuestAddress(qmpMonitor, s.NetBridge, "user.0")
		if guestAddress != "" {
			log.Printf("Found guest address %s", guestAddress)
			state.Put("guestAddress", guestAddress)
			return multistep.ActionContinue
		}
		select {
		case <-time.After(10 * time.Second):
			continue
		case <-ctx.Done():
			return multistep.ActionHalt
		}
	}
}

func (s *stepWaitGuestAddress) Cleanup(state multistep.StateBag) {
}

func getGuestAddress(qmpMonitor *qmp.SocketMonitor, bridgeName string, deviceName string) string {
	devices, err := getNetDevices(qmpMonitor)
	if err != nil {
		log.Printf("Could not retrieve QEMU QMP network device list: %v", err)
		return ""
	}

	for _, device := range devices {
		if device.Name == deviceName {
			ipAddress, _ := getDeviceIPAddress(bridgeName, device.MacAddress)
			return ipAddress
		}
	}

	log.Printf("QEMU QMP network device %s was not found", deviceName)
	return ""
}

func getDeviceIPAddress(device string, macAddress string) (string, error) {
	// this parses /proc/net/arp to retrieve the given device IP address.
	//
	// /proc/net/arp is normally someting alike:
	//
	// 		IP address       HW type     Flags       HW address            Mask     Device
	// 		192.168.121.111  0x1         0x2         52:54:00:12:34:56     *        virbr0
	//

	const (
		IPAddressIndex int = iota
		HWTypeIndex
		FlagsIndex
		HWAddressIndex
		MaskIndex
		DeviceIndex
	)

	// see ARP flags at https://github.com/torvalds/linux/blob/v5.4/include/uapi/linux/if_arp.h#L132
	const (
		AtfCom int = 0x02 // ATF_COM (complete)
	)

	f, err := os.Open("/proc/net/arp")
	if err != nil {
		return "", fmt.Errorf("failed to open /proc/net/arp: %w", err)
	}
	defer f.Close()

	s := bufio.NewScanner(f)
	s.Scan()

	for s.Scan() {
		fields := strings.Fields(s.Text())

		if device != "" && fields[DeviceIndex] != device {
			continue
		}

		if fields[HWAddressIndex] != macAddress {
			continue
		}

		flags, err := strconv.ParseInt(fields[FlagsIndex], 0, 32)
		if err != nil {
			return "", fmt.Errorf("failed to parse /proc/net/arp flags field %s: %w", fields[FlagsIndex], err)
		}

		if int(flags)&AtfCom == AtfCom {
			return fields[IPAddressIndex], nil
		}
	}

	return "", fmt.Errorf("could not find %s", macAddress)
}