diff --git a/README.md b/README.md index 2d41a7c..a3178cd 100644 --- a/README.md +++ b/README.md @@ -124,9 +124,3 @@ This wraps the environment with all Go tools and build dependencies preinstalled - `qemu-system-x86_64` (x86_64 VMs are supported and tested) - `qemu-system-aarch64` (ARM64 VMs are supported and tested) - -## 🔧 Networking Notes - -On Linux, a DHCP server is required for automated VM networking (e.g., bridge + TAP). For setup and an example configuration, see [dhcp](/dhcp/README.md). - -> **Note**: DHCP is not bundled with this project to give users the freedom to plug into their existing setup diff --git a/dhcp/Dockerfile b/dhcp/Dockerfile deleted file mode 100644 index 0fff0f7..0000000 --- a/dhcp/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM alpine:3.20 AS dhcp - -RUN apk add --no-cache dnsmasq bash iptables - -ADD dhcp-entrypoint.sh /usr/bin/dhcp-entrypoint.sh - -ENTRYPOINT ["/usr/bin/dhcp-entrypoint.sh"] diff --git a/dhcp/README.md b/dhcp/README.md deleted file mode 100644 index c8a0787..0000000 --- a/dhcp/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Run DHCP - -```shell -docker run --rm -d \ - --cap-add NET_ADMIN \ - --network host \ - --privileged \ - --device /dev/net/tun:/dev/net/tun \ - $(docker build -q .) \ - -n "${INTERFACE_NAME}" -``` diff --git a/dhcp/dhcp-entrypoint.sh b/dhcp/dhcp-entrypoint.sh deleted file mode 100755 index 81e97f4..0000000 --- a/dhcp/dhcp-entrypoint.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash - -set -Eeuo pipefail -trap cleanup SIGINT SIGTERM ERR EXIT - -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) -INTERFACE_NAME="krjakbrjakbr0" -LOCATION=$(mktemp -d) - -usage() { - cat <&2 -e "$msg" - exit "$code" -} - -parse_params() { - while :; do - case "${1-}" in - -h | --help) usage ;; - -v | --verbose) set -x ;; - -n | --name) - INTERFACE_NAME="${2-}" - shift - ;; - -?*) die "Unknown option: $1" ;; - *) break ;; - esac - shift - done - - args=("$@") - - return 0 -} - -parse_params "$@" - -until ip link show ${INTERFACE_NAME} 2>/dev/null; do - echo "Waiting for ${INTERFACE_NAME} to be CREATED..." - sleep 1 -done - -while true; do - IP_INFO=$(ip -o -f inet addr show "${INTERFACE_NAME}" | awk '{print $4}') - if [ -n "${IP_INFO}" ]; then - IFS='./' read -r a b c d mask <<< "${IP_INFO}" - cat < ${LOCATION}/dnsmasq.conf -port=0 # Disable DNS server -interface=${INTERFACE_NAME} -bind-interfaces -listen-address=${a}.${b}.${c}.${d} -dhcp-range=${a}.${b}.${c}.$((d+1)),${a}.${b}.${c}.$((d+100)),255.255.255.0,12h -dhcp-option=3,${a}.${b}.${c}.${d} # Gateway -dhcp-option=6,8.8.8.8,1.1.1.1 # DNS -log-queries -log-dhcp -EOF - break - fi -done - -until ip link show ${INTERFACE_NAME} | grep -q 'state UP'; do - echo "Waiting for ${INTERFACE_NAME} to be UP..." - sleep 1 -done - -exec dnsmasq -k --conf-file=${LOCATION}/dnsmasq.conf --dhcp-ignore-clid --dhcp-authoritative --dhcp-no-override diff --git a/go.mod b/go.mod index a6145f8..0c5cd61 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ replace github.com/q-controller/qemu-client => ./qemu-client replace github.com/q-controller/qapi-client => ./qapi-client +replace github.com/coredhcp/coredhcp => github.com/krjakbrjak/coredhcp v0.0.0-20251128103901-a2aa0dc11269 + require ( github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 @@ -21,36 +23,62 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/bits-and-blooms/bitset v1.24.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb // indirect + github.com/coredhcp/coredhcp v0.0.0-20250927164030-d2ed887fca9b // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gopacket v1.1.19 // indirect github.com/google/nftables v0.3.0 // indirect + github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/socket v0.5.0 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/viper v1.20.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/swaggo/files/v2 v2.0.0 // indirect github.com/swaggo/swag v1.8.1 // indirect + github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/vishvananda/netns v0.0.5 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.26.0 // indirect - golang.org/x/tools v0.33.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -58,9 +86,9 @@ require ( github.com/dgraph-io/badger/v4 v4.8.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/q-controller/qapi-client v0.0.0-00010101000000-000000000000 - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.7 // indirect github.com/swaggo/http-swagger/v2 v2.0.2 - github.com/vishvananda/netlink v1.3.1 - golang.org/x/sync v0.15.0 + github.com/vishvananda/netlink v1.3.1 // indirect + golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.6 ) diff --git a/go.sum b/go.sum index 9237109..250734c 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,18 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= +github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb h1:aZTKxMminKeQWHtzJBbV8TttfTxzdJ+7iEJFE6FmUzg= +github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb/go.mod h1:xzXc1S/L+64uglB3pw54o8kqyM6KFYpTeC9Q6+qZIu8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= @@ -17,6 +22,10 @@ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa5 github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -32,12 +41,16 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg= github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -46,8 +59,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef h1:NzQKDfd5ZOPnuZYf9MnRee8x2qecsVqzsnaLjEZiBko= +github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -57,43 +74,89 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/krjakbrjak/coredhcp v0.0.0-20251128103901-a2aa0dc11269 h1:btYwynwpjVZ8FNtgWGvWGSzyFoZb0NdQp3Wkaa0gR4o= +github.com/krjakbrjak/coredhcp v0.0.0-20251128103901-a2aa0dc11269/go.mod h1:1yk/ElKGa71B3YnjAeBhHuQTnzJk82A0ab/u2PVbDok= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= +github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= +github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= @@ -106,20 +169,41 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= @@ -133,6 +217,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/network-utils b/network-utils index 3a5ed00..76d75e9 160000 --- a/network-utils +++ b/network-utils @@ -1 +1 @@ -Subproject commit 3a5ed000c669b387afaa683347c9a3b09ef6c924 +Subproject commit 76d75e9d6c25489e24fa98cff5c10c7b73526c6c diff --git a/qapi-client b/qapi-client index 6fa9e16..8321b74 160000 --- a/qapi-client +++ b/qapi-client @@ -1 +1 @@ -Subproject commit 6fa9e16ef14536cec423d7bf73b667b7eb97b001 +Subproject commit 8321b748a06921916e75bf459657f10b60b6b9cd diff --git a/src/pkg/protos/networkmanager_linux.go b/src/pkg/protos/networkmanager_linux.go deleted file mode 100644 index 191d2bf..0000000 --- a/src/pkg/protos/networkmanager_linux.go +++ /dev/null @@ -1,39 +0,0 @@ -package protos - -import ( - "context" - - servicesv1 "github.com/q-controller/qcontroller/src/generated/services/v1" - settingsv1 "github.com/q-controller/qcontroller/src/generated/settings/v1" - "github.com/q-controller/qcontroller/src/pkg/utils/network" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -type NetworkManagerServer struct { - servicesv1.UnimplementedNetworkManagerServiceServer - - nm network.NetworkManager -} - -func (s *NetworkManagerServer) CreateInterface(ctx context.Context, - req *servicesv1.CreateInterfaceRequest) (*emptypb.Empty, error) { - return &emptypb.Empty{}, s.nm.CreateInterface(req.Name) -} - -func (s *NetworkManagerServer) RemoveInterface(ctx context.Context, - req *servicesv1.RemoveInterfaceRequest) (*emptypb.Empty, error) { - return &emptypb.Empty{}, s.nm.RemoveInterface(req.Name) -} - -func NewNetworkManager(bridge *settingsv1.Bridge) (servicesv1.NetworkManagerServiceServer, error) { - nm, nmErr := network.NewNetworkManager(bridge.Name, bridge.Subnet) - if nmErr != nil { - return nil, nmErr - } - - server := &NetworkManagerServer{ - nm: nm, - } - - return server, nil -} diff --git a/src/pkg/protos/process.go b/src/pkg/protos/process.go index cbc3e01..a05321d 100644 --- a/src/pkg/protos/process.go +++ b/src/pkg/protos/process.go @@ -237,7 +237,7 @@ func NewQemuService(monitor *process.InstanceMonitor, config *settingsv1.QemuCon } if linuxSettings := config.GetLinuxSettings(); linuxSettings != nil { - nm, nmErr := network.NewNetworkManager(linuxSettings.Bridge.Name, linuxSettings.Bridge.Subnet) + nm, nmErr := network.NewNetworkManager(linuxSettings.Network.Name, linuxSettings.Network.BridgeIp) if nmErr != nil { return nil, nmErr } diff --git a/src/pkg/qemu/process/instancemonitor.go b/src/pkg/qemu/process/instancemonitor.go index 022801a..dbd0b2f 100644 --- a/src/pkg/qemu/process/instancemonitor.go +++ b/src/pkg/qemu/process/instancemonitor.go @@ -164,6 +164,11 @@ func NewInstanceMonitor() (*InstanceMonitor, error) { if msg.Generic != nil { var greeting Greeting if err := json.Unmarshal(msg.Generic, &greeting); err == nil { + // Verify that the unmarshaled greeting has the expected QMP structure + if greeting.QMP.Version.Qemu.Major == 0 && greeting.QMP.Version.Qemu.Minor == 0 && greeting.QMP.Version.Qemu.Micro == 0 { + continue // Invalid greeting structure + } + if req, reqErr := qapi.PrepareQmpCapabilitiesRequest(qapi.QObjQmpCapabilitiesArg{}); reqErr == nil { if ch, chErr := qapiClient.Execute(msg.Instance, client.Request(*req)); chErr == nil { res, resOk := ch.Get(context.Background(), -1) diff --git a/src/pkg/utils/network/networkmanager_linux.go b/src/pkg/utils/network/networkmanager_linux.go index ad55db4..ae9624c 100644 --- a/src/pkg/utils/network/networkmanager_linux.go +++ b/src/pkg/utils/network/networkmanager_linux.go @@ -1,19 +1,11 @@ package network import ( - "errors" - "log/slog" - "net" "sync" - "github.com/q-controller/network-utils/src/utils/network/firewall" "github.com/q-controller/network-utils/src/utils/network/ifc" - "github.com/vishvananda/netlink" - "github.com/vishvananda/netlink/nl" ) -var ErrNetworkDisconnected = errors.New("network disconnected") - type linuxNetworkManager struct { bridgeName string mu sync.RWMutex @@ -25,25 +17,6 @@ func (m *linuxNetworkManager) Close() { m.done <- struct{}{} } -// getDefaultInterface finds the default network interface -func getDefaultInterface() (string, error) { - routes, err := netlink.RouteList(nil, nl.FAMILY_V4) - if err != nil { - return "", err - } - - for _, route := range routes { - if route.Dst.IP.IsUnspecified() && route.Dst.Mask.String() == net.CIDRMask(0, 32).String() { - link, err := netlink.LinkByIndex(route.LinkIndex) - if err != nil { - continue - } - return link.Attrs().Name, nil - } - } - return "", ErrNetworkDisconnected -} - func (m *linuxNetworkManager) CreateInterface(interfaceName string) error { m.mu.Lock() defer m.mu.Unlock() @@ -66,8 +39,8 @@ func (m *linuxNetworkManager) RemoveInterface(interfaceName string) error { return nil } -func NewNetworkManager(bridgeName, subnet string) (NetworkManager, error) { - if bridgeErr := ifc.CreateBridge(bridgeName, subnet, true); bridgeErr != nil { +func NewNetworkManager(bridgeName, gateway string) (NetworkManager, error) { + if bridgeErr := ifc.CreateBridge(bridgeName, gateway, true); bridgeErr != nil { return nil, bridgeErr } @@ -77,64 +50,5 @@ func NewNetworkManager(bridgeName, subnet string) (NetworkManager, error) { events: make(chan Event, 10), } - currentDefaultInterface := "" - go func() { - for event := range nm.events { - switch ev := event.(type) { - case *DefaultInterfaceChanged: - if firewallErr := firewall.ConfigureFirewall(ev.OldInterface, - ev.NewInterface, - bridgeName); firewallErr == nil { - } else { - slog.Error("could not configure firewall", "new interface", ev.NewInterface, "error", firewallErr) - } - } - } - }() - - updates := make(chan netlink.LinkUpdate) - - subscribeErr := netlink.LinkSubscribe(updates, nm.done) - if subscribeErr != nil { - return nil, subscribeErr - } - - if defaultInterface, defaultInterfaceErr := getDefaultInterface(); defaultInterfaceErr == nil && defaultInterface != "" { - nm.events <- &DefaultInterfaceChanged{ - NewInterface: defaultInterface, - OldInterface: currentDefaultInterface, - } - currentDefaultInterface = defaultInterface - } else { - return nil, defaultInterfaceErr - } - - go func() { - outerloop: - for { - select { - case <-updates: - newDefaultInterface := "" - if defaultInterface, defaultInterfaceErr := getDefaultInterface(); defaultInterfaceErr == nil { - newDefaultInterface = defaultInterface - } - if currentDefaultInterface != newDefaultInterface { - if newDefaultInterface == "" { - slog.Debug("Got disconnected from the internet") - } else { - slog.Debug("New default interface was configured", "interface", newDefaultInterface) - } - nm.events <- &DefaultInterfaceChanged{ - NewInterface: newDefaultInterface, - OldInterface: currentDefaultInterface, - } - currentDefaultInterface = newDefaultInterface - } - case <-nm.done: - break outerloop - } - } - }() - return nm, nil } diff --git a/src/protos/services/v1/networkmanager.proto b/src/protos/services/v1/networkmanager.proto deleted file mode 100644 index ddaef71..0000000 --- a/src/protos/services/v1/networkmanager.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; - -package services.v1; - -import "google/protobuf/empty.proto"; - -option go_package = "github.com/q-controller/qcontroller/src/generated/services/v1;v1"; - -message CreateInterfaceRequest { - string name = 1; -} - -message RemoveInterfaceRequest { - string name = 1; -} - -service NetworkManagerService { - rpc CreateInterface(CreateInterfaceRequest) returns (google.protobuf.Empty) {} - rpc RemoveInterface(RemoveInterfaceRequest) returns (google.protobuf.Empty) {} -} diff --git a/src/protos/settings/v1/settings.proto b/src/protos/settings/v1/settings.proto index 8238ddb..d2b6aeb 100644 --- a/src/protos/settings/v1/settings.proto +++ b/src/protos/settings/v1/settings.proto @@ -14,14 +14,23 @@ message Cache { string root = 1; } -message Bridge { +message Dhcp { + string start = 1; + string end = 2; + repeated string dns = 3; + int64 lease_time = 4; + string lease_file = 5; +} + +message Network { string name = 1; - string subnet = 2; - string gateway = 3; + string gateway_ip = 2; + string bridge_ip = 3; + Dhcp dhcp = 4; } message LinuxSettings { - Bridge bridge = 1; + Network network = 1; } message ControllerConfig { diff --git a/src/qcontrollerd/cmd/config_linux.go b/src/qcontrollerd/cmd/config_linux.go new file mode 100644 index 0000000..bbcf8b7 --- /dev/null +++ b/src/qcontrollerd/cmd/config_linux.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "errors" + "fmt" + "net" + + settingsv1 "github.com/q-controller/qcontroller/src/generated/settings/v1" +) + +type LinuxConfig struct { + Name string + BridgeIp net.IP + GatewayIp net.IP + StartIp net.IP + EndIp net.IP + Subnet *net.IPNet +} + +func NewLinuxConfig(settings *settingsv1.LinuxSettings) (*LinuxConfig, error) { + if settings == nil { + return nil, errors.New("linux settings cannot be nil") + } + + hostIp, hostNet, hostErr := net.ParseCIDR(settings.Network.GatewayIp) + if hostErr != nil { + return nil, fmt.Errorf("failed to parse host_ip %s: %w", settings.Network.GatewayIp, hostErr) + } + + bridgeIp, bridgeNet, bridgeErr := net.ParseCIDR(settings.Network.BridgeIp) + if bridgeErr != nil { + return nil, fmt.Errorf("failed to parse bridge_ip %s: %w", settings.Network.BridgeIp, bridgeErr) + } + + if bridgeNet.String() != hostNet.String() { + return nil, fmt.Errorf("bridge (%s) and host (%s) subnets are not same", bridgeNet.String(), hostNet.String()) + } + + if bridgeIp.Equal(hostIp) { + return nil, fmt.Errorf("bridge_ip (%s) cannot be same as host_ip (%s)", bridgeIp.String(), hostIp.String()) + } + + startIp, startNet, startErr := net.ParseCIDR(settings.Network.Dhcp.Start) + if startErr != nil { + return nil, fmt.Errorf("failed to parse dhcp_start %s: %w", settings.Network.Dhcp.Start, startErr) + } + + endIp, endNet, endErr := net.ParseCIDR(settings.Network.Dhcp.End) + if endErr != nil { + return nil, fmt.Errorf("failed to parse dhcp_end %s: %w", settings.Network.Dhcp.End, endErr) + } + + if startNet.String() != hostNet.String() { + return nil, fmt.Errorf("dhcp_start (%s) and network (%s) subnets are not same", startNet.String(), hostNet.String()) + } + + if endNet.String() != hostNet.String() { + return nil, fmt.Errorf("dhcp_end (%s) and network (%s) subnets are not same", endNet.String(), hostNet.String()) + } + + return &LinuxConfig{ + BridgeIp: bridgeIp, + GatewayIp: hostIp, + Subnet: hostNet, + Name: settings.Network.Name, + StartIp: startIp, + EndIp: endIp, + }, nil +} diff --git a/src/qcontrollerd/cmd/qemu.go b/src/qcontrollerd/cmd/qemu.go deleted file mode 100644 index f4fc620..0000000 --- a/src/qcontrollerd/cmd/qemu.go +++ /dev/null @@ -1,125 +0,0 @@ -package cmd - -import ( - "context" - "errors" - "fmt" - "log/slog" - "net" - "os" - "os/signal" - "syscall" - "time" - - servicesv1 "github.com/q-controller/qcontroller/src/generated/services/v1" - settingsv1 "github.com/q-controller/qcontroller/src/generated/settings/v1" - "github.com/q-controller/qcontroller/src/pkg/protos" - "github.com/q-controller/qcontroller/src/pkg/qemu/process" - "github.com/q-controller/qcontroller/src/pkg/utils" - "github.com/spf13/cobra" - "google.golang.org/grpc" -) - -var qemuCmd = &cobra.Command{ - Use: "qemu", - Short: "Starts QEMU client", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if os.Geteuid() != 0 { - return errors.New("this command must be run as root") - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - configPath, configPathErr := cmd.Flags().GetString("config") - if configPathErr != nil { - return fmt.Errorf("failed to get config: %w", configPathErr) - } - - config := &settingsv1.QemuConfig{} - if unmarshalErr := utils.Unmarshal(config, configPath); unmarshalErr != nil { - return unmarshalErr - } - slog.Debug("Read config", "config", config) - - lis, lisErr := net.Listen("tcp", fmt.Sprintf(":%d", config.Port)) - if lisErr != nil { - return fmt.Errorf("failed to listen: %w", lisErr) - } - defer func() { - if err := lis.Close(); err != nil { - slog.Error("Failed to close the listener", "error", err) - } - }() - - s := grpc.NewServer() - defer func() { - // Create a deadline for graceful shutdown - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) - defer shutdownCancel() - - // Give ongoing operations a chance to complete - done := make(chan struct{}) - go func() { - s.GracefulStop() // Stop accepting new requests, let existing ones finish - close(done) - }() - - // Wait for graceful shutdown or timeout - select { - case <-shutdownCtx.Done(): - slog.Warn("Graceful shutdown timed out, forcing stop") - s.Stop() - case <-done: - slog.Info("Graceful shutdown completed") - } - }() - - monitor, monitorErr := process.NewInstanceMonitor() - if monitorErr != nil { - return monitorErr - } - defer func() { - if err := monitor.Close(); err != nil { - slog.Error("Error closing monitor", "error", err) - } - }() - - reg, regErr := protos.NewQemuService(monitor, config) - if regErr != nil { - return fmt.Errorf("failed to create server %w", regErr) - } - servicesv1.RegisterQemuServiceServer(s, reg) - - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) - - // Server error channel - errCh := make(chan error, 1) - - // Start server in background - go func() { - if err := s.Serve(lis); err != nil { - errCh <- err - } - }() - - // Wait for signal or error - select { - case <-stop: - slog.Info("Shutting down gRPC server due to signal...") - case err := <-errCh: - return err - } - - return nil - }, -} - -func init() { - rootCmd.AddCommand(qemuCmd) - - qemuCmd.Flags().StringP("config", "c", "", "Path to qemu's config file") - if err := qemuCmd.MarkFlagRequired("config"); err != nil { - panic(fmt.Errorf("failed to mark flag `config` as required: %w", err)) - } -} diff --git a/src/qcontrollerd/cmd/qemu_common.go b/src/qcontrollerd/cmd/qemu_common.go new file mode 100644 index 0000000..3c274d3 --- /dev/null +++ b/src/qcontrollerd/cmd/qemu_common.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "context" + "fmt" + "log/slog" + "net" + "time" + + servicesv1 "github.com/q-controller/qcontroller/src/generated/services/v1" + settingsv1 "github.com/q-controller/qcontroller/src/generated/settings/v1" + "github.com/q-controller/qcontroller/src/pkg/protos" + "github.com/q-controller/qcontroller/src/pkg/qemu/process" + "google.golang.org/grpc" +) + +func Entrypoint(config *settingsv1.QemuConfig, stop <-chan struct{}) error { + lis, lisErr := net.Listen("tcp", fmt.Sprintf(":%d", config.Port)) + if lisErr != nil { + return fmt.Errorf("failed to listen: %w", lisErr) + } + defer func() { + if err := lis.Close(); err != nil { + slog.Error("Failed to close the listener", "error", err) + } + }() + + s := grpc.NewServer() + defer func() { + // Create a deadline for graceful shutdown + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer shutdownCancel() + + // Give ongoing operations a chance to complete + done := make(chan struct{}) + go func() { + s.GracefulStop() // Stop accepting new requests, let existing ones finish + close(done) + }() + + // Wait for graceful shutdown or timeout + select { + case <-shutdownCtx.Done(): + slog.Warn("Graceful shutdown timed out, forcing stop") + s.Stop() + case <-done: + slog.Info("Graceful shutdown completed") + } + }() + + monitor, monitorErr := process.NewInstanceMonitor() + if monitorErr != nil { + return monitorErr + } + defer func() { + if err := monitor.Close(); err != nil { + slog.Error("Error closing monitor", "error", err) + } + }() + + reg, regErr := protos.NewQemuService(monitor, config) + if regErr != nil { + return fmt.Errorf("failed to create server %w", regErr) + } + servicesv1.RegisterQemuServiceServer(s, reg) + + // Server error channel + errCh := make(chan error, 1) + + // Start server in background + go func() { + if err := s.Serve(lis); err != nil { + errCh <- err + } + }() + + // Wait for signal or error + select { + case <-stop: + slog.Info("Shutting down gRPC server due to signal...") + case err := <-errCh: + return err + } + + return nil +} diff --git a/src/qcontrollerd/cmd/qemu_darwin.go b/src/qcontrollerd/cmd/qemu_darwin.go new file mode 100644 index 0000000..f1ec6f8 --- /dev/null +++ b/src/qcontrollerd/cmd/qemu_darwin.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "errors" + "fmt" + "log/slog" + "os" + "os/signal" + "syscall" + + settingsv1 "github.com/q-controller/qcontroller/src/generated/settings/v1" + "github.com/q-controller/qcontroller/src/pkg/utils" + + "github.com/spf13/cobra" +) + +var qemuCmd = &cobra.Command{ + Use: "qemu", + Short: "Starts QEMU client", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if os.Geteuid() != 0 { + return errors.New("this command must be run as root") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + configPath, configPathErr := cmd.Flags().GetString("config") + if configPathErr != nil { + return fmt.Errorf("failed to get config: %w", configPathErr) + } + + config := &settingsv1.QemuConfig{} + if unmarshalErr := utils.Unmarshal(config, configPath); unmarshalErr != nil { + return unmarshalErr + } + slog.Debug("Read config", "config", config) + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + + done := make(chan struct{}) + go func() { + <-stop + close(done) + }() + + return Entrypoint(config, done) + }, +} + +func init() { + rootCmd.AddCommand(qemuCmd) + + qemuCmd.Flags().StringP("config", "c", "", "Path to qemu's config file") + if err := qemuCmd.MarkFlagRequired("config"); err != nil { + panic(fmt.Errorf("failed to mark flag `config` as required: %w", err)) + } + + qemuCmd.Flags().Bool("in-namespace", false, "Run QEMU inside network namespace") + if err := qemuCmd.Flags().MarkHidden("in-namespace"); err != nil { + panic(fmt.Errorf("failed to mark flag `in-namespace` as hidden: %w", err)) + } +} diff --git a/src/qcontrollerd/cmd/qemu_linux.go b/src/qcontrollerd/cmd/qemu_linux.go new file mode 100644 index 0000000..ccc7243 --- /dev/null +++ b/src/qcontrollerd/cmd/qemu_linux.go @@ -0,0 +1,149 @@ +package cmd + +import ( + "errors" + "fmt" + "log/slog" + "net" + "os" + "os/exec" + "os/signal" + "syscall" + + "github.com/q-controller/network-utils/src/utils/network/dhcp" + "github.com/q-controller/network-utils/src/utils/network/ifc" + "github.com/q-controller/network-utils/src/utils/network/network" + settingsv1 "github.com/q-controller/qcontroller/src/generated/settings/v1" + "github.com/q-controller/qcontroller/src/pkg/utils" + + "github.com/spf13/cobra" +) + +var qemuCmd = &cobra.Command{ + Use: "qemu", + Short: "Starts QEMU client", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if os.Geteuid() != 0 { + return errors.New("this command must be run as root") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + configPath, configPathErr := cmd.Flags().GetString("config") + if configPathErr != nil { + return fmt.Errorf("failed to get config: %w", configPathErr) + } + + config := &settingsv1.QemuConfig{} + if unmarshalErr := utils.Unmarshal(config, configPath); unmarshalErr != nil { + return unmarshalErr + } + slog.Debug("Read config", "config", config) + + linuxConfig, linuxConfigErr := NewLinuxConfig(config.GetLinuxSettings()) + if linuxConfigErr != nil { + return fmt.Errorf("failed to create linux config: %w", linuxConfigErr) + } + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + + done := make(chan struct{}) + go func() { + <-stop + close(done) + }() + + inNamespace, inNamespaceErr := cmd.Flags().GetBool("in-namespace") + if inNamespaceErr != nil { + return fmt.Errorf("failed to get in-namespace flag: %w", inNamespaceErr) + } + + if inNamespace { + dns := []net.IP{} + for _, ipStr := range config.GetLinuxSettings().Network.Dhcp.Dns { + ip := net.ParseIP(ipStr) + if ip == nil { + return fmt.Errorf("failed to parse dns ip %s", ipStr) + } + dns = append(dns, ip) + } + + dhcpServer, dhcpServerErr := dhcp.StartDHCPServer( + dhcp.WithDNS(dns...), + dhcp.WithInterface(linuxConfig.Name, linuxConfig.BridgeIp), + dhcp.WithLeaseFile(config.GetLinuxSettings().Network.Dhcp.LeaseFile), + dhcp.WithRange(linuxConfig.StartIp, linuxConfig.EndIp), + ) + if dhcpServerErr != nil { + return fmt.Errorf("failed to start DHCP server: %w", dhcpServerErr) + } + defer dhcpServer.Stop() + + return Entrypoint(config, done) + } else { + netw, netwErr := network.NewNetwork( + network.WithName(linuxConfig.Name), + network.WithGateway(linuxConfig.GatewayIp), + network.WithBridge(linuxConfig.BridgeIp), + network.WithSubnet(linuxConfig.Subnet), + network.WithLinkManager(ifc.NetlinkBridgeManager{}), + ) + if netwErr != nil { + return fmt.Errorf("failed to create network: %w", netwErr) + } + + subscription, subscribeErr := ifc.SubscribeDefaultInterfaceChanges() + if subscribeErr != nil { + return fmt.Errorf("failed to subscribe to default interface changes: %w", subscribeErr) + } + defer subscription.Stop() + + go func() { + for iface := range subscription.InterfaceCh { + slog.Info("Default interface changed", "interface", iface) + if err := netw.Connect(iface); err != nil { + slog.Error("Failed to connect network to interface", "interface", iface, "error", err) + } + } + }() + + args := append([]string(nil), os.Args[1:]...) // clone without program name + args = append(args, "--in-namespace", linuxConfig.Name) + + cmd := exec.Command(os.Args[0], args...) + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + if err := netw.Execute(func() error { + if err := cmd.Start(); err != nil { + return fmt.Errorf("failed to start qemu command: %w", err) + } + return nil + }); err != nil { + return fmt.Errorf("failed to execute network command: %w", err) + } + + <-done + + slog.Info("Stopping QEMU process") + if err := cmd.Process.Signal(os.Interrupt); err != nil { + slog.Error("Failed to send interrupt to QEMU process", "error", err) + } + } + + return nil + }, +} + +func init() { + rootCmd.AddCommand(qemuCmd) + + qemuCmd.Flags().StringP("config", "c", "", "Path to qemu's config file") + if err := qemuCmd.MarkFlagRequired("config"); err != nil { + panic(fmt.Errorf("failed to mark flag `config` as required: %w", err)) + } + + qemuCmd.Flags().Bool("in-namespace", false, "Run QEMU inside network namespace") + if err := qemuCmd.Flags().MarkHidden("in-namespace"); err != nil { + panic(fmt.Errorf("failed to mark flag `in-namespace` as hidden: %w", err)) + } +} diff --git a/start.sh b/start.sh index a510d7e..2ffe69d 100755 --- a/start.sh +++ b/start.sh @@ -5,7 +5,10 @@ set -Eeuo pipefail trap cleanup SIGINT SIGTERM ERR EXIT INTERFACE_NAME=br0 -SUBNET=192.168.71.0/24 +HOST_IP=192.168.71.1/24 +BRIDGE_IP=192.168.71.3/24 +START=192.168.71.4/24 +END=192.168.71.254/24 CONTROLLER_PORT=8009 QEMU_PORT=8008 GATEWAY_PORT=8080 @@ -29,7 +32,9 @@ Available options: --bin Path to qcontrollerd binary --rundir Path to the rundir --interface Interface name [default: ${INTERFACE_NAME}] (Linux only) ---cidr Subnet [default: ${SUBNET}] (Linux only) +--cidr Gateway CIDR [default: ${GATEWAY}] (Linux only) +--start Start of DHCP range (Linux only) [default: ${START}] +--end End of DHCP range (Linux only) [default: ${END}] EOF exit } @@ -62,7 +67,19 @@ parse_params() { ;; --cidr) if [[ "$OS_TYPE" == "Linux" ]]; then - SUBNET="${2-}" + GATEWAY="${2-}" + fi + shift + ;; + --start) + if [[ "$OS_TYPE" == "Linux" ]]; then + START="${2-}" + fi + shift + ;; + --end) + if [[ "$OS_TYPE" == "Linux" ]]; then + END="${2-}" fi shift ;; @@ -103,9 +120,17 @@ cat >${CONFIGDIR}/qemu-config.json <${CONFIGDIR}/qemu-config.json <${CONFIGDIR}/controller-config.json <${CONFIGDIR}/controller-config.json <