Skip to content

Create installation VM and run bootc install inside a VM using rootless podman #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
binary_name = podman-bootc
binary_proxy= vsock-proxy
output_dir = bin
build_tags = exclude_graphdriver_btrfs,btrfs_noversion,exclude_graphdriver_devicemapper,containers_image_openpgp,remote

Expand All @@ -10,6 +11,10 @@ vm_image = $(registry)/$(vm_image_name):$(vm_image_tag)
all: out_dir docs
go build -tags $(build_tags) $(GOOPTS) -o $(output_dir)/$(binary_name)

.PHONY: proxy
proxy: out_dir
go build -o ${output_dir}/$(binary_proxy) ./proxy

out_dir:
mkdir -p $(output_dir)

Expand All @@ -23,10 +28,9 @@ integration_tests:
e2e_test: all
ginkgo -tags $(build_tags) ./test/...

image:
image: proxy
podman build -t $(vm_image) --device /dev/kvm \
-f containerfiles/vm/Containerfile \
containerfiles/vm
-f containerfiles/vm/Containerfile .

.PHONY: docs
docs:
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/docker/docker v25.0.3+incompatible
github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.8.1
github.com/mdlayher/vsock v1.2.1
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0
github.com/opencontainers/runtime-spec v1.2.0
Expand All @@ -22,6 +23,8 @@ require (
libvirt.org/go/libvirt v1.10002.0
)

require github.com/mdlayher/socket v0.4.1 // indirect

require (
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=
Expand Down
185 changes: 185 additions & 0 deletions pkg/vsock/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package vsock

import (
"context"
"fmt"
"io"
"net"
"os"

"github.com/mdlayher/vsock"
log "github.com/sirupsen/logrus"
)

type Proxy struct {
cid uint32
port uint32
socket string
done chan struct{}
start func(socket string, port, cid uint32, done chan struct{}) error
}

func NewProxyUnixSocketToVsock(port, cid uint32, socket string) *Proxy {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, you can use https://github.com/inetaf/tcpproxy to implement this kind of proxying, see https://github.com/crc-org/vfkit/blob/main/pkg/vf/vsock.go for an example of this.
Not sure it’s going to be significantly less code though, so the additional dependency is not necessarily worth it.

p := &Proxy{
cid: cid,
port: port,
socket: socket,
done: make(chan struct{}),
start: startUnixToVsock,
}
return p
}

func NewProxyVSockToUnixSocket(port uint32, socket string) *Proxy {
p := &Proxy{
port: port,
socket: socket,
done: make(chan struct{}),
start: startVsockToUnix,
}
return p
}

func (proxy *Proxy) GetSocket() string {
return proxy.socket
}

func (proxy *Proxy) Stop() {
select {
case <-proxy.done:
// already closed
default:
close(proxy.done)
}
os.Remove(proxy.socket)
log.Debugf("Stopped proxy")
}

func (p *Proxy) Start() error {
return p.start(p.socket, p.port, p.cid, p.done)
}

func startUnixToVsock(socket string, port, cid uint32, done chan struct{}) error {
_ = os.Remove(socket)

unixListener, err := net.Listen("unix", socket)
if err != nil {
return fmt.Errorf("Failed to listen on unix socket: %v", err)
}
go func() {
defer unixListener.Close()

for {
select {
case <-done:
return
default:
unixConn, err := unixListener.Accept()
if err != nil {
log.Warnf("Accept error: %v", err)
continue
}
log.Debugf("Accepted connection from %s to port %d and cid", socket, port, cid)

go handleConnectionToVsock(unixConn, port, cid, done)
}
}
}()

log.Debugf("Started proxy at: %s", socket)

return nil
}

func handleConnectionToVsock(unixConn net.Conn, port, cid uint32, done chan struct{}) {
defer unixConn.Close()
vsockConn, err := vsock.Dial(cid, port, nil)
if err != nil {
log.Printf("vsock connect error (cid: %d, port: %d): %v", cid, port, err)
return
}
defer vsockConn.Close()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

errCh := make(chan error, 2)
go proxy(ctx, vsockConn, unixConn, errCh, done)
go proxy(ctx, unixConn, vsockConn, errCh, done)

// Wait for the first error or cancellation
select {
case <-done:
case err := <-errCh:
if err != nil && err != io.EOF {
log.Errorf("proxy error: %v", err)
}
}
}

func proxy(ctx context.Context, src, dst net.Conn, errCh chan error, done chan struct{}) {
go func() {
_, err := io.Copy(dst, src)
errCh <- err
}()
select {
case <-ctx.Done():
case <-done:
case <-errCh:
}
}

func startVsockToUnix(socket string, port, cid uint32, done chan struct{}) error {
vsockListener, err := vsock.Listen(port, &vsock.Config{})
if err != nil {
return fmt.Errorf("failed to listen on vsock port %d: %v", port, err)
}
go func() {
defer vsockListener.Close()

for {
select {
case <-done:
return
default:
vsockConn, err := vsockListener.Accept()
if err != nil {
log.Warnf("Accept error: %v", err)
continue
}
log.Debugf("Accepted connection from port %d to socket %d", port, socket)

go handleConnectionToUnix(vsockConn, socket, port, done)
}
}
}()

log.Debugf("Started proxy at port: %d", port)

return nil
}

func handleConnectionToUnix(vsockConn net.Conn, socket string, port uint32, done chan struct{}) {
defer vsockConn.Close()

conn, err := net.Dial("unix", socket)
if err != nil {
log.Errorf("failed to connect: %v", err)
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

errCh := make(chan error, 2)
go proxy(ctx, conn, vsockConn, errCh, done)
go proxy(ctx, vsockConn, conn, errCh, done)

// Wait for the first error or cancellation
select {
case <-done:
case err := <-errCh:
if err != nil && err != io.EOF {
log.Errorf("proxy error: %v", err)
}
}
}
130 changes: 130 additions & 0 deletions proxy/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package cmd

import (
"context"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/containers/podman-bootc/pkg/vsock"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

type mode string

const (
unixToVsock mode = "unixToVsock"
vsockToUnix mode = "vsockToUnix"
)

func (m *mode) String() string {
return string(*m)
}

func (m *mode) Set(val string) error {
switch val {
case string(vsockToUnix), string(unixToVsock):
*m = mode(val)
return nil
default:
return fmt.Errorf("invalid mode: %s (must be '%s' or '%s')", val, unixToVsock, vsockToUnix)
}
}

func (m *mode) Type() string {
return "mode"
}

type rootCmd struct {
proxy *vsock.Proxy
logLevel string
listenMode mode
cid uint32
port uint32
socket string
}

func NewRootCmd() *cobra.Command {
c := rootCmd{}
cmd := &cobra.Command{
Use: "proxy",
Short: "Proxy connections between VSOCK and UNIX socket",
Long: "Proxy the connection between VSOCK and UNIX socket based on the direction",
PersistentPreRunE: c.preExec,
RunE: func(cmd *cobra.Command, _ []string) error {
return c.run()
},
}

cmd.PersistentFlags().Uint32VarP(&c.cid, "cid", "c", 0, "CID allocated by the VM")
cmd.PersistentFlags().Uint32VarP(&c.port, "port", "p", 0, "Port for the VSOCK on the VM")
cmd.PersistentFlags().StringVarP(&c.socket, "socket", "s", "", "Socket for the proxy")
cmd.PersistentFlags().StringVarP(&c.logLevel, "log-level", "", "", "Set log level")
cmd.PersistentFlags().VarP(&c.listenMode, "listen-mode", "l",
fmt.Sprintf("Direction for the listentin proxy, values: %s or %s", unixToVsock, vsockToUnix))
cmd.MarkPersistentFlagRequired("port")
cmd.MarkPersistentFlagRequired("socket")
cmd.MarkPersistentFlagRequired("listen-mode")

return cmd
}

func (c *rootCmd) preExec(cmd *cobra.Command, args []string) error {
if c.logLevel != "" {
level, err := log.ParseLevel(c.logLevel)
if err != nil {
return err
}
log.SetLevel(level)
} else {
log.SetLevel(log.InfoLevel)
}
socket, _ := cmd.Flags().GetString("socket")
if socket == "" {
return fmt.Errorf("the socket needs to be set")
}

return nil
}

func (c *rootCmd) validateArgs() error {
if c.port == 0 {
return fmt.Errorf("the port cannot be 0")
}
if c.listenMode == unixToVsock && c.cid == 0 {
return fmt.Errorf("the cid cannot be 0 when the listen mode is unixToVsock")
}

return nil
}

func (c *rootCmd) run() error {
if err := c.validateArgs(); err != nil {
return err
}
switch c.listenMode {
case vsockToUnix:
c.proxy = vsock.NewProxyVSockToUnixSocket(c.port, c.socket)
case unixToVsock:
c.proxy = vsock.NewProxyUnixSocketToVsock(c.port, c.cid, c.socket)
}

if err := c.proxy.Start(); err != nil {
return err
}
defer c.proxy.Stop()

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()

return nil
}

func Execute() {
if err := NewRootCmd().Execute(); err != nil {
os.Exit(1)
}
}
7 changes: 7 additions & 0 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/containers/podman-bootc/proxy/cmd"

func main() {
cmd.Execute()
}