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