diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 16:16:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 16:16:56 +0000 |
commit | 62e212dec2415aa605363d616c481c2a56e70737 (patch) | |
tree | b5ace7061598be728afaf95a9a40054daf05ce19 /test | |
parent | Initial commit. (diff) | |
download | golang-github-containers-gvisor-tap-vsocks-62e212dec2415aa605363d616c481c2a56e70737.tar.xz golang-github-containers-gvisor-tap-vsocks-62e212dec2415aa605363d616c481c2a56e70737.zip |
Adding upstream version 0.7.3+ds1.upstream/0.7.3+ds1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test')
-rw-r--r-- | test/basic_test.go | 222 | ||||
-rw-r--r-- | test/fcos.go | 80 | ||||
-rw-r--r-- | test/fcos_amd64.go | 64 | ||||
-rw-r--r-- | test/ignition.go | 162 | ||||
-rw-r--r-- | test/ignition_schema.go | 248 | ||||
-rwxr-xr-x | test/performance.sh | 54 | ||||
-rw-r--r-- | test/port_forwarding_test.go | 269 | ||||
-rw-r--r-- | test/pull.go | 81 | ||||
-rw-r--r-- | test/suite_test.go | 242 | ||||
-rwxr-xr-x | test/wsl.sh | 9 |
10 files changed, 1431 insertions, 0 deletions
diff --git a/test/basic_test.go b/test/basic_test.go new file mode 100644 index 0000000..9d3faf0 --- /dev/null +++ b/test/basic_test.go @@ -0,0 +1,222 @@ +package e2e + +import ( + "context" + "net" + "net/http" + + gvproxyclient "github.com/containers/gvisor-tap-vsock/pkg/client" + "github.com/containers/gvisor-tap-vsock/pkg/types" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe("connectivity", func() { + ginkgo.It("should configure the interface", func() { + out, err := sshExec("ifconfig $(route | grep '^default' | grep -o '[^ ]*$')") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("mtu 1500")) + gomega.Expect(string(out)).To(gomega.ContainSubstring("inet 192.168.127.2")) + gomega.Expect(string(out)).To(gomega.ContainSubstring("netmask 255.255.255.0")) + gomega.Expect(string(out)).To(gomega.ContainSubstring("broadcast 192.168.127.255")) + }) + + ginkgo.It("should configure the default route", func() { + out, err := sshExec("ip route show") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.MatchRegexp(`default via 192\.168\.127\.1 dev (.*?) proto dhcp (src 192\.168\.127\.2 )?metric 100`)) + }) + + ginkgo.It("should configure dns settings", func() { + out, err := sshExec("cat /etc/resolv.conf") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("nameserver 192.168.127.1")) + }) + + ginkgo.It("should ping the tap device", func() { + out, err := sshExec("ping -c2 192.168.127.2") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("2 packets transmitted, 2 received, 0% packet loss")) + }) + + ginkgo.It("should ping the gateway", func() { + out, err := sshExec("ping -c2 192.168.127.1") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("2 packets transmitted, 2 received, 0% packet loss")) + }) +}) + +var _ = ginkgo.Describe("dns", func() { + ginkgo.It("should resolve redhat.com", func() { + out, err := sshExec("nslookup redhat.com") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 52.200.142.250")) + }) + + ginkgo.It("should resolve CNAME record for www.wikipedia.org", func() { + out, err := sshExec("nslookup -query=cname www.wikipedia.org") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("www.wikipedia.org canonical name = dyna.wikimedia.org.")) + }) + ginkgo.It("should resolve MX record for wikipedia.org", func() { + out, err := sshExec("nslookup -query=mx wikipedia.org") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("wikipedia.org mail exchanger = 10 mx1001.wikimedia.org.")) + }) + + ginkgo.It("should resolve NS record for wikipedia.org", func() { + out, err := sshExec("nslookup -query=ns wikipedia.org") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("wikipedia.org nameserver = ns0.wikimedia.org.")) + }) + ginkgo.It("should resolve LDAP SRV record for google.com", func() { + out, err := sshExec("nslookup -query=srv _ldap._tcp.google.com") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring(`_ldap._tcp.google.com service = 5 0 389 ldap.google.com.`)) + }) + ginkgo.It("should resolve TXT for wikipedia.org", func() { + out, err := sshExec("nslookup -query=txt wikipedia.org") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring(`"v=spf1 include:wikimedia.org ~all"`)) + }) + + ginkgo.It("should resolve gateway.containers.internal", func() { + out, err := sshExec("nslookup gateway.containers.internal") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.1")) + }) + + ginkgo.It("should resolve host.containers.internal", func() { + out, err := sshExec("nslookup host.containers.internal") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.254")) + }) + + ginkgo.It("should resolve dynamically added dns entry test.dynamic.internal", func() { + client := gvproxyclient.New(&http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", sock) + }, + }, + }, "http://base") + err := client.AddDNS(&types.Zone{ + Name: "dynamic.internal.", + Records: []types.Record{ + { + Name: "test", + IP: net.ParseIP("192.168.127.254"), + }, + }, + }) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + out, err := sshExec("nslookup test.dynamic.internal") + + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.254")) + }) + + ginkgo.It("should resolve recently added dns entry test.dynamic.internal", func() { + client := gvproxyclient.New(&http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", sock) + }, + }, + }, "http://base") + err := client.AddDNS(&types.Zone{ + Name: "dynamic.internal.", + Records: []types.Record{ + { + Name: "test", + IP: net.ParseIP("192.168.127.254"), + }, + }, + }) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = client.AddDNS(&types.Zone{ + Name: "dynamic.internal.", + Records: []types.Record{ + { + Name: "test", + IP: net.ParseIP("192.168.127.253"), + }, + }, + }) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + out, err := sshExec("nslookup test.dynamic.internal") + + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.253")) + }) + + ginkgo.It("should retain order of existing zone", func() { + client := gvproxyclient.New(&http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", sock) + }, + }, + }, "http://base") + _ = client.AddDNS(&types.Zone{ + Name: "dynamic.testing.", + DefaultIP: net.ParseIP("192.168.127.2"), + }) + _ = client.AddDNS(&types.Zone{ + Name: "testing.", + Records: []types.Record{ + { + Name: "host", + IP: net.ParseIP("192.168.127.3"), + }, + }, + }) + out, err := sshExec("nslookup test.dynamic.internal") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.2")) + + _ = client.AddDNS(&types.Zone{ + Name: "testing.", + Records: []types.Record{ + { + Name: "gateway", + IP: net.ParseIP("192.168.127.1"), + }, + }, + }) + out, err = sshExec("nslookup *.dynamic.testing") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.2")) + + out, err = sshExec("nslookup gateway.testing") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Address: 192.168.127.1")) + }) +}) + +var _ = ginkgo.Describe("command-line format", func() { + ginkgo.It("should convert Command to command line format", func() { + command := types.NewGvproxyCommand() + command.AddEndpoint("unix:///tmp/network.sock") + command.Debug = true + command.AddQemuSocket("tcp://0.0.0.0:1234") + command.PidFile = "~/gv-pidfile.txt" + command.LogFile = "~/gv.log" + command.AddForwardUser("demouser") + + cmd := command.ToCmdline() + gomega.Expect(cmd).To(gomega.Equal([]string{ + "-listen", "unix:///tmp/network.sock", + "-debug", + "-mtu", "1500", + "-ssh-port", "2222", + "-listen-qemu", "tcp://0.0.0.0:1234", + "-forward-user", "demouser", + "-pid-file", "~/gv-pidfile.txt", + "-log-file", "~/gv.log", + })) + }) +}) diff --git a/test/fcos.go b/test/fcos.go new file mode 100644 index 0000000..e9cb9ac --- /dev/null +++ b/test/fcos.go @@ -0,0 +1,80 @@ +package e2e + +import ( + "os" + "path/filepath" + "strings" + + "github.com/opencontainers/go-digest" +) + +type FcosDownload struct { + DataDir string +} + +type fcosDownloadInfo struct { + Location string + Sha256Sum string +} + +func NewFcosDownloader(dataDir string) (*FcosDownload, error) { + return &FcosDownload{ + DataDir: dataDir, + }, nil +} + +func imageName(info *fcosDownloadInfo) string { + urlSplit := strings.Split(info.Location, "/") + return urlSplit[len(urlSplit)-1] +} + +func (downloader *FcosDownload) DownloadImage() (string, error) { + info, err := getFCOSDownload() + if err != nil { + return "", err + } + + compressedImage := filepath.Join(downloader.DataDir, imageName(info)) + uncompressedImage := strings.TrimSuffix(filepath.Join(filepath.Dir(compressedImage), imageName(info)), ".xz") + + // check if the latest image is already present + ok, err := downloader.updateAvailable(info, compressedImage) + if err != nil { + return "", err + } + if !ok { + if err := DownloadVMImage(info.Location, compressedImage); err != nil { + return "", err + } + } + + if _, err := os.Stat(uncompressedImage); err == nil { + return uncompressedImage, nil + } + if err := Decompress(compressedImage, uncompressedImage); err != nil { + return "", err + } + return uncompressedImage, nil +} + +func (downloader *FcosDownload) updateAvailable(info *fcosDownloadInfo, compressedImage string) (bool, error) { + // check the sha of the local image if it exists + // get the sha of the remote image + // == dont bother to pull + if _, err := os.Stat(compressedImage); os.IsNotExist(err) { + return false, nil + } + fd, err := os.Open(compressedImage) + if err != nil { + return false, err + } + defer fd.Close() + sum, err := digest.SHA256.FromReader(fd) + if err != nil { + return false, err + } + if sum.Encoded() == info.Sha256Sum { + return true, nil + } + return false, nil +} diff --git a/test/fcos_amd64.go b/test/fcos_amd64.go new file mode 100644 index 0000000..3ffb906 --- /dev/null +++ b/test/fcos_amd64.go @@ -0,0 +1,64 @@ +package e2e + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/coreos/stream-metadata-go/fedoracoreos" + "github.com/coreos/stream-metadata-go/stream" + "github.com/sirupsen/logrus" +) + +// This should get Exported and stay put as it will apply to all fcos downloads +// getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version +func getFCOSDownload() (*fcosDownloadInfo, error) { + streamurl := fedoracoreos.GetStreamURL(fedoracoreos.StreamNext) + resp, err := http.Get(streamurl.String()) + if err != nil { + return nil, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer func() { + if err := resp.Body.Close(); err != nil { + logrus.Error(err) + } + }() + + var fcosstable stream.Stream + if err := json.Unmarshal(body, &fcosstable); err != nil { + return nil, err + } + arch, ok := fcosstable.Architectures["x86_64"] + if !ok { + return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream") + } + artifacts := arch.Artifacts + if artifacts == nil { + return nil, fmt.Errorf("unable to pull VM image: no artifact in stream") + } + qemu, ok := artifacts["qemu"] + if !ok { + return nil, fmt.Errorf("unable to pull VM image: no qemu artifact in stream") + } + formats := qemu.Formats + if formats == nil { + return nil, fmt.Errorf("unable to pull VM image: no formats in stream") + } + qcow, ok := formats["qcow2.xz"] + if !ok { + return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream") + } + disk := qcow.Disk + if disk == nil { + return nil, fmt.Errorf("unable to pull VM image: no disk in stream") + } + return &fcosDownloadInfo{ + Location: disk.Location, + Sha256Sum: disk.Sha256, + }, nil +} diff --git a/test/ignition.go b/test/ignition.go new file mode 100644 index 0000000..a9dcbff --- /dev/null +++ b/test/ignition.go @@ -0,0 +1,162 @@ +package e2e + +import ( + "encoding/json" + "net/url" + "os" +) + +var ( + mode = 0644 + dirMode = 0744 + root = "root" + test = "test" + yes = true + no = false +) + +func CreateIgnition(ignitionFile string, publicKey string, user string, password string) error { + + linger := `[Unit] +Description=Activate podman socket +Wants=podman.socket +[Service] +ExecStart=/usr/bin/sleep infinity +` + + systemd := Systemd{ + Units: []Unit{ + { + Name: "systemd-resolved.service", + Enabled: &no, + Mask: &yes, + }, + { + Name: "podman.socket", + Enabled: &yes, + }, + }, + } + + passwd := Passwd{ + Users: []PasswdUser{ + { + Name: user, + PasswordHash: &password, + SSHAuthorizedKeys: []SSHAuthorizedKey{ + SSHAuthorizedKey(publicKey), + }, + Groups: []Group{ + "wheel", + "sudo", + }, + }, + { + Name: "root", + PasswordHash: &password, + SSHAuthorizedKeys: []SSHAuthorizedKey{ + SSHAuthorizedKey(publicKey), + }, + }, + }, + } + + storage := Storage{ + // Replaces resolv.conf with an empty file that will be overwritten by NetworkManager + Files: []File{ + { + Node: Node{ + Group: NodeGroup{Name: &root}, + Path: "/etc/resolv.conf", + User: NodeUser{Name: &root}, + Overwrite: &yes, + }, + FileEmbedded1: FileEmbedded1{ + Contents: Resource{ + Source: encodeData(""), + }, + Mode: &mode, + }, + }, + { + Node: Node{ + Group: NodeGroup{Name: &test}, + Path: "/home/" + test + "/.config/systemd/user/linger-podman.service", + User: NodeUser{Name: &test}, + Overwrite: &yes, + }, + FileEmbedded1: FileEmbedded1{ + Contents: Resource{ + Source: encodeData(linger), + }, + Mode: &mode, + }, + }, + { + Node: Node{ + Group: NodeGroup{Name: &test}, + Path: "/var/lib/systemd/linger/" + test, + User: NodeUser{Name: &test}, + Overwrite: &yes, + }, + FileEmbedded1: FileEmbedded1{ + Contents: Resource{ + Source: encodeData(""), + }, + Mode: &mode, + }, + }, + }, + Directories: []Directory{ + dir("/home/" + test + "/.config"), + dir("/home/" + test + "/.config/containers"), + dir("/home/" + test + "/.config/systemd"), + dir("/home/" + test + "/.config/systemd/user"), + dir("/home/" + test + "/.config/systemd/user/default.target.wants"), + }, + Links: []Link{ + { + Node: Node{ + Group: NodeGroup{Name: &test}, + Path: "/home/" + test + "/.config/systemd/user/default.target.wants/linger-podman.service", + User: NodeUser{Name: &test}, + }, + LinkEmbedded1: LinkEmbedded1{ + Hard: &no, + Target: "/home/" + test + "/.config/systemd/user/linger-podman.service", + }, + }, + }, + } + + config := Config{ + Ignition: Ignition{Version: "3.2.0"}, + Systemd: systemd, + Passwd: passwd, + Storage: storage, + } + + contents, err := json.Marshal(config) + if err != nil { + return err + } + + // #nosec + return os.WriteFile(ignitionFile, contents, 0644) +} + +func dir(path string) Directory { + return Directory{ + Node: Node{ + Group: NodeGroup{Name: &test}, + Path: path, + User: NodeUser{Name: &test}, + }, + DirectoryEmbedded1: DirectoryEmbedded1{Mode: &dirMode}, + } +} + +func encodeData(data string) *string { + str := "data:," + url.PathEscape(data) + return &str +} diff --git a/test/ignition_schema.go b/test/ignition_schema.go new file mode 100644 index 0000000..5fb9d75 --- /dev/null +++ b/test/ignition_schema.go @@ -0,0 +1,248 @@ +package e2e + +// Taken from https://github.com/coreos/ignition/blob/master/config/v3_2/types/schema.go + +// generated by "schematyper --package=types config/v3_2/schema/ignition.json -o config/v3_2/types/schema.go --root-type=Config" -- DO NOT EDIT + +type Clevis struct { + Custom *Custom `json:"custom,omitempty"` + Tang []Tang `json:"tang,omitempty"` + Threshold *int `json:"threshold,omitempty"` + Tpm2 *bool `json:"tpm2,omitempty"` +} + +type Config struct { + Ignition Ignition `json:"ignition"` + Passwd Passwd `json:"passwd,omitempty"` + Storage Storage `json:"storage,omitempty"` + Systemd Systemd `json:"systemd,omitempty"` +} + +type Custom struct { + Config string `json:"config"` + NeedsNetwork *bool `json:"needsNetwork,omitempty"` + Pin string `json:"pin"` +} + +type Device string + +type Directory struct { + Node + DirectoryEmbedded1 +} + +type DirectoryEmbedded1 struct { + Mode *int `json:"mode,omitempty"` +} + +type Disk struct { + Device string `json:"device"` + Partitions []Partition `json:"partitions,omitempty"` + WipeTable *bool `json:"wipeTable,omitempty"` +} + +type Dropin struct { + Contents *string `json:"contents,omitempty"` + Name string `json:"name"` +} + +type File struct { + Node + FileEmbedded1 +} + +type FileEmbedded1 struct { + Append []Resource `json:"append,omitempty"` + Contents Resource `json:"contents,omitempty"` + Mode *int `json:"mode,omitempty"` +} + +type Filesystem struct { + Device string `json:"device"` + Format *string `json:"format,omitempty"` + Label *string `json:"label,omitempty"` + MountOptions []MountOption `json:"mountOptions,omitempty"` + Options []FilesystemOption `json:"options,omitempty"` + Path *string `json:"path,omitempty"` + UUID *string `json:"uuid,omitempty"` + WipeFilesystem *bool `json:"wipeFilesystem,omitempty"` +} + +type FilesystemOption string + +type Group string + +type HTTPHeader struct { + Name string `json:"name"` + Value *string `json:"value,omitempty"` +} + +type HTTPHeaders []HTTPHeader + +type Ignition struct { + Config IgnitionConfig `json:"config,omitempty"` + Proxy Proxy `json:"proxy,omitempty"` + Security Security `json:"security,omitempty"` + Timeouts Timeouts `json:"timeouts,omitempty"` + Version string `json:"version,omitempty"` +} + +type IgnitionConfig struct { + Merge []Resource `json:"merge,omitempty"` + Replace Resource `json:"replace,omitempty"` +} + +type Link struct { + Node + LinkEmbedded1 +} + +type LinkEmbedded1 struct { + Hard *bool `json:"hard,omitempty"` + Target string `json:"target"` +} + +type Luks struct { + Clevis *Clevis `json:"clevis,omitempty"` + Device *string `json:"device,omitempty"` + KeyFile Resource `json:"keyFile,omitempty"` + Label *string `json:"label,omitempty"` + Name string `json:"name"` + Options []LuksOption `json:"options,omitempty"` + UUID *string `json:"uuid,omitempty"` + WipeVolume *bool `json:"wipeVolume,omitempty"` +} + +type LuksOption string + +type MountOption string + +type NoProxyItem string + +type Node struct { + Group NodeGroup `json:"group,omitempty"` + Overwrite *bool `json:"overwrite,omitempty"` + Path string `json:"path"` + User NodeUser `json:"user,omitempty"` +} + +type NodeGroup struct { + ID *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +type NodeUser struct { + ID *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +type Partition struct { + GUID *string `json:"guid,omitempty"` + Label *string `json:"label,omitempty"` + Number int `json:"number,omitempty"` + Resize *bool `json:"resize,omitempty"` + ShouldExist *bool `json:"shouldExist,omitempty"` + SizeMiB *int `json:"sizeMiB,omitempty"` + StartMiB *int `json:"startMiB,omitempty"` + TypeGUID *string `json:"typeGuid,omitempty"` + WipePartitionEntry *bool `json:"wipePartitionEntry,omitempty"` +} + +type Passwd struct { + Groups []PasswdGroup `json:"groups,omitempty"` + Users []PasswdUser `json:"users,omitempty"` +} + +type PasswdGroup struct { + Gid *int `json:"gid,omitempty"` + Name string `json:"name"` + PasswordHash *string `json:"passwordHash,omitempty"` + ShouldExist *bool `json:"shouldExist,omitempty"` + System *bool `json:"system,omitempty"` +} + +type PasswdUser struct { + Gecos *string `json:"gecos,omitempty"` + Groups []Group `json:"groups,omitempty"` + HomeDir *string `json:"homeDir,omitempty"` + Name string `json:"name"` + NoCreateHome *bool `json:"noCreateHome,omitempty"` + NoLogInit *bool `json:"noLogInit,omitempty"` + NoUserGroup *bool `json:"noUserGroup,omitempty"` + PasswordHash *string `json:"passwordHash,omitempty"` + PrimaryGroup *string `json:"primaryGroup,omitempty"` + SSHAuthorizedKeys []SSHAuthorizedKey `json:"sshAuthorizedKeys,omitempty"` + Shell *string `json:"shell,omitempty"` + ShouldExist *bool `json:"shouldExist,omitempty"` + System *bool `json:"system,omitempty"` + UID *int `json:"uid,omitempty"` +} + +type Proxy struct { + HTTPProxy *string `json:"httpProxy,omitempty"` + HTTPSProxy *string `json:"httpsProxy,omitempty"` + NoProxy []NoProxyItem `json:"noProxy,omitempty"` +} + +type Raid struct { + Devices []Device `json:"devices"` + Level string `json:"level"` + Name string `json:"name"` + Options []RaidOption `json:"options,omitempty"` + Spares *int `json:"spares,omitempty"` +} + +type RaidOption string + +type Resource struct { + Compression *string `json:"compression,omitempty"` + HTTPHeaders HTTPHeaders `json:"httpHeaders,omitempty"` + Source *string `json:"source,omitempty"` + Verification Verification `json:"verification,omitempty"` +} + +type SSHAuthorizedKey string + +type Security struct { + TLS TLS `json:"tls,omitempty"` +} + +type Storage struct { + Directories []Directory `json:"directories,omitempty"` + Disks []Disk `json:"disks,omitempty"` + Files []File `json:"files,omitempty"` + Filesystems []Filesystem `json:"filesystems,omitempty"` + Links []Link `json:"links,omitempty"` + Luks []Luks `json:"luks,omitempty"` + Raid []Raid `json:"raid,omitempty"` +} + +type Systemd struct { + Units []Unit `json:"units,omitempty"` +} + +type TLS struct { + CertificateAuthorities []Resource `json:"certificateAuthorities,omitempty"` +} + +type Tang struct { + Thumbprint *string `json:"thumbprint,omitempty"` + URL string `json:"url,omitempty"` +} + +type Timeouts struct { + HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"` + HTTPTotal *int `json:"httpTotal,omitempty"` +} + +type Unit struct { + Contents *string `json:"contents,omitempty"` + Dropins []Dropin `json:"dropins,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Mask *bool `json:"mask,omitempty"` + Name string `json:"name"` +} + +type Verification struct { + Hash *string `json:"hash,omitempty"` +}
\ No newline at end of file diff --git a/test/performance.sh b/test/performance.sh new file mode 100755 index 0000000..4f4c413 --- /dev/null +++ b/test/performance.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -x + +VM=$1 +GVPROXY_SOCKET=$2 + +echo "Testing Internet access with a server running on the host" + +nohup iperf3 -s > /dev/null 2>&1 & +serverPID=$! + +ssh $VM curl https://iperf.fr/download/ubuntu/libiperf.so.0_3.1.3 -o libiperf.so.0 +ssh $VM curl https://iperf.fr/download/ubuntu/iperf3_3.1.3 -o iperf3 +ssh $VM chmod +x iperf3 + +echo "TCP: sending data" +ssh $VM LD_LIBRARY_PATH=. ./iperf3 -c host.crc.testing +echo "TCP: receiving data" +ssh $VM LD_LIBRARY_PATH=. ./iperf3 -c host.crc.testing -R + +echo "UDP: sending data" +ssh $VM LD_LIBRARY_PATH=. ./iperf3 -c host.crc.testing -u +echo "UDP: receiving data" +ssh $VM LD_LIBRARY_PATH=. ./iperf3 -c host.crc.testing -R -u + +kill $serverPID + +echo "Testing forwarder with a server running in the VM" + +curl --unix-socket $GVPROXY_SOCKET http:/unix/services/forwarder/expose -X POST \ + -d'{"local":":5201", "protocol": "udp", "remote": "192.168.127.2:5201"}' +curl --unix-socket $GVPROXY_SOCKET http:/unix/services/forwarder/expose -X POST \ + -d'{"local":":5201", "protocol": "tcp", "remote": "192.168.127.2:5201"}' + +ssh $VM LD_LIBRARY_PATH=. ./iperf3 -s > /dev/null 2>&1 & +sleep 1 + +echo "TCP: sending data" +iperf3 -c 127.0.0.1 +echo "TCP: receiving data" +iperf3 -c 127.0.0.1 -R + +echo "UDP: sending data" +iperf3 -c 127.0.0.1 -u -l 9216 +echo "UDP: receiving data" +iperf3 -c 127.0.0.1 -R -u -l 9216 + +ssh $VM pkill iperf3 + +curl --unix-socket $GVPROXY_SOCKET http:/unix/services/forwarder/unexpose -X POST \ + -d'{"local":":5201", "protocol": "udp"}' +curl --unix-socket $GVPROXY_SOCKET http:/unix/services/forwarder/unexpose -X POST \ + -d'{"local":":5201", "protocol": "tcp"}' diff --git a/test/port_forwarding_test.go b/test/port_forwarding_test.go new file mode 100644 index 0000000..c38e4f6 --- /dev/null +++ b/test/port_forwarding_test.go @@ -0,0 +1,269 @@ +package e2e + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "time" + + gvproxyclient "github.com/containers/gvisor-tap-vsock/pkg/client" + "github.com/containers/gvisor-tap-vsock/pkg/transport" + "github.com/containers/gvisor-tap-vsock/pkg/types" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" +) + +var _ = ginkgo.Describe("port forwarding", func() { + client := gvproxyclient.New(&http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", sock) + }, + }, + }, "http://base") + + ginkgo.It("should reach a http server on the host", func() { + ln, err := net.Listen("tcp", "127.0.0.1:9090") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + defer ln.Close() + + mux := http.NewServeMux() + mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { + _, _ = writer.Write([]byte("Hello from the host")) + }) + go func() { + s := &http.Server{ + Handler: mux, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + err := s.Serve(ln) + if err != nil { + log.Error(err) + } + }() + + out, err := sshExec("curl http://host.containers.internal:9090") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Hello from the host")) + + out, err = sshExec("curl http://host.docker.internal:9090") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(string(out)).To(gomega.ContainSubstring("Hello from the host")) + }) + + ginkgo.It("should reach a http server in the VM using dynamic port forwarding", func() { + _, err := net.Dial("tcp", "127.0.0.1:9090") + gomega.Expect(err).Should(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.HaveSuffix("connection refused")) + + gomega.Expect(client.Expose(&types.ExposeRequest{ + Local: "127.0.0.1:9090", + Remote: "192.168.127.2:8080", + })).Should(gomega.Succeed()) + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := http.Get("http://127.0.0.1:9090") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + }).Should(gomega.Succeed()) + + gomega.Expect(client.Unexpose(&types.UnexposeRequest{ + Local: "127.0.0.1:9090", + })).Should(gomega.Succeed()) + + gomega.Eventually(func(g gomega.Gomega) { + _, err = net.Dial("tcp", "127.0.0.1:9090") + g.Expect(err).Should(gomega.HaveOccurred()) + g.Expect(err.Error()).To(gomega.HaveSuffix("connection refused")) + }).Should(gomega.Succeed()) + }) + + ginkgo.It("should reach a dns server in the VM using dynamic port forwarding", func() { + gomega.Expect(client.Expose(&types.ExposeRequest{ + Local: ":1053", + Remote: "192.168.127.2:53", + Protocol: "udp", + })).Should(gomega.Succeed()) + + gomega.Eventually(func(g gomega.Gomega) { + cmd := exec.Command("nslookup", "-timeout=1", "-port=1053", "foobar", "127.0.0.1") + out, err := cmd.CombinedOutput() + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(string(out)).To(gomega.ContainSubstring("Address: 1.2.3.4")) + }).Should(gomega.Succeed()) + + gomega.Expect(client.Unexpose(&types.UnexposeRequest{ + Local: ":1053", + Protocol: "udp", + })).Should(gomega.Succeed()) + }) + + ginkgo.It("should reach a http server in the VM using the tunneling of the daemon", func() { + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + conn, err := net.Dial("unix", sock) + if err != nil { + return nil, err + } + return conn, transport.Tunnel(conn, "192.168.127.2", 8080) + }, + }, + } + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := httpClient.Get("http://placeholder/") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + }).Should(gomega.Succeed()) + }) + + ginkgo.It("should reach a http server in the VM using dynamic port forwarding configured within the VM", func() { + _, err := net.Dial("tcp", "127.0.0.1:9090") + gomega.Expect(err).Should(gomega.HaveOccurred()) + gomega.Expect(err.Error()).To(gomega.HaveSuffix("connection refused")) + + _, err = sshExec(`curl http://gateway.containers.internal/services/forwarder/expose -X POST -d'{"local":":9090", "remote":":8080"}'`) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := http.Get("http://127.0.0.1:9090") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + }).Should(gomega.Succeed()) + + _, err = sshExec(`curl http://gateway.containers.internal/services/forwarder/unexpose -X POST -d'{"local":":9090"}'`) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + gomega.Eventually(func(g gomega.Gomega) { + _, err = net.Dial("tcp", "127.0.0.1:9090") + g.Expect(err).Should(gomega.HaveOccurred()) + g.Expect(err.Error()).To(gomega.HaveSuffix("connection refused")) + }).Should(gomega.Succeed()) + }) + + ginkgo.It("should reach rootless podman API using unix socket forwarding over ssh", func() { + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", forwardSock) + }, + }, + } + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := httpClient.Get("http://host/_ping") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + g.Expect(resp.ContentLength).To(gomega.Equal(int64(2))) + + reply := make([]byte, resp.ContentLength) + _, err = io.ReadAtLeast(resp.Body, reply, len(reply)) + + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(string(reply)).To(gomega.Equal("OK")) + }).Should(gomega.Succeed()) + }) + + ginkgo.It("should reach rootful podman API using unix socket forwarding over ssh", func() { + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", forwardRootSock) + }, + }, + } + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := httpClient.Get("http://host/_ping") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + g.Expect(resp.ContentLength).To(gomega.Equal(int64(2))) + + reply := make([]byte, resp.ContentLength) + _, err = io.ReadAtLeast(resp.Body, reply, len(reply)) + + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(string(reply)).To(gomega.Equal("OK")) + }).Should(gomega.Succeed()) + }) + + ginkgo.It("should expose and reach an http service using unix to tcp forwarding", func() { + if runtime.GOOS == "windows" { + ginkgo.Skip("AF_UNIX not supported on Windows") + } + + unix2tcpfwdsock, _ := filepath.Abs(filepath.Join(tmpDir, "podman-unix-to-unix-forwarding.sock")) + + out, err := sshExec(`curl http://gateway.containers.internal/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2tcpfwdsock + `","remote":"tcp://192.168.127.2:8080"}'`) + gomega.Expect(string(out)).Should(gomega.Equal("")) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + gomega.Eventually(func(g gomega.Gomega) { + sockfile, err := os.Stat(unix2tcpfwdsock) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(sockfile.Mode().Type().String()).To(gomega.Equal(os.ModeSocket.String())) + }).Should(gomega.Succeed()) + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", unix2tcpfwdsock) + }, + }, + } + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := httpClient.Get("http://placeholder/") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + }).Should(gomega.Succeed()) + }) + + ginkgo.It("should expose and reach rootless podman API using unix to unix forwarding over ssh", func() { + if runtime.GOOS == "windows" { + ginkgo.Skip("AF_UNIX not supported on Windows") + } + + unix2unixfwdsock, _ := filepath.Abs(filepath.Join(tmpDir, "podman-unix-to-unix-forwarding.sock")) + + remoteuri := fmt.Sprintf(`ssh-tunnel://root@%s:%d%s?key=%s`, "192.168.127.2", 22, podmanSock, privateKeyFile) + _, err := sshExec(`curl http://192.168.127.1/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2unixfwdsock + `","remote":"` + remoteuri + `"}'`) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + gomega.Eventually(func(g gomega.Gomega) { + sockfile, err := os.Stat(unix2unixfwdsock) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(sockfile.Mode().Type().String()).To(gomega.Equal(os.ModeSocket.String())) + }).Should(gomega.Succeed()) + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", unix2unixfwdsock) + }, + }, + } + + gomega.Eventually(func(g gomega.Gomega) { + resp, err := httpClient.Get("http://host/_ping") + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(resp.StatusCode).To(gomega.Equal(http.StatusOK)) + g.Expect(resp.ContentLength).To(gomega.Equal(int64(2))) + + reply := make([]byte, resp.ContentLength) + _, err = io.ReadAtLeast(resp.Body, reply, len(reply)) + + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + g.Expect(string(reply)).To(gomega.Equal("OK")) + }).Should(gomega.Succeed()) + }) +}) diff --git a/test/pull.go b/test/pull.go new file mode 100644 index 0000000..f1fa236 --- /dev/null +++ b/test/pull.go @@ -0,0 +1,81 @@ +package e2e + +import ( + "fmt" + "io" + "net/http" + "os" + "os/exec" + "strings" + + "github.com/sirupsen/logrus" +) + +// DownloadVMImage downloads a VM image from url to given path +// with download status +func DownloadVMImage(downloadURL string, localImagePath string) error { + fmt.Println("Downloading VM image: " + downloadURL) + + out, err := os.Create(localImagePath) + if err != nil { + return err + } + defer func() { + if err := out.Close(); err != nil { + logrus.Error(err) + } + }() + + // #nosec + resp, err := http.Get(downloadURL) + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + logrus.Error(err) + } + }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status) + } + + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + + return nil +} + +func Decompress(localPath, uncompressedPath string) error { + uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return err + } + + if !strings.HasSuffix(localPath, ".xz") { + return fmt.Errorf("unsupported compression for %s", localPath) + } + + fmt.Printf("Extracting %s\n", localPath) + return decompressXZ(localPath, uncompressedFileWriter) +} + +// Will error out if file without .xz already exists +// Maybe extracting then renameing is a good idea here.. +// depends on xz: not pre-installed on mac, so it becomes a brew dependency +func decompressXZ(src string, output io.Writer) error { + cmd := exec.Command("xzcat", "-T0", "-k", src) + stdOut, err := cmd.StdoutPipe() + if err != nil { + return err + } + cmd.Stderr = os.Stderr + go func() { + if _, err := io.Copy(output, stdOut); err != nil { + logrus.Error(err) + } + }() + return cmd.Run() +} diff --git a/test/suite_test.go b/test/suite_test.go new file mode 100644 index 0000000..5c08d5f --- /dev/null +++ b/test/suite_test.go @@ -0,0 +1,242 @@ +package e2e + +import ( + "flag" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func TestSuite(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "gvisor-tap-vsock suite") +} + +const ( + sock = "/tmp/gvproxy-api.sock" + qemuPort = 5555 + sshPort = 2222 + ignitionUser = "test" + qconLog = "qcon.log" + podmanSock = "/run/user/1001/podman/podman.sock" + podmanRootSock = "/run/podman/podman.sock" + + // #nosec "test" (for manual usage) + ignitionPasswordHash = "$y$j9T$TqJWt3/mKJbH0sYi6B/LD1$QjVRuUgntjTHjAdAkqhkr4F73m.Be4jBXdAaKw98sPC" +) + +var ( + tmpDir string + binDir string + host *exec.Cmd + client *exec.Cmd + privateKeyFile string + publicKeyFile string + ignFile string + forwardSock string + forwardRootSock string +) + +func init() { + flag.StringVar(&tmpDir, "tmpDir", "../tmp", "temporary working directory") + flag.StringVar(&binDir, "bin", "../bin", "directory with compiled binaries") + privateKeyFile = filepath.Join(tmpDir, "id_test") + publicKeyFile = privateKeyFile + ".pub" + ignFile = filepath.Join(tmpDir, "test.ign") + forwardSock = filepath.Join(tmpDir, "podman-remote.sock") + forwardRootSock = filepath.Join(tmpDir, "podman-root-remote.sock") + +} + +var _ = ginkgo.BeforeSuite(func() { + gomega.Expect(os.MkdirAll(filepath.Join(tmpDir, "disks"), os.ModePerm)).Should(gomega.Succeed()) + + downloader, err := NewFcosDownloader(filepath.Join(tmpDir, "disks")) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + qemuImage, err := downloader.DownloadImage() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + publicKey, err := createSSHKeys() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + err = CreateIgnition(ignFile, publicKey, ignitionUser, ignitionPasswordHash) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + +outer: + for panics := 0; ; panics++ { + _ = os.Remove(sock) + + // #nosec + host = exec.Command(filepath.Join(binDir, "gvproxy"), fmt.Sprintf("--listen=unix://%s", sock), fmt.Sprintf("--listen-qemu=tcp://127.0.0.1:%d", qemuPort), + fmt.Sprintf("--forward-sock=%s", forwardSock), fmt.Sprintf("--forward-dest=%s", podmanSock), fmt.Sprintf("--forward-user=%s", ignitionUser), + fmt.Sprintf("--forward-identity=%s", privateKeyFile), + fmt.Sprintf("--forward-sock=%s", forwardRootSock), fmt.Sprintf("--forward-dest=%s", podmanRootSock), fmt.Sprintf("--forward-user=%s", "root"), + fmt.Sprintf("--forward-identity=%s", privateKeyFile)) + + host.Stderr = os.Stderr + host.Stdout = os.Stdout + gomega.Expect(host.Start()).Should(gomega.Succeed()) + go func() { + if err := host.Wait(); err != nil { + log.Error(err) + } + }() + + for { + _, err := os.Stat(sock) + if os.IsNotExist(err) { + log.Info("waiting for socket") + time.Sleep(100 * time.Millisecond) + continue + } + break + } + + template := `%s -m 2048 -nographic -serial file:%s -snapshot -drive if=virtio,file=%s -fw_cfg name=opt/com.coreos/config,file=%s -netdev socket,id=vlan,connect=127.0.0.1:%d -device virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee` + // #nosec + client = exec.Command(qemuExecutable(), strings.Split(fmt.Sprintf(template, qemuArgs(), qconLog, qemuImage, ignFile, qemuPort), " ")...) + client.Stderr = os.Stderr + client.Stdout = os.Stdout + gomega.Expect(client.Start()).Should(gomega.Succeed()) + go func() { + if err := client.Wait(); err != nil { + log.Error(err) + } + }() + + for { + _, err := sshExec("whoami") + if err == nil { + break outer + } + + // Check for panic + didPanic, err := panicCheck(qconLog) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + if didPanic { + gomega.Expect(panics).ToNot(gomega.BeNumerically(">", 15), "No more than 15 panics allowed") + log.Info("Detected Kernel panic, retrying...") + _ = client.Process.Kill() + _ = host.Process.Kill() + _ = os.Remove(qconLog) + continue outer + } + + log.Infof("waiting for client to connect: %v", err) + time.Sleep(time.Second) + } + } + + err = scp(filepath.Join(binDir, "test-companion"), "/tmp/test-companion") + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + + // start an embedded DNS and http server in the VM. Wait a bit for the server to start. + cmd := sshCommand("sudo /tmp/test-companion") + gomega.Expect(cmd.Start()).ShouldNot(gomega.HaveOccurred()) + time.Sleep(5 * time.Second) +}) + +func qemuExecutable() string { + if runtime.GOOS == "darwin" { + return "qemu-system-x86_64" + } + return "qemu-kvm" +} + +func qemuArgs() string { + if runtime.GOOS == "darwin" { + return "-machine q35,accel=hvf:tcg -smp 4 -cpu host" + } + return "-cpu host" +} + +func createSSHKeys() (string, error) { + _ = os.Remove(publicKeyFile) + _ = os.Remove(privateKeyFile) + err := exec.Command("ssh-keygen", "-N", "", "-t", "ed25519", "-f", privateKeyFile).Run() + if err != nil { + return "", errors.Wrap(err, "Could not generate ssh keys") + } + + return readPublicKey() +} + +func readPublicKey() (string, error) { + publicKey, err := os.ReadFile(publicKeyFile) + if err != nil { + return "", nil + } + + return strings.TrimSpace(string(publicKey)), nil +} + +func scp(src, dst string) error { + sshCmd := exec.Command("scp", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-i", privateKeyFile, + "-P", strconv.Itoa(sshPort), + src, + fmt.Sprintf("%s@127.0.0.1:%s", ignitionUser, dst)) // #nosec G204 + sshCmd.Stderr = os.Stderr + sshCmd.Stdout = os.Stdout + return sshCmd.Run() +} + +func sshExec(cmd ...string) ([]byte, error) { + return sshCommand(cmd...).Output() +} + +func sshCommand(cmd ...string) *exec.Cmd { + sshCmd := exec.Command("ssh", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "StrictHostKeyChecking=no", + "-i", privateKeyFile, + "-p", strconv.Itoa(sshPort), + fmt.Sprintf("%s@127.0.0.1", ignitionUser), "--", strings.Join(cmd, " ")) // #nosec G204 + return sshCmd +} + +func panicCheck(con string) (bool, error) { + file, err := os.Open(con) + if err != nil { + return false, err + } + + _, _ = file.Seek(-500, io.SeekEnd) + // Ignore seek errors (not enough content yet) + + contents := make([]byte, 500) + _, err = io.ReadAtLeast(file, contents, len(contents)) + if err != nil && err != io.ErrUnexpectedEOF { + return false, err + } + + return strings.Contains(string(contents), "end Kernel panic"), nil +} + +var _ = ginkgo.AfterSuite(func() { + if host != nil { + if err := host.Process.Kill(); err != nil { + log.Error(err) + } + } + if client != nil { + if err := client.Process.Kill(); err != nil { + log.Error(err) + } + } +}) diff --git a/test/wsl.sh b/test/wsl.sh new file mode 100755 index 0000000..a28daf5 --- /dev/null +++ b/test/wsl.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x + +./bin/gvforwarder \ + -url="stdio:$(pwd)/bin/gvproxy-windows.exe?listen-stdio=accept&debug=true" \ + -iface="eth1" \ + -stop-if-exist="" \ + -debug |