Compare commits

...

111 Commits

Author SHA1 Message Date
07cceed0c8 chore: release (#202)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-07-12 23:28:49 +00:00
4ef466ceb6 chore(workflow): implement oci releases (#248) 2024-07-12 21:38:17 +00:00
8c9b3a6ceb fix(dependabot): separate production and development dependency updates (#247) 2024-07-12 20:36:19 +00:00
a970cddacf fix(dependabot): enable docker version update checks (#244) 2024-07-12 20:00:00 +00:00
a878d16c3c build(deps): bump thiserror from 1.0.61 to 1.0.62 (#246)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.61 to 1.0.62.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-12 06:56:50 +00:00
1126f1ffc9 fix(install): use /usr/sbin as install path and fix systemd dependency (#245) 2024-07-12 06:49:02 +00:00
d1b2cb3683 build(deps): bump serde from 1.0.203 to 1.0.204 (#234)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.203 to 1.0.204.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.203...v1.0.204)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 22:53:48 +00:00
8e1e197113 build(deps): bump uuid from 1.9.1 to 1.10.0 (#239)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.9.1...1.10.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 22:53:33 +00:00
ffb7de7d68 build(deps): bump sysinfo from 0.30.12 to 0.30.13 (#238)
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.30.12 to 0.30.13.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/v0.30.13/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits/v0.30.13)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 22:53:09 +00:00
bd464d9f03 build(deps): bump clap from 4.5.8 to 4.5.9 (#237)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.8 to 4.5.9.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.8...v4.5.9)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 22:53:06 +00:00
31d04c2f43 build(deps): bump async-trait from 0.1.80 to 0.1.81 (#235)
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.80 to 0.1.81.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.80...0.1.81)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 22:53:02 +00:00
04401c1d07 fix(runtime): use iommu only if devices are needed (#243) 2024-07-10 04:02:53 +00:00
b2dd4af09b chore(powermgmt): disable for now as a hackfix (#242)
Signed-off-by: Ariadne Conill <ariadne@dereferenced.org>
2024-07-10 03:47:02 +00:00
783dd51f05 chore(systemd): align systemd unit definitions with OCI asset paths (#241)
Signed-off-by: Ariadne Conill <ariadne@dereferenced.org>
2024-07-10 00:37:12 +00:00
2f866ad47b feature(oci-distribution): distribute guestinit via OCI (#240)
Signed-off-by: Ariadne Conill <ariadne@dereferenced.org>
2024-07-10 00:34:05 +00:00
94e45c1c8c build(deps): bump actions/upload-artifact from 4.3.3 to 4.3.4 (#236)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65462800fd...0b2256b8c0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 12:56:34 +00:00
3a398810b6 Minor readability fixes to the lovely FAQ (#229)
* Minor readability fixes to the lovely FAQ

Signed-off-by: Jed Salazar <jed@edera.dev>

* Remove comma

Signed-off-by: Jed Salazar <jed@edera.dev>

---------

Signed-off-by: Jed Salazar <jed@edera.dev>
2024-07-07 18:54:31 +00:00
5da214fa48 build(deps): bump serde_json from 1.0.119 to 1.0.120 (#226)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.119 to 1.0.120.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.119...v1.0.120)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-05 20:21:14 +00:00
8840bf34a4 build(deps): bump actions/create-github-app-token from 1.10.2 to 1.10.3 (#227)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](ad38cffc07...31c86eb3b3)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-05 20:21:11 +00:00
f953c87b90 build(deps): bump oci-spec from 0.6.5 to 0.6.7 (#232)
Bumps [oci-spec](https://github.com/containers/oci-spec-rs) from 0.6.5 to 0.6.7.
- [Release notes](https://github.com/containers/oci-spec-rs/releases)
- [Changelog](https://github.com/containers/oci-spec-rs/blob/main/release.md)
- [Commits](https://github.com/containers/oci-spec-rs/compare/v0.6.5...v0.6.7)

---
updated-dependencies:
- dependency-name: oci-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-05 16:03:04 +00:00
ff571630b9 build(deps): bump docker/build-push-action from 6.2.0 to 6.3.0 (#231)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](15560696de...1a162644f9)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-05 16:03:00 +00:00
e45a9d82d2 build(deps): bump docker/setup-buildx-action from 3.3.0 to 3.4.0 (#233)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](d70bba72b1...4fd812986e)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-05 16:02:40 +00:00
98ca623828 fix(oci-distribution): use scratch images for OCI distributed-artefacts (#230)
Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-07-05 16:02:26 +00:00
deeaa20a4a fix(workflow): format check should print output but not error (#225) 2024-07-01 20:11:25 +00:00
fe8e1d5521 feature(oci): add configuration value for oci seed file (#220) 2024-07-01 19:36:21 +00:00
367d31b11f fix(workflow): remove reference to unused platform matrix key (#223) 2024-07-01 09:10:09 +00:00
71301ee689 fix(daemon): decrease rate of runtime reconcile (#224) 2024-07-01 09:09:50 +00:00
350e02c553 build(deps): bump clap from 4.5.7 to 4.5.8 (#222)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.7 to 4.5.8.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.7...v4.5.8)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 05:49:32 +00:00
f0914fb39f build(deps): bump serde_json from 1.0.118 to 1.0.119 (#221)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.118 to 1.0.119.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.118...v1.0.119)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 05:49:25 +00:00
0e64d4ea79 feature(power-management-defaults): set an initial power management policy (#219)
The default policy enables performance mode and SMT.

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-07-01 03:37:17 +00:00
35d585e3b1 fix(power): ensure that xeon cpus with cpu gaps are not detected as p/e compatible (#218) 2024-06-30 05:25:15 +00:00
a79320b4fc Power management core functionality (#217)
* feat(power-management-core): add core power management control messages for kratad

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): expose xen hypercall client publicly

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): add indexmap to kratart crate dependencies

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): implement power management core in kratart

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): bubble up runtime context in daemon/control service

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): expose performance/efficiency core data in protobuf

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): fix up some protobuf message names

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): fix up performance core heuristic

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): implement GetHostCpuTopology RPC

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): hackfix to get sysctls working with tokio

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): borrow the PowerManagementContext when calling functions belonging to it

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): remove GetHostPowerManagementPolicy RPC for now

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): implement SetHostPowerManagementPolicy RPC

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): add cpu-topology command

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(power-management-core): appease format checking

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* fix(runtime): cpu topology corrections

---------

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
Co-authored-by: Alex Zenla <alex@edera.dev>
2024-06-29 15:43:08 -07:00
39ded9c7f4 Ensure list item ends with period for consistency (#216)
Signed-off-by: Andrés Vega <av@messier42.com>
2024-06-29 00:17:41 +00:00
b42b730b77 feature(xen): implement power management operations (#215) 2024-06-28 22:13:57 +00:00
0f49d0cec4 build(deps): bump log from 0.4.21 to 0.4.22 (#214)
Bumps [log](https://github.com/rust-lang/log) from 0.4.21 to 0.4.22.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.21...0.4.22)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-28 19:35:03 +00:00
dc4b14b5d1 chore: temporarily disable format checks (#207)
As per https://github.com/edera-dev/krata/issues/206, we are disabling
format checks until we have migrated to the new formatting rules, which
are commited in a later change.
2024-06-28 17:01:03 +00:00
f5b4c66ec7 build(deps): bump docker/build-push-action from 6.1.0 to 6.2.0 (#211)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](31159d49c0...15560696de)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-27 12:34:18 +00:00
9062d78e51 build(deps): bump actions/checkout from 4.1.6 to 4.1.7 (#212)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.6...692973e3d937129bcbf40652eb9f2f61becf3332)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-27 12:33:13 +00:00
6161bea7bf build(deps): bump step-security/harden-runner from 2.8.0 to 2.8.1 (#213)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.8.0 to 2.8.1.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](https://github.com/step-security/harden-runner/compare/v2.8.0...17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-27 12:32:31 +00:00
8363ed0085 OCI distribution (#210)
* feat(images): add dockerfiles for the OCI distributions of krata components

Signed-off-by: Ariadne Conill <ariadne@dereferenced.org>

* feat(images): add oci distribution workflow

Signed-off-by: Ariadne Conill <ariadne@dereferenced.org>

---------

Signed-off-by: Ariadne Conill <ariadne@dereferenced.org>
2024-06-26 21:31:30 +00:00
8ddc190018 build(deps): bump actions/create-github-app-token from 1.10.1 to 1.10.2 (#208)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](c8f55efbd4...ad38cffc07)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-26 17:42:29 +00:00
c687561541 build(deps): bump serde_json from 1.0.117 to 1.0.118 (#204)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.117 to 1.0.118.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.117...v1.0.118)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-26 17:42:13 +00:00
4c83902729 build(deps): bump uuid from 1.9.0 to 1.9.1 (#203)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.9.0...1.9.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-26 17:41:22 +00:00
6f50167798 Use native loopdev implementation instead of loopdev-3 (#209)
* feature(loopdev): add native loop device implementation

The previous loop device implementation required bindgen for no reason,
making cross-compilation difficult.

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* feat(runtime): use native krata-loopdev instead of loopdev-3

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* chore: update cargo workspace lock file

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

* chore: appease formatting linter

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>

---------

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-06-26 10:29:58 -07:00
88a62441b1 Initial fluentd support (#205)
* fix(hack): use sudo -E when running Rust binaries

This makes it possible to pass envvars to the Krata runtime

* feat(o11y): add fluent sink to logs

This change adds fluent logging as an opt-in feature. Setting
`KRATA_LOG_FLUENT` with an address:port will start a TCP connection,
sending logs.

A later changes will respect a URI scheme and use structured logging.
2024-06-25 19:10:57 +00:00
93aae83b3f build(deps): bump nix from 0.28.0 to 0.29.0 (#198)
Bumps [nix](https://github.com/nix-rust/nix) from 0.28.0 to 0.29.0.
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 05:30:30 +00:00
6e1e4e3806 build(deps): bump reqwest from 0.12.4 to 0.12.5 (#199)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.4 to 0.12.5.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.4...v0.12.5)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 05:30:22 +00:00
9e532345f0 build(deps): bump uuid from 1.8.0 to 1.9.0 (#200)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.8.0...1.9.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 05:30:14 +00:00
89b7f40520 build(deps): bump memchr from 2.7.2 to 2.7.4 (#201)
Bumps [memchr](https://github.com/BurntSushi/memchr) from 2.7.2 to 2.7.4.
- [Commits](https://github.com/BurntSushi/memchr/compare/2.7.2...2.7.4)

---
updated-dependencies:
- dependency-name: memchr
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 05:30:06 +00:00
4175e1e3fe chore: release (#181)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-06-24 05:01:32 +00:00
1bdf3bda87 build(deps): bump regex from 1.10.4 to 1.10.5 (#187)
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.4 to 1.10.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.4...1.10.5)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:58:15 +00:00
9a45d754bf chore(xenplatform): elf loader should async load the file (#197)
* fix(build): remove unused environment variables

* chore(xenplatform): elf loader should async load the file
2024-06-23 12:57:01 +00:00
6c3fc54688 build(deps): bump url from 2.5.0 to 2.5.2 (#193)
Bumps [url](https://github.com/servo/rust-url) from 2.5.0 to 2.5.2.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.0...v2.5.2)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:15:20 +00:00
af6a1a3ad2 build(deps): bump MarcoIeni/release-plz-action from 0.5.61 to 0.5.62 (#192)
Bumps [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action) from 0.5.61 to 0.5.62.
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](7566221bba...86afd21a7b)

---
updated-dependencies:
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:15:17 +00:00
7bef74fadf build(deps): bump actions/checkout from 4.1.6 to 4.1.7 (#190)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](a5ac7e51b4...692973e3d9)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:15:15 +00:00
ec1b6d4370 build(deps): bump clap from 4.5.4 to 4.5.7 (#188)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.4 to 4.5.7.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.4...v4.5.7)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:15:10 +00:00
b2d146713b build(deps): bump redb from 2.1.0 to 2.1.1 (#186)
Bumps [redb](https://github.com/cberner/redb) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/cberner/redb/releases)
- [Changelog](https://github.com/cberner/redb/blob/master/CHANGELOG.md)
- [Commits](https://github.com/cberner/redb/compare/v2.1.0...v2.1.1)

---
updated-dependencies:
- dependency-name: redb
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:15:04 +00:00
b730b08d6e build(deps): bump step-security/harden-runner from 2.8.0 to 2.8.1 (#185)
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.8.0 to 2.8.1.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](f086349bfa...17d0e2bd7d)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:15:02 +00:00
87758d7ae9 build(deps): bump tokio-tun from 0.11.4 to 0.11.5 (#183)
Bumps [tokio-tun](https://github.com/yaa110/tokio-tun) from 0.11.4 to 0.11.5.
- [Commits](https://github.com/yaa110/tokio-tun/compare/0.11.4...0.11.5)

---
updated-dependencies:
- dependency-name: tokio-tun
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-23 12:14:59 +00:00
349664abf1 chore(git): delete old gitmodules 2024-06-23 01:50:35 -07:00
ef068e790c chore(xen): move device creation into transaction interface (#196)
* chore(xen): move domain creation to xenplatform

* chore(xen): move device transactions into separate interface
2024-06-21 17:38:19 +00:00
6f39f115b7 chore(xen): split platform support into separate crate (#195) 2024-06-21 08:10:45 +00:00
23c7302c04 docs: first pass of krata as an isolation engine 2024-06-20 19:57:18 -07:00
e219f3adf1 feature(xen): dynamic platform architecture (#194)
* wip hvm

* feat: move platform stuff all into it's own thing

* hvm work

* more hvm work

* more hvm work

* feat: rework to support multiple platforms

* hvm nonredist

* more hvm work

* more hvm work

* pvh work

* work on loading cmdline

* implement initrd loading for pvh

* partially working pvh support

* fix merge issues

* pvh works!

* swap over to pv support

* remove old kernel stuff

* fix support for pv

* pvh is gone for now

* fix(runtime): debug should be respected

* fix(xen): arm64 is currently unsupported, treat it as such at runtime

* fix(examples): use architecture cfg for boot example

* fix(x86): use IOMMU only when needed for passthrough

* chore(build): print kernel architecture during fetch
2024-06-21 02:42:45 +00:00
2c7210d85e build(deps): bump prost-build from 0.12.4 to 0.12.6 (#170)
Bumps [prost-build](https://github.com/tokio-rs/prost) from 0.12.4 to 0.12.6.
- [Release notes](https://github.com/tokio-rs/prost/releases)
- [Commits](https://github.com/tokio-rs/prost/compare/v0.12.4...v0.12.6)

---
updated-dependencies:
- dependency-name: prost-build
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:24:00 +00:00
ade37e92f3 build(deps): bump serde from 1.0.202 to 1.0.203 (#172)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.202 to 1.0.203.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.202...v1.0.203)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:23:52 +00:00
ef3bc83069 build(deps): bump async-compression from 0.4.10 to 0.4.11 (#175)
Bumps [async-compression](https://github.com/Nullus157/async-compression) from 0.4.10 to 0.4.11.
- [Release notes](https://github.com/Nullus157/async-compression/releases)
- [Changelog](https://github.com/Nullus157/async-compression/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Nullus157/async-compression/compare/v0.4.10...v0.4.11)

---
updated-dependencies:
- dependency-name: async-compression
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:23:43 +00:00
14084f13d8 build(deps): bump tokio from 1.37.0 to 1.38.0 (#176)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.37.0...tokio-1.38.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:23:35 +00:00
fbc953cf46 build(deps): bump actions/create-github-app-token from 1.10.0 to 1.10.1 (#177)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](a0de6af839...c8f55efbd4)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:23:26 +00:00
fd7974fc98 build(deps): bump MarcoIeni/release-plz-action from 0.5.58 to 0.5.61 (#178)
Bumps [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action) from 0.5.58 to 0.5.61.
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](7fe60ae5d7...7566221bba)

---
updated-dependencies:
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:23:18 +00:00
d17769d69f build(deps): bump toml from 0.8.13 to 0.8.14 (#179)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.13 to 0.8.14.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.13...toml-v0.8.14)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 19:23:11 +00:00
7ba04f26a3 fix(os): alpine v3.20 requires copying kernel config before grub-mkconfig (#180)
There is currently a bug in the Xen support for Alpine where /boot/config-lts
is expected to exist but in Alpine /boot/config-VERSION-lts is the only file
available. This change copies the config to /boot/config-lts to fix the build.
2024-06-04 17:00:49 +00:00
11235b6837 --- (#168)
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 06:29:06 +00:00
e8849048db --- (#167)
updated-dependencies:
- dependency-name: prost-types
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 06:29:03 +00:00
cd15337ad8 build(deps): bump libc from 0.2.154 to 0.2.155 (#163)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.154 to 0.2.155.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.154...0.2.155)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 04:44:36 +00:00
037261991a build(deps): bump anyhow from 1.0.83 to 1.0.86 (#164)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.83 to 1.0.86.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.83...1.0.86)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 04:44:33 +00:00
67fb5891e4 build(deps): bump prost from 0.12.4 to 0.12.6 (#165)
Bumps [prost](https://github.com/tokio-rs/prost) from 0.12.4 to 0.12.6.
- [Release notes](https://github.com/tokio-rs/prost/releases)
- [Commits](https://github.com/tokio-rs/prost/compare/v0.12.4...v0.12.6)

---
updated-dependencies:
- dependency-name: prost
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 04:44:30 +00:00
d1f6d1e742 --- (#166)
updated-dependencies:
- dependency-name: ratatui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 04:44:27 +00:00
18fc2c3a7e build(deps): bump thiserror from 1.0.60 to 1.0.61 (#162)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.60 to 1.0.61.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.60...1.0.61)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-22 04:44:24 +00:00
54486b119b build(deps): bump actions/checkout from 4.1.5 to 4.1.6 (#161)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](44c2b7a8a4...a5ac7e51b4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-17 17:48:51 +00:00
04a633d501 build(deps): bump MarcoIeni/release-plz-action from 0.5.57 to 0.5.58 (#152)
Bumps [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action) from 0.5.57 to 0.5.58.
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](a290444218...7fe60ae5d7)

---
updated-dependencies:
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-16 19:11:46 +00:00
612203f014 build(deps): bump serde from 1.0.201 to 1.0.202 (#154)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.201 to 1.0.202.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.201...v1.0.202)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-16 19:09:33 +00:00
e9ba336f68 build(deps): bump toml from 0.8.12 to 0.8.13 (#155)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.12 to 0.8.13.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.12...toml-v0.8.13)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-16 19:09:14 +00:00
94790ce7dc fix(build): kernel fetch should use host target (#159) 2024-05-16 18:13:04 +00:00
023063327f fix(build): use host resolv.conf in os build chroot (#153)
The resolv.conf that the stage1 os script generates is fine for actual use,
but our GitHub workflows now uses the Step Security hardened runner action.
This action replaces the nameserver so that all lookups go through that,
but because the chroot calls apk add, it needs to contact the internet.
On the GitHub workflows, the OS build currently fails since the hardened
runner cannot access other nameservers.
2024-05-16 08:41:42 +00:00
d46aa878af feat(build): fetch kernels from image registry instead of building the kernel (#156)
Now that we have the kernel build infrastructure at https://github.com/edera-dev/kernels
it makes sense to drop building the kernel and download the kernel images directly.

This change introduces a ./hack/kernel/fetch.sh script which is backed by crates/build
We utilize the OCI infrastructure itself to download the kernel image. The DEV guide
has been updated to include calling the fetch script, and the OS builder now uses this
method instead. Due to the lack of need for the kernel build infra to exist here now,
it has also been removed. This should significantly speed up full builds.

This change will also enable us to turn on os build workflows for all PRs. We should
likely make the OS status checks required once this is merged.
2024-05-16 08:40:58 +00:00
2462a99fdc chore(dependabot): group some dependency updates (#157)
We have a need to ensure great security while also ensuring that dependabot
does not constantly provide multiple PRs. After all, when something becomes
too time consuming it risks not being handled with care. With grouped updates,
version bumps will get grouped together, but security updates will still be
indvidualized. This makes it safer for us to enable grouped dependency updates.
2024-05-16 08:39:50 +00:00
fc18bc6a18 feat(runtime): concurrent ip allocation (#151)
Previously, krata runtime allowed a single permit when performing operations.
This was necessary because the only IP allocation storage was xenstore, and
the commit of xenstore data happens after allocation. This commit introduces
IpVendor, a service which vends IPv4 and IPv6 addresses to guests using a
linear address strategy within an IP network space. The IpVendor table is
initialized from xenstore, and from there on out, the in-memory table
is the source of truth. This implementation is not perfect, but it will allow
us to lift the single permit limit, allowing guests to start concurrently.
2024-05-14 18:29:12 +00:00
b0f0934fa4 build(deps): bump async-compression from 0.4.9 to 0.4.10 (#145)
Bumps [async-compression](https://github.com/Nullus157/async-compression) from 0.4.9 to 0.4.10.
- [Release notes](https://github.com/Nullus157/async-compression/releases)
- [Changelog](https://github.com/Nullus157/async-compression/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Nullus157/async-compression/commits/v0.4.10)

---
updated-dependencies:
- dependency-name: async-compression
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 19:23:40 +00:00
f6721d5e2c build(deps): bump actions/checkout from 4.1.4 to 4.1.5 (#149)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](0ad4b8fada...44c2b7a8a4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 19:23:24 +00:00
0d43a8be54 build(deps): bump MarcoIeni/release-plz-action from 0.5.55 to 0.5.57 (#150)
Bumps [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action) from 0.5.55 to 0.5.57.
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](76e66a600f...a290444218)

---
updated-dependencies:
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 19:23:08 +00:00
0193921053 Pin actions to digests and introduce Step Security Harden Runners (#137)
Signed-off-by: Jed Salazar <jedsalazar@gmail.com>
2024-05-11 00:00:56 +00:00
485f6e8319 chore(kernel): upgrade to kernel 6.8.9 (#143) 2024-05-10 17:30:06 +00:00
09ee251c9e Fix typo and nit (#144)
Signed-off-by: Jed Salazar <jed@edera.dev>
2024-05-10 01:44:42 +00:00
75011ef8cb fix(oci): use mirror.gcr.io as a mirror to docker hub (#141) 2024-05-09 17:30:27 +00:00
69c7af5220 build(deps): bump thiserror from 1.0.59 to 1.0.60 (#135)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.59 to 1.0.60.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.59...1.0.60)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 19:14:17 +00:00
a364abe887 build(deps): bump anyhow from 1.0.82 to 1.0.83 (#136)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.82 to 1.0.83.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.82...1.0.83)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 19:14:00 +00:00
95accc6d3f build(deps): bump serde from 1.0.200 to 1.0.201 (#139)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.200 to 1.0.201.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.200...v1.0.201)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 19:13:44 +00:00
04fb6cce8e build(deps): bump serde_json from 1.0.116 to 1.0.117 (#140)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.116 to 1.0.117.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.116...v1.0.117)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 19:13:27 +00:00
5420214bdd build(deps): bump sysinfo from 0.30.11 to 0.30.12 (#131)
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.30.11 to 0.30.12.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 19:13:11 +00:00
b4f26787d4 fix(oci): remove file size limit (#142)
the addons.squashfs file often is fairly large due to the GPU modules containing a lot of code
2024-05-08 19:09:33 +00:00
51dff0361d fix(xenclient): use a single transaction for device setup (#130) 2024-05-05 20:39:53 +00:00
3187830ff5 build(deps): bump serde from 1.0.199 to 1.0.200 (#129)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.199 to 1.0.200.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.199...v1.0.200)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 17:19:20 +00:00
338322619c build(deps): bump base64 from 0.22.0 to 0.22.1 (#128)
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.22.0 to 0.22.1.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.0...v0.22.1)

---
updated-dependencies:
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 16:27:53 +00:00
511f83bfd9 fix: dev guide should mention copying kernel addons (fixes #125) (#126) 2024-04-30 21:46:45 +00:00
b0f5c38fb0 build(deps): bump async-compression from 0.4.8 to 0.4.9 (#120)
Bumps [async-compression](https://github.com/Nullus157/async-compression) from 0.4.8 to 0.4.9.
- [Release notes](https://github.com/Nullus157/async-compression/releases)
- [Changelog](https://github.com/Nullus157/async-compression/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Nullus157/async-compression/commits)

---
updated-dependencies:
- dependency-name: async-compression
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 16:31:41 +00:00
520018a86d build(deps): bump serde from 1.0.198 to 1.0.199 (#122)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.198 to 1.0.199.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.198...v1.0.199)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 16:31:20 +00:00
39c2e58fbc build(deps): bump flate2 from 1.0.28 to 1.0.30 (#123)
Bumps [flate2](https://github.com/rust-lang/flate2-rs) from 1.0.28 to 1.0.30.
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.28...1.0.30)

---
updated-dependencies:
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 16:31:00 +00:00
d1d6eb5c8b build(deps): bump libc from 0.2.153 to 0.2.154 (#124)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.153 to 0.2.154.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.153...0.2.154)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 16:30:44 +00:00
84920a88ab feat: pci passthrough (#114)
* feat: pci passthrough

* feat: guest device management

* feat: addons mounting and kernel modules support

* feat: more pci work

* fix: kernel build squashfs fixes

* fix: e820entry should be available on all platforms
2024-04-29 17:02:20 +00:00
bece7f33c7 feat: CONTRIBUTING.md and Bug Report template (#117)
This change introduces an initial CONTRIBUTING.md doc and a template for
bug reports.

Signed-off-by: Khionu Sybiern <khionu@edera.dev>
2024-04-24 21:01:52 +00:00
109 changed files with 5817 additions and 13885 deletions

34
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Bug Report
description: File a bug report.
title: "bug: "
labels: ["bug"]
assignees:
- edera-dev/engineering
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: input
id: version
attributes:
label: Version / Commit
description: What version of our software are you running?
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

View File

@ -4,7 +4,32 @@ updates:
directory: "/"
schedule:
interval: "daily"
groups:
production-version-updates:
dependency-type: "production"
applies-to: "version-updates"
development-version-updates:
dependency-type: "development"
applies-to: "version-updates"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
groups:
production-version-updates:
dependency-type: "production"
applies-to: "version-updates"
development-version-updates:
dependency-type: "development"
applies-to: "version-updates"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
groups:
production-version-updates:
dependency-type: "production"
applies-to: "version-updates"
development-version-updates:
dependency-type: "development"
applies-to: "version-updates"

View File

@ -11,19 +11,26 @@ jobs:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
components: rustfmt
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/build/cargo.sh fmt --all -- --check
# Temporarily ignored: https://github.com/edera-dev/krata/issues/206
- run: ./hack/build/cargo.sh fmt --all -- --check || true
shellcheck:
name: shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- run: ./hack/code/shellcheck.sh

View File

@ -27,18 +27,21 @@ jobs:
run:
shell: bash
steps:
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- run: git config --global core.autocrlf false && git config --global core.eol lf
if: ${{ matrix.platform.os == 'windows' }}
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
if: ${{ matrix.platform.os != 'darwin' }}
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
targets: "${{ matrix.platform.arch }}-apple-darwin"
if: ${{ matrix.platform.os == 'darwin' }}
- uses: homebrew/actions/setup-homebrew@master
- uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master
if: ${{ matrix.platform.os == 'darwin' }}
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
- run: ./hack/build/cargo.sh build --bin kratactl

View File

@ -1,32 +0,0 @@
name: kernel
on:
pull_request:
branches:
- main
paths:
- "kernel/**"
- "hack/ci/**"
merge_group:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: kernel build ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/kernel/build.sh
env:
KRATA_KERNEL_BUILD_JOBS: "5"

View File

@ -3,6 +3,10 @@ on:
workflow_dispatch:
schedule:
- cron: "0 10 * * *"
permissions:
contents: read
packages: write
id-token: write
jobs:
server:
runs-on: ubuntu-latest
@ -16,25 +20,24 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: nightly server ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/dist/bundle.sh
env:
KRATA_KERNEL_BUILD_JOBS: "5"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: krata-bundle-systemd-${{ matrix.arch }}
path: "target/dist/bundle-systemd-${{ matrix.arch }}.tgz"
compression-level: 0
- run: ./hack/dist/deb.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: krata-debian-${{ matrix.arch }}
path: "target/dist/*.deb"
@ -42,15 +45,13 @@ jobs:
- run: ./hack/dist/apk.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: krata-alpine-${{ matrix.arch }}
path: "target/dist/*_${{ matrix.arch }}.apk"
compression-level: 0
- run: ./hack/os/build.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: krata-os-${{ matrix.arch }}
path: "target/os/krata-${{ matrix.arch }}.qcow2"
@ -75,28 +76,68 @@ jobs:
run:
shell: bash
steps:
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- run: git config --global core.autocrlf false && git config --global core.eol lf
if: ${{ matrix.platform.os == 'windows' }}
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
if: ${{ matrix.platform.os != 'darwin' }}
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
targets: "${{ matrix.platform.arch }}-apple-darwin"
if: ${{ matrix.platform.os == 'darwin' }}
- uses: homebrew/actions/setup-homebrew@master
- uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master
if: ${{ matrix.platform.os == 'darwin' }}
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
- run: ./hack/build/cargo.sh build --release --bin kratactl
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }}
path: "target/*/release/kratactl"
if: ${{ matrix.platform.os != 'windows' }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }}
path: "target/*/release/kratactl.exe"
if: ${{ matrix.platform.os == 'windows' }}
oci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
component:
- kratactl
- kratad
- kratanet
- krata-guest-init
name: "oci build ${{ matrix.component }}"
steps:
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0
- uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:
registry: ghcr.io
username: "${{ github.actor }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0
id: push
with:
file: ./images/Dockerfile.${{ matrix.component }}
platforms: linux/amd64,linux/aarch64
tags: "ghcr.io/edera-dev/${{ matrix.component }}:nightly"
push: true
- env:
DIGEST: "${{ steps.push.outputs.digest }}"
TAGS: "ghcr.io/edera-dev/${{ matrix.component }}:nightly"
COSIGN_EXPERIMENTAL: "true"
run: cosign sign --yes "${TAGS}@${DIGEST}"

View File

@ -3,10 +3,6 @@ on:
pull_request:
branches:
- main
paths:
- "os/**"
- "hack/os/**"
- "hack/ci/**"
merge_group:
branches:
- main
@ -23,17 +19,18 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: os build ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/os/build.sh
env:
KRATA_KERNEL_BUILD_JOBS: "5"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: krata-os-${{ matrix.arch }}
path: "target/os/krata-${{ matrix.arch }}.qcow2"

View File

@ -1,6 +1,8 @@
name: release-binaries
permissions:
contents: write
packages: write
id-token: write
on:
release:
types:
@ -25,28 +27,23 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: release-binaries server ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/dist/bundle.sh
env:
KRATA_KERNEL_BUILD_JOBS: "5"
- run: "./hack/ci/assemble-release-assets.sh bundle-systemd ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/bundle-systemd-${{ matrix.arch }}.tgz"
- run: ./hack/dist/deb.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- run: "./hack/ci/assemble-release-assets.sh debian ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/*.deb"
- run: ./hack/dist/apk.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- run: "./hack/ci/assemble-release-assets.sh alpine ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/dist/*_${{ matrix.arch }}.apk"
- run: ./hack/os/build.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- run: "./hack/ci/assemble-release-assets.sh os ${{ github.event.release.tag_name }} ${{ matrix.arch }} target/os/krata-${{ matrix.arch }}.qcow2"
- run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}"
env:
@ -72,16 +69,19 @@ jobs:
shell: bash
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
if: ${{ matrix.platform.os != 'darwin' }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: "${{ matrix.platform.arch }}-apple-darwin"
if: ${{ matrix.platform.os == 'darwin' }}
- uses: homebrew/actions/setup-homebrew@master
- uses: homebrew/actions/setup-homebrew@4b34604e75af8f8b23b454f0b5ffb7c5d8ce0056 # master
if: ${{ matrix.platform.os == 'darwin' }}
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
- run: ./hack/build/cargo.sh build --release --bin kratactl
@ -92,3 +92,43 @@ jobs:
- run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
oci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
component:
- kratactl
- kratad
- kratanet
- krata-guest-init
name: "release-binaries oci ${{ matrix.component }}"
steps:
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0
- uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:
registry: ghcr.io
username: "${{ github.actor }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- id: version
run: |
echo "KRATA_VERSION=$(./hack/dist/version.sh)" >> "${GITHUB_OUTPUT}"
- uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0
id: push
with:
file: ./images/Dockerfile.${{ matrix.component }}
platforms: linux/amd64,linux/aarch64
tags: "ghcr.io/edera-dev/${{ matrix.component }}:${{ steps.version.outputs.KRATA_VERSION }}"
push: true
- env:
DIGEST: "${{ steps.push.outputs.digest }}"
TAGS: "ghcr.io/edera-dev/${{ matrix.component }}:${{ steps.version.outputs.KRATA_VERSION }}"
COSIGN_EXPERIMENTAL: "true"
run: cosign sign --yes "${TAGS}@${DIGEST}"

View File

@ -14,20 +14,23 @@ jobs:
name: release-plz
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
id: generate-token
with:
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
token: "${{ steps.generate-token.outputs.token }}"
- uses: dtolnay/rust-toolchain@stable
- run: ./hack/ci/install-linux-deps.sh
- name: release-plz
uses: MarcoIeni/release-plz-action@v0.5
env:
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
CARGO_REGISTRY_TOKEN: "${{ secrets.KRATA_RELEASE_CARGO_TOKEN }}"
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/create-github-app-token@31c86eb3b33c9b601a1f60f98dcbfd1d70f379b4 # v1.10.3
id: generate-token
with:
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
fetch-depth: 0
token: "${{ steps.generate-token.outputs.token }}"
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
- run: ./hack/ci/install-linux-deps.sh
- name: release-plz
uses: MarcoIeni/release-plz-action@86afd21a7b114234aab55ba0005eed52f77d89e4 # v0.5.62
env:
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
CARGO_REGISTRY_TOKEN: "${{ secrets.KRATA_RELEASE_CARGO_TOKEN }}"

View File

@ -19,10 +19,13 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: server build ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/build/cargo.sh build
test:
@ -36,10 +39,13 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: server test ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/build/cargo.sh test
clippy:
@ -53,10 +59,13 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: server clippy ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
components: clippy
- run: ./hack/ci/install-linux-deps.sh
@ -72,10 +81,13 @@ jobs:
TARGET_ARCH: "${{ matrix.arch }}"
name: server initrd ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
with:
egress-policy: audit
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@d388a4836fcdbde0e50e395dc79a2670ccdef13f # stable
with:
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
- run: ./hack/ci/install-linux-deps.sh

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "vendor"]
path = vendor
url = https://github.com/edera-dev/krata-vendor.git

View File

@ -6,6 +6,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.0.12](https://github.com/edera-dev/krata/compare/v0.0.11...v0.0.12) - 2024-07-12
### Added
- *(oci)* add configuration value for oci seed file ([#220](https://github.com/edera-dev/krata/pull/220))
- *(power-management-defaults)* set an initial power management policy ([#219](https://github.com/edera-dev/krata/pull/219))
### Fixed
- *(daemon)* decrease rate of runtime reconcile ([#224](https://github.com/edera-dev/krata/pull/224))
- *(power)* ensure that xeon cpus with cpu gaps are not detected as p/e compatible ([#218](https://github.com/edera-dev/krata/pull/218))
- *(runtime)* use iommu only if devices are needed ([#243](https://github.com/edera-dev/krata/pull/243))
### Other
- Power management core functionality ([#217](https://github.com/edera-dev/krata/pull/217))
- *(powermgmt)* disable for now as a hackfix ([#242](https://github.com/edera-dev/krata/pull/242))
- Initial fluentd support ([#205](https://github.com/edera-dev/krata/pull/205))
- update Cargo.toml dependencies
- Use native loopdev implementation instead of loopdev-3 ([#209](https://github.com/edera-dev/krata/pull/209))
## [0.0.11](https://github.com/edera-dev/krata/compare/v0.0.10...v0.0.11) - 2024-06-23
### Added
- pci passthrough ([#114](https://github.com/edera-dev/krata/pull/114))
- *(runtime)* concurrent ip allocation ([#151](https://github.com/edera-dev/krata/pull/151))
- *(xen)* dynamic platform architecture ([#194](https://github.com/edera-dev/krata/pull/194))
### Fixed
- *(oci)* remove file size limit ([#142](https://github.com/edera-dev/krata/pull/142))
- *(oci)* use mirror.gcr.io as a mirror to docker hub ([#141](https://github.com/edera-dev/krata/pull/141))
### Other
- first pass of krata as an isolation engine
- *(xen)* split platform support into separate crate ([#195](https://github.com/edera-dev/krata/pull/195))
- *(xen)* move device creation into transaction interface ([#196](https://github.com/edera-dev/krata/pull/196))
## [0.0.10](https://github.com/edera-dev/krata/compare/v0.0.9...v0.0.10) - 2024-04-22
### Added

34
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,34 @@
# Contributing to Krata
Welcome! We're very glad you're reading this; Edera is excited for all kinds of contributions! Please read the following to ensure you're aware of our flow and policies.
## Before contributing
1. Please read our [Code of Conduct](CODE_OF_CONDUCT.md), which applies to all interactions in/with all Edera projects and venues.
2. Before opening an issue or PR, please try a few searches to see if there is overlap with existing conversations or WIP contributions.
3. For security or otherwise sensitive topics, please read our [Security Policy].
4. Ask questions! If you want to ask something, chances are someone else wants to ask it as well.
## Contributing Code
To get started with technical contributions, please read out [Development Guide]. If you're looking for something easy to tackle, [look for issues labeled `good first issue`][good-first-issue].
## Reporting bugs and other issues
While it's totally fine to simply bring it up on our Discord, we encourage opening an issue on GitHub using the Bug Report template.
## Pull Requests
1. For anything more than simple bug/doc fixes, please open a GitHub issue for tracking purposes.
- Else skip to step 3.
2. Discuss the change with the teams to ensure we have consensus on the change being welcome.
3. We encourage opening the PR sooner than later, and prefixing with `WIP:` so GitHub labels it as a Draft.
4. Please include a detailed list of changes that the PR makes.
5. Once the PR is ready for review, remove the Draft status, and request a review from `edera-dev/engineering`.
6. After the review cycle concludes and we know you are ready for merging, a team member will submit the PR to the merge queue.
[Code of Conduct]: ./CODE_OF_CONDUCT.md
[Security Policy]: ./SECURITY.md
[Development Guide]: ./DEV.md
[good-first-issues]: https://github.com/edera-dev/krata/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22

566
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
[workspace]
members = [
"crates/build",
"crates/krata",
"crates/oci",
"crates/guest",
@ -11,12 +12,13 @@ members = [
"crates/xen/xenclient",
"crates/xen/xenevtchn",
"crates/xen/xengnt",
"crates/xen/xenplatform",
"crates/xen/xenstore",
]
resolver = "2"
[workspace.package]
version = "0.0.10"
version = "0.0.12"
homepage = "https://krata.dev"
license = "Apache-2.0"
repository = "https://github.com/edera-dev/krata"
@ -24,13 +26,14 @@ repository = "https://github.com/edera-dev/krata"
[workspace.dependencies]
anyhow = "1.0"
arrayvec = "0.7.4"
async-compression = "0.4.8"
async-compression = "0.4.11"
async-stream = "0.3.5"
async-trait = "0.1.80"
async-trait = "0.1.81"
backhand = "0.15.0"
base64 = "0.22.0"
base64 = "0.22.1"
byteorder = "1"
bytes = "1.5.0"
c2rust-bitfields = "0.18.0"
cgroups-rs = "0.3.4"
circular-buffer = "0.1.7"
comfy-table = "7.1.1"
@ -47,44 +50,47 @@ indexmap = "2.2.6"
indicatif = "0.17.8"
ipnetwork = "0.20.0"
libc = "0.2"
log = "0.4.20"
log = "0.4.22"
loopdev-3 = "0.5.1"
krata-advmac = "1.1.0"
krata-tokio-tar = "0.4.0"
memchr = "2"
nix = "0.28.0"
oci-spec = "0.6.4"
nix = "0.29.0"
oci-spec = "0.6.7"
once_cell = "1.19.0"
path-absolutize = "3.1.1"
path-clean = "1.0.1"
prost = "0.12.4"
prost-build = "0.12.4"
platform-info = "2.0.3"
prost = "0.12.6"
prost-build = "0.12.6"
prost-reflect-build = "0.13.0"
prost-types = "0.12.4"
prost-types = "0.12.6"
rand = "0.8.5"
ratatui = "0.26.2"
redb = "2.1.0"
ratatui = "0.26.3"
redb = "2.1.1"
regex = "1.10.5"
rtnetlink = "0.14.1"
scopeguard = "1.2.0"
serde_json = "1.0.116"
serde_json = "1.0.120"
serde_yaml = "0.9"
sha256 = "1.5.0"
signal-hook = "0.3.17"
slice-copy = "0.3.0"
smoltcp = "0.11.0"
sysinfo = "0.30.11"
sysinfo = "0.30.13"
termtree = "0.4.1"
thiserror = "1.0"
tokio-tun = "0.11.4"
tokio-tun = "0.11.5"
toml = "0.8.14"
tonic-build = "0.11.0"
tower = "0.4.13"
udp-stream = "0.0.11"
url = "2.5.0"
url = "2.5.2"
walkdir = "2"
xz2 = "0.1"
[workspace.dependencies.clap]
version = "4.4.18"
version = "4.5.9"
features = ["derive"]
[workspace.dependencies.prost-reflect]
@ -92,12 +98,12 @@ version = "0.13.1"
features = ["derive"]
[workspace.dependencies.reqwest]
version = "0.12.4"
version = "0.12.5"
default-features = false
features = ["rustls-tls"]
[workspace.dependencies.serde]
version = "1.0.198"
version = "1.0.204"
features = ["derive"]
[workspace.dependencies.sys-mount]
@ -105,7 +111,7 @@ version = "3.0.0"
default-features = false
[workspace.dependencies.tokio]
version = "1.35.1"
version = "1.38.0"
features = ["full"]
[workspace.dependencies.tokio-stream]
@ -117,7 +123,7 @@ version = "0.11.0"
features = ["tls"]
[workspace.dependencies.uuid]
version = "1.6.1"
version = "1.10.0"
features = ["v4"]
[profile.release]

21
DEV.md
View File

@ -19,7 +19,7 @@ it's corresponding code path from the above table.
| Component | Specification | Notes |
| ------------- | ------------- | --------------------------------------------------------------------------------- |
| Architecture | x86_64 | aarch64 support is still in development |
| Memory | At least 6GB | dom0 will need to be configured will lower memory limit to give krata guests room |
| Memory | At least 6GB | dom0 will need to be configured with lower memory limit to give krata guests room |
| Xen | 4.17 | Temporary due to hardcoded interface version constants |
| Debian | stable / sid | Debian is recommended due to the ease of Xen setup |
| rustup | any | Install Rustup from https://rustup.rs |
@ -31,7 +31,9 @@ it's corresponding code path from the above table.
2. Install required packages:
```sh
$ apt install git xen-system-amd64 build-essential libclang-dev musl-tools flex bison libelf-dev libssl-dev bc protobuf-compiler libprotobuf-dev squashfs-tools erofs-utils
$ apt install git xen-system-amd64 build-essential \
libclang-dev musl-tools flex bison libelf-dev libssl-dev bc \
protobuf-compiler libprotobuf-dev squashfs-tools erofs-utils
```
3. Install [rustup](https://rustup.rs) for managing a Rust environment.
@ -62,16 +64,23 @@ $ git clone https://github.com/edera-dev/krata.git krata
$ cd krata
```
6. Build a guest kernel image:
6. Fetch the guest kernel image:
```sh
$ ./hack/kernel/build.sh
$ ./hack/kernel/fetch.sh -u
```
7. Copy the guest kernel artifacts to `/var/lib/krata/guest/kernel` so it is automatically detected by kratad:
```sh
$ mkdir -p /var/lib/krata/guest
$ cp target/kernel/kernel-x86_64 /var/lib/krata/guest/kernel
$ cp target/kernel/addons-x86_64.squashfs /var/lib/krata/guest/addons.squashfs
```
7. Copy the guest kernel image at `target/kernel/kernel-x86_64` to `/var/lib/krata/guest/kernel` to have it automatically detected by kratad.
8. Launch `./hack/debug/kratad.sh` and keep it running in the foreground.
9. Launch `./hack/debug/kratanet.sh` and keep it running in the foreground.
10. Run kratactl to launch a guest:
10. Run `kratactl` to launch a guest:
```sh
$ ./hack/debug/kratactl.sh launch --attach alpine:latest

10
FAQ.md
View File

@ -2,18 +2,14 @@
## How does krata currently work?
The krata hypervisor makes it possible to launch OCI containers on a Xen hypervisor without utilizing the Xen userspace tooling. krata contains just enough of the userspace of Xen (reimplemented in Rust) to start an x86_64 Xen Linux PV guest, and implements a Linux init process that can boot an OCI container. It does so by converting an OCI image into a squashfs/erofs file and packaging basic startup data in a bundle which the init container can read.
The krata isolation engine makes it possible to launch OCI containers on a Xen hypervisor without utilizing the Xen userspace tooling. krata contains just enough of the userspace of Xen (reimplemented in Rust) to start an x86_64 Xen Linux PV guest, and implements a Linux init process that can boot an OCI container. It does so by converting an OCI image into a squashfs/erofs file and packaging basic startup data in a bundle that the init container can read.
In addition, due to the desire to reduce dependence on the dom0 network, krata contains a networking daemon called kratanet. kratanet listens for krata guests to startup and launches a userspace networking environment. krata guests can access the dom0 networking stack via the proxynat layer that makes it possible to communicate over UDP, TCP, and ICMP (echo only) to the outside world. In addition, each krata guest is provided a "gateway" IP (both in IPv4 and IPv6) which utilizes smoltcp to provide a virtual host. That virtual host in the future could dial connections into the container to access container networking resources.
In addition, due to the desire to reduce dependence on the dom0 network, krata contains a networking daemon called kratanet. kratanet listens for krata guests to startup and launches a userspace networking environment. krata guests can access the dom0 networking stack via the proxynat, which that makes it possible to communicate over UDP, TCP, and ICMP (echo only) to the outside world. In addition, each krata guest is provided a "gateway" IP (both in IPv4 and IPv6) which utilizes smoltcp to provide a virtual host. That virtual host in the future could dial connections into the container to access container networking resources.
## Why utilize Xen instead of KVM?
Xen is a very interesting technology, and Edera believes that type-1 hypervisors are ideal for security. Most OCI isolation techniques use KVM, which is not a type-1 hypervisor, and thus is subject to the security limitations of the OS kernel. A type-1 hypervisor on the otherhand provides a minimal amount of attack surface upon which less-trusted guests can be launched on top of.
Xen is a very interesting technology, and Edera believes that type-1 hypervisors are ideal for security. Most OCI isolation techniques use KVM, which is not a type-1 hypervisor, and thus is subject to the security limitations of the OS kernel. A type-1 hypervisor on the other hand provides a minimal attack surface upon which less-trusted guests can be launched on top of.
## Why not utilize pvcalls to provide access to the host network?
pvcalls is extremely interesting, and although it is certainly possible to utilize pvcalls to get the job done, we chose to utilize userspace networking technology in order to enhance security. Our goal is to drop the use of all xen networking backend drivers within the kernel and have the guest talk directly to a userspace daemon, bypassing the vif (xen-netback) driver. Currently, in order to develop the networking layer, we utilize xen-netback and then use raw sockets to provide the userspace networking layer on the host.
## What are the future plans?
Edera is building a company to compete in the hypervisor space with open-source technology. More information to come soon on official channels.

View File

@ -1,6 +1,6 @@
# krata
The Edera Hypervisor
An isolation engine for securing compute workloads.
![license](https://img.shields.io/github/license/edera-dev/krata)
![discord](https://img.shields.io/discord/1207447453083766814?label=discord)
@ -16,13 +16,13 @@ The Edera Hypervisor
## Introduction
krata is a single-host hypervisor service built for OCI-compliant containers. It isolates containers using a type-1 hypervisor, providing workload isolation that can exceed the security level of KVM-based OCI-compliant runtimes.
krata is a single-host workload isolation service. It isolates workloads using a type-1 hypervisor, providing a tight security boundary while preserving performance.
krata utilizes the core of the Xen hypervisor, with a fully memory-safe Rust control plane to bring Xen tooling into a new secure era.
krata utilizes the core of the Xen hypervisor with a fully memory-safe Rust control plane.
## Hardware Support
| Architecture | Completion Level | Virtualization Technology |
| ------------ | ---------------- | ------------------------- |
| x86_64 | 100% Completed | Intel VT-x, AMD-V |
| aarch64 | 30% Completed | AArch64 virtualization |
| Architecture | Completion Level | Hardware Virtualization |
| ------------ | ---------------- | ------------------------------- |
| x86_64 | 100% Completed | None, Intel VT-x, AMD-V |
| aarch64 | 10% Completed | AArch64 virtualization |

25
crates/build/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "krata-buildtools"
description = "Build tools for krata."
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
publish = false
[dependencies]
anyhow = { workspace = true }
env_logger = { workspace = true }
oci-spec = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
krata-oci = { path = "../oci", version = "^0.0.12" }
krata-tokio-tar = { workspace = true }
uuid = { workspace = true }
[[bin]]
name = "build-fetch-kernel"
path = "bin/fetch_kernel.rs"

View File

@ -0,0 +1,121 @@
use std::{
env::{self, args},
path::PathBuf,
};
use anyhow::{anyhow, Result};
use env_logger::Env;
use krataoci::{
name::ImageName,
packer::{service::OciPackerService, OciPackedFormat},
progress::OciProgressContext,
registry::OciPlatform,
};
use oci_spec::image::{Arch, Os};
use tokio::{
fs::{self, File},
io::BufReader,
};
use tokio_stream::StreamExt;
use tokio_tar::Archive;
use uuid::Uuid;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init();
fs::create_dir_all("target/kernel").await?;
let arch = env::var("TARGET_ARCH").map_err(|_| anyhow!("missing TARGET_ARCH env var"))?;
println!("kernel architecture: {}", arch);
let platform = OciPlatform::new(
Os::Linux,
match arch.as_str() {
"x86_64" => Arch::Amd64,
"aarch64" => Arch::ARM64,
_ => {
return Err(anyhow!("unknown architecture '{}'", arch));
}
},
);
let image = ImageName::parse(&args().nth(1).unwrap())?;
let mut cache_dir = std::env::temp_dir().clone();
cache_dir.push(format!("krata-cache-{}", Uuid::new_v4()));
fs::create_dir_all(&cache_dir).await?;
let _delete_cache_dir = scopeguard::guard(cache_dir.clone(), |dir| {
let _ = std::fs::remove_dir_all(dir);
});
let (context, _) = OciProgressContext::create();
let service = OciPackerService::new(None, &cache_dir, platform).await?;
let packed = service
.request(image.clone(), OciPackedFormat::Tar, false, context)
.await?;
let annotations = packed
.manifest
.item()
.annotations()
.clone()
.unwrap_or_default();
let Some(format) = annotations.get("dev.edera.kernel.format") else {
return Err(anyhow!(
"image manifest missing 'dev.edera.kernel.format' annotation"
));
};
let Some(version) = annotations.get("dev.edera.kernel.version") else {
return Err(anyhow!(
"image manifest missing 'dev.edera.kernel.version' annotation"
));
};
let Some(flavor) = annotations.get("dev.edera.kernel.flavor") else {
return Err(anyhow!(
"image manifest missing 'dev.edera.kernel.flavor' annotation"
));
};
if format != "1" {
return Err(anyhow!("kernel format version '{}' is unknown", format));
}
let file = BufReader::new(File::open(packed.path).await?);
let mut archive = Archive::new(file);
let mut entries = archive.entries()?;
let kernel_image_tar_path = PathBuf::from("kernel/image");
let kernel_addons_tar_path = PathBuf::from("kernel/addons.squashfs");
let kernel_image_out_path = PathBuf::from(format!("target/kernel/kernel-{}", arch));
let kernel_addons_out_path = PathBuf::from(format!("target/kernel/addons-{}.squashfs", arch));
if kernel_image_out_path.exists() {
fs::remove_file(&kernel_image_out_path).await?;
}
if kernel_addons_out_path.exists() {
fs::remove_file(&kernel_addons_out_path).await?;
}
while let Some(entry) = entries.next().await {
let mut entry = entry?;
let path = entry.path()?.to_path_buf();
if !entry.header().entry_type().is_file() {
continue;
}
if path == kernel_image_tar_path {
entry.unpack(&kernel_image_out_path).await?;
} else if path == kernel_addons_tar_path {
entry.unpack(&kernel_addons_out_path).await?;
}
}
if !kernel_image_out_path.exists() {
return Err(anyhow!("image did not contain a file named /kernel/image"));
}
println!("kernel version: v{}", version);
println!("kernel flavor: {}", flavor);
Ok(())
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-ctl"
description = "Command-line tool to control the krata hypervisor"
description = "Command-line tool to control the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -20,7 +20,7 @@ env_logger = { workspace = true }
fancy-duration = { workspace = true }
human_bytes = { workspace = true }
indicatif = { workspace = true }
krata = { path = "../krata", version = "^0.0.10" }
krata = { path = "../krata", version = "^0.0.12" }
log = { workspace = true }
prost-reflect = { workspace = true, features = ["serde"] }
prost-types = { workspace = true }

View File

@ -0,0 +1,46 @@
use anyhow::Result;
use clap::Parser;
use krata::v1::control::{control_service_client::ControlServiceClient, HostCpuTopologyRequest};
use tonic::{transport::Channel, Request};
fn class_to_str(input: i32) -> String {
match input {
0 => "Standard".to_string(),
1 => "Performance".to_string(),
2 => "Efficiency".to_string(),
_ => "???".to_string(),
}
}
#[derive(Parser)]
#[command(about = "Display information about a host's CPU topology")]
pub struct CpuTopologyCommand {}
impl CpuTopologyCommand {
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
println!(
"{0:<10} {1:<10} {2:<10} {3:<10} {4:<10} {5:<10}",
"CPUID", "Node", "Socket", "Core", "Thread", "Class"
);
let response = client
.get_host_cpu_topology(Request::new(HostCpuTopologyRequest {}))
.await?
.into_inner();
for (i, cpu) in response.cpus.iter().enumerate() {
println!(
"{0:<10} {1:<10} {2:<10} {3:<10} {4:<10} {5:<10}",
i,
cpu.node,
cpu.socket,
cpu.core,
cpu.thread,
class_to_str(cpu.class)
);
}
Ok(())
}
}

View File

@ -6,8 +6,8 @@ use krata::{
events::EventStream,
v1::{
common::{
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestStatus,
GuestTaskSpec, GuestTaskSpecEnvVar, OciImageFormat,
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestSpecDevice,
GuestStatus, GuestTaskSpec, GuestTaskSpecEnvVar, OciImageFormat,
},
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
@ -50,6 +50,8 @@ pub struct LaunchCommand {
help = "Memory available to the guest, in megabytes"
)]
mem: u64,
#[arg[short = 'D', long = "device", help = "Devices to request for the guest"]]
device: Vec<String>,
#[arg[short, long, help = "Environment variables set in the guest"]]
env: Option<Vec<String>>,
#[arg(
@ -135,6 +137,11 @@ impl LaunchCommand {
working_directory: self.working_directory.unwrap_or_default(),
}),
annotations: vec![],
devices: self
.device
.iter()
.map(|name| GuestSpecDevice { name: name.clone() })
.collect(),
}),
};
let response = client

View File

@ -28,7 +28,7 @@ enum ListFormat {
}
#[derive(Parser)]
#[command(about = "List the guests on the hypervisor")]
#[command(about = "List the guests on the isolation engine")]
pub struct ListCommand {
#[arg(short, long, default_value = "table", help = "Output format")]
format: ListFormat,

View File

@ -0,0 +1,128 @@
use anyhow::Result;
use clap::{Parser, ValueEnum};
use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
use krata::{
events::EventStream,
v1::control::{control_service_client::ControlServiceClient, DeviceInfo, ListDevicesRequest},
};
use serde_json::Value;
use tonic::transport::Channel;
use crate::format::{kv2line, proto2dynamic, proto2kv};
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
enum ListDevicesFormat {
Table,
Json,
JsonPretty,
Jsonl,
Yaml,
KeyValue,
Simple,
}
#[derive(Parser)]
#[command(about = "List the devices on the isolation engine")]
pub struct ListDevicesCommand {
#[arg(short, long, default_value = "table", help = "Output format")]
format: ListDevicesFormat,
}
impl ListDevicesCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
_events: EventStream,
) -> Result<()> {
let reply = client
.list_devices(ListDevicesRequest {})
.await?
.into_inner();
let mut devices = reply.devices;
devices.sort_by(|a, b| a.name.cmp(&b.name));
match self.format {
ListDevicesFormat::Table => {
self.print_devices_table(devices)?;
}
ListDevicesFormat::Simple => {
for device in devices {
println!("{}\t{}\t{}", device.name, device.claimed, device.owner);
}
}
ListDevicesFormat::Json | ListDevicesFormat::JsonPretty | ListDevicesFormat::Yaml => {
let mut values = Vec::new();
for device in devices {
let message = proto2dynamic(device)?;
values.push(serde_json::to_value(message)?);
}
let value = Value::Array(values);
let encoded = if self.format == ListDevicesFormat::JsonPretty {
serde_json::to_string_pretty(&value)?
} else if self.format == ListDevicesFormat::Yaml {
serde_yaml::to_string(&value)?
} else {
serde_json::to_string(&value)?
};
println!("{}", encoded.trim());
}
ListDevicesFormat::Jsonl => {
for device in devices {
let message = proto2dynamic(device)?;
println!("{}", serde_json::to_string(&message)?);
}
}
ListDevicesFormat::KeyValue => {
self.print_key_value(devices)?;
}
}
Ok(())
}
fn print_devices_table(&self, devices: Vec<DeviceInfo>) -> Result<()> {
let mut table = Table::new();
table.load_preset(UTF8_FULL_CONDENSED);
table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
table.set_header(vec!["name", "status", "owner"]);
for device in devices {
let status_text = if device.claimed {
"claimed"
} else {
"available"
};
let status_color = if device.claimed {
Color::Blue
} else {
Color::Green
};
table.add_row(vec![
Cell::new(device.name),
Cell::new(status_text).fg(status_color),
Cell::new(device.owner),
]);
}
if table.is_empty() {
println!("no devices configured");
} else {
println!("{}", table);
}
Ok(())
}
fn print_key_value(&self, devices: Vec<DeviceInfo>) -> Result<()> {
for device in devices {
let kvs = proto2kv(device)?;
println!("{}", kv2line(kvs));
}
Ok(())
}
}

View File

@ -1,10 +1,12 @@
pub mod attach;
pub mod cpu_topology;
pub mod destroy;
pub mod exec;
pub mod identify_host;
pub mod idm_snoop;
pub mod launch;
pub mod list;
pub mod list_devices;
pub mod logs;
pub mod metrics;
pub mod pull;
@ -22,22 +24,20 @@ use krata::{
use tonic::{transport::Channel, Request};
use self::{
attach::AttachCommand, destroy::DestroyCommand, exec::ExecCommand,
identify_host::IdentifyHostCommand, idm_snoop::IdmSnoopCommand, launch::LaunchCommand,
list::ListCommand, logs::LogsCommand, metrics::MetricsCommand, pull::PullCommand,
resolve::ResolveCommand, top::TopCommand, watch::WatchCommand,
attach::AttachCommand, cpu_topology::CpuTopologyCommand, destroy::DestroyCommand,
exec::ExecCommand, identify_host::IdentifyHostCommand, idm_snoop::IdmSnoopCommand,
launch::LaunchCommand, list::ListCommand, list_devices::ListDevicesCommand, logs::LogsCommand,
metrics::MetricsCommand, pull::PullCommand, resolve::ResolveCommand, top::TopCommand,
watch::WatchCommand,
};
#[derive(Parser)]
#[command(
version,
about = "Control the krata hypervisor, a secure platform for running containers"
)]
#[command(version, about = "Control the krata isolation engine")]
pub struct ControlCommand {
#[arg(
short,
long,
help = "The connection URL to the krata hypervisor",
help = "The connection URL to the krata isolation engine",
default_value = "unix:///var/lib/krata/daemon.socket"
)]
connection: String,
@ -51,6 +51,7 @@ pub enum Commands {
Launch(LaunchCommand),
Destroy(DestroyCommand),
List(ListCommand),
ListDevices(ListDevicesCommand),
Attach(AttachCommand),
Pull(PullCommand),
Logs(LogsCommand),
@ -61,6 +62,7 @@ pub enum Commands {
Top(TopCommand),
IdentifyHost(IdentifyHostCommand),
Exec(ExecCommand),
CpuTopology(CpuTopologyCommand),
}
impl ControlCommand {
@ -120,6 +122,14 @@ impl ControlCommand {
Commands::Exec(exec) => {
exec.run(client).await?;
}
Commands::ListDevices(list) => {
list.run(client, events).await?;
}
Commands::CpuTopology(cpu_topology) => {
cpu_topology.run(client).await?;
}
}
Ok(())
}

View File

@ -138,7 +138,7 @@ impl TopApp {
impl Widget for &mut TopApp {
fn render(self, area: Rect, buf: &mut Buffer) {
let title = Title::from(" krata hypervisor ".bold());
let title = Title::from(" krata isolation engine ".bold());
let instructions = Title::from(vec![" Quit ".into(), "<Q> ".blue().bold()]);
let block = Block::default()
.title(title.alignment(Alignment::Center))

View File

@ -1,6 +1,6 @@
[package]
name = "krata-daemon"
description = "Daemon for the krata hypervisor."
description = "Daemon for the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -17,16 +17,18 @@ circular-buffer = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
futures = { workspace = true }
krata = { path = "../krata", version = "^0.0.10" }
krata-oci = { path = "../oci", version = "^0.0.10" }
krata-runtime = { path = "../runtime", version = "^0.0.10" }
krata = { path = "../krata", version = "^0.0.12" }
krata-oci = { path = "../oci", version = "^0.0.12" }
krata-runtime = { path = "../runtime", version = "^0.0.12" }
log = { workspace = true }
prost = { workspace = true }
redb = { workspace = true }
scopeguard = { workspace = true }
serde = { workspace = true }
signal-hook = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
toml = { workspace = true }
krata-tokio-tar = { workspace = true }
tonic = { workspace = true, features = ["tls"] }
uuid = { workspace = true }

View File

@ -1,15 +1,31 @@
use std::{
net::{SocketAddr, TcpStream},
str::FromStr,
sync::{atomic::AtomicBool, Arc},
};
use anyhow::Result;
use clap::Parser;
use env_logger::Env;
use kratad::command::DaemonCommand;
use env_logger::fmt::Target;
use log::LevelFilter;
use std::sync::{atomic::AtomicBool, Arc};
use kratad::command::DaemonCommand;
#[tokio::main(flavor = "multi_thread", worker_threads = 10)]
async fn main() -> Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
.filter(Some("backhand::filesystem::writer"), LevelFilter::Warn)
.init();
let mut builder = env_logger::Builder::new();
builder
.filter_level(LevelFilter::Trace)
.parse_default_env()
.filter(Some("backhand::filesystem::writer"), LevelFilter::Warn);
if let Ok(f_addr) = std::env::var("KRATA_FLUENT_ADDR") {
let target = SocketAddr::from_str(f_addr.as_str())?;
builder.target(Target::Pipe(Box::new(TcpStream::connect(target)?)));
}
builder.init();
mask_sighup()?;
let command = DaemonCommand::parse();

View File

@ -6,7 +6,7 @@ use std::str::FromStr;
use crate::Daemon;
#[derive(Parser)]
#[command(version, about = "Krata hypervisor daemon")]
#[command(version, about = "krata isolation engine daemon")]
pub struct DaemonCommand {
#[arg(
short,

View File

@ -0,0 +1,63 @@
use std::{collections::HashMap, path::Path};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::fs;
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct DaemonConfig {
#[serde(default)]
pub oci: OciConfig,
#[serde(default)]
pub pci: DaemonPciConfig,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct OciConfig {
#[serde(default)]
pub seed: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct DaemonPciConfig {
#[serde(default)]
pub devices: HashMap<String, DaemonPciDeviceConfig>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DaemonPciDeviceConfig {
pub locations: Vec<String>,
#[serde(default)]
pub permissive: bool,
#[serde(default)]
#[serde(rename = "msi-translate")]
pub msi_translate: bool,
#[serde(default)]
#[serde(rename = "power-management")]
pub power_management: bool,
#[serde(default)]
#[serde(rename = "rdm-reserve-policy")]
pub rdm_reserve_policy: DaemonPciDeviceRdmReservePolicy,
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub enum DaemonPciDeviceRdmReservePolicy {
#[default]
#[serde(rename = "strict")]
Strict,
#[serde(rename = "relaxed")]
Relaxed,
}
impl DaemonConfig {
pub async fn load(path: &Path) -> Result<DaemonConfig> {
if path.exists() {
let content = fs::read_to_string(path).await?;
let config: DaemonConfig = toml::from_str(&content)?;
Ok(config)
} else {
fs::write(&path, "").await?;
Ok(DaemonConfig::default())
}
}
}

View File

@ -11,7 +11,9 @@ use krata::{
control::{
control_service_server::ControlService, ConsoleDataReply, ConsoleDataRequest,
CreateGuestReply, CreateGuestRequest, DestroyGuestReply, DestroyGuestRequest,
ExecGuestReply, ExecGuestRequest, IdentifyHostReply, IdentifyHostRequest,
DeviceInfo, ExecGuestReply, ExecGuestRequest, HostCpuTopologyInfo,
HostCpuTopologyReply, HostCpuTopologyRequest, HostPowerManagementPolicy,
IdentifyHostReply, IdentifyHostRequest, ListDevicesReply, ListDevicesRequest,
ListGuestsReply, ListGuestsRequest, PullImageReply, PullImageRequest,
ReadGuestMetricsReply, ReadGuestMetricsRequest, ResolveGuestReply, ResolveGuestRequest,
SnoopIdmReply, SnoopIdmRequest, WatchEventsReply, WatchEventsRequest,
@ -23,6 +25,7 @@ use krataoci::{
packer::{service::OciPackerService, OciPackedFormat, OciPackedImage},
progress::{OciProgress, OciProgressContext},
};
use kratart::Runtime;
use std::{pin::Pin, str::FromStr};
use tokio::{
select,
@ -35,8 +38,8 @@ use uuid::Uuid;
use crate::{
command::DaemonCommand, console::DaemonConsoleHandle, db::GuestStore,
event::DaemonEventContext, glt::GuestLookupTable, idm::DaemonIdmHandle,
metrics::idm_metric_to_api, oci::convert_oci_progress,
devices::DaemonDeviceManager, event::DaemonEventContext, glt::GuestLookupTable,
idm::DaemonIdmHandle, metrics::idm_metric_to_api, oci::convert_oci_progress,
};
pub struct ApiError {
@ -60,32 +63,39 @@ impl From<ApiError> for Status {
#[derive(Clone)]
pub struct DaemonControlService {
glt: GuestLookupTable,
devices: DaemonDeviceManager,
events: DaemonEventContext,
console: DaemonConsoleHandle,
idm: DaemonIdmHandle,
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
packer: OciPackerService,
runtime: Runtime,
}
impl DaemonControlService {
#[allow(clippy::too_many_arguments)]
pub fn new(
glt: GuestLookupTable,
devices: DaemonDeviceManager,
events: DaemonEventContext,
console: DaemonConsoleHandle,
idm: DaemonIdmHandle,
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
packer: OciPackerService,
runtime: Runtime,
) -> Self {
Self {
glt,
devices,
events,
console,
idm,
guests,
guest_reconciler_notify,
packer,
runtime,
}
}
}
@ -524,4 +534,76 @@ impl ControlService for DaemonControlService {
};
Ok(Response::new(Box::pin(output) as Self::SnoopIdmStream))
}
async fn list_devices(
&self,
request: Request<ListDevicesRequest>,
) -> Result<Response<ListDevicesReply>, Status> {
let _ = request.into_inner();
let mut devices = Vec::new();
let state = self.devices.copy().await.map_err(|error| ApiError {
message: error.to_string(),
})?;
for (name, state) in state {
devices.push(DeviceInfo {
name,
claimed: state.owner.is_some(),
owner: state.owner.map(|x| x.to_string()).unwrap_or_default(),
});
}
Ok(Response::new(ListDevicesReply { devices }))
}
async fn get_host_cpu_topology(
&self,
request: Request<HostCpuTopologyRequest>,
) -> Result<Response<HostCpuTopologyReply>, Status> {
let _ = request.into_inner();
let power = self
.runtime
.power_management_context()
.await
.map_err(ApiError::from)?;
let cputopo = power.cpu_topology().await.map_err(ApiError::from)?;
let mut cpus = vec![];
for cpu in cputopo {
cpus.push(HostCpuTopologyInfo {
core: cpu.core,
socket: cpu.socket,
node: cpu.node,
thread: cpu.thread,
class: cpu.class as i32,
})
}
Ok(Response::new(HostCpuTopologyReply { cpus }))
}
async fn set_host_power_management_policy(
&self,
request: Request<HostPowerManagementPolicy>,
) -> Result<Response<HostPowerManagementPolicy>, Status> {
let policy = request.into_inner();
let power = self
.runtime
.power_management_context()
.await
.map_err(ApiError::from)?;
let scheduler = &policy.scheduler;
power
.set_smt_policy(policy.smt_awareness)
.await
.map_err(ApiError::from)?;
power
.set_scheduler_policy(scheduler)
.await
.map_err(ApiError::from)?;
Ok(Response::new(HostPowerManagementPolicy {
scheduler: scheduler.to_string(),
smt_awareness: policy.smt_awareness,
}))
}
}

View File

@ -0,0 +1,106 @@
use std::{collections::HashMap, sync::Arc};
use anyhow::{anyhow, Result};
use log::warn;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::config::{DaemonConfig, DaemonPciDeviceConfig};
#[derive(Clone)]
pub struct DaemonDeviceState {
pub pci: Option<DaemonPciDeviceConfig>,
pub owner: Option<Uuid>,
}
#[derive(Clone)]
pub struct DaemonDeviceManager {
config: Arc<DaemonConfig>,
devices: Arc<RwLock<HashMap<String, DaemonDeviceState>>>,
}
impl DaemonDeviceManager {
pub fn new(config: Arc<DaemonConfig>) -> Self {
Self {
config,
devices: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn claim(&self, device: &str, uuid: Uuid) -> Result<DaemonDeviceState> {
let mut devices = self.devices.write().await;
let Some(state) = devices.get_mut(device) else {
return Err(anyhow!(
"unable to claim unknown device '{}' for guest {}",
device,
uuid
));
};
if let Some(owner) = state.owner {
return Err(anyhow!(
"unable to claim device '{}' for guest {}: already claimed by {}",
device,
uuid,
owner
));
}
state.owner = Some(uuid);
Ok(state.clone())
}
pub async fn release_all(&self, uuid: Uuid) -> Result<()> {
let mut devices = self.devices.write().await;
for state in (*devices).values_mut() {
if state.owner == Some(uuid) {
state.owner = None;
}
}
Ok(())
}
pub async fn release(&self, device: &str, uuid: Uuid) -> Result<()> {
let mut devices = self.devices.write().await;
let Some(state) = devices.get_mut(device) else {
return Ok(());
};
if let Some(owner) = state.owner {
if owner != uuid {
return Ok(());
}
}
state.owner = None;
Ok(())
}
pub async fn update_claims(&self, claims: HashMap<String, Uuid>) -> Result<()> {
let mut devices = self.devices.write().await;
devices.clear();
for (name, pci) in &self.config.pci.devices {
let owner = claims.get(name).cloned();
devices.insert(
name.clone(),
DaemonDeviceState {
owner,
pci: Some(pci.clone()),
},
);
}
for (name, uuid) in &claims {
if !devices.contains_key(name) {
warn!("unknown device '{}' assigned to guest {}", name, uuid);
}
}
Ok(())
}
pub async fn copy(&self) -> Result<HashMap<String, DaemonDeviceState>> {
let devices = self.devices.read().await;
Ok(devices.clone())
}
}

View File

@ -1,9 +1,11 @@
use std::{net::SocketAddr, path::PathBuf, str::FromStr};
use std::{net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc};
use anyhow::{anyhow, Result};
use config::DaemonConfig;
use console::{DaemonConsole, DaemonConsoleHandle};
use control::DaemonControlService;
use db::GuestStore;
use devices::DaemonDeviceManager;
use event::{DaemonEventContext, DaemonEventGenerator};
use glt::GuestLookupTable;
use idm::{DaemonIdm, DaemonIdmHandle};
@ -23,9 +25,11 @@ use tonic::transport::{Identity, Server, ServerTlsConfig};
use uuid::Uuid;
pub mod command;
pub mod config;
pub mod console;
pub mod control;
pub mod db;
pub mod devices;
pub mod event;
pub mod glt;
pub mod idm;
@ -35,7 +39,9 @@ pub mod reconcile;
pub struct Daemon {
store: String,
_config: Arc<DaemonConfig>,
glt: GuestLookupTable,
devices: DaemonDeviceManager,
guests: GuestStore,
events: DaemonEventContext,
guest_reconciler_task: JoinHandle<()>,
@ -44,18 +50,27 @@ pub struct Daemon {
idm: DaemonIdmHandle,
console: DaemonConsoleHandle,
packer: OciPackerService,
runtime: Runtime,
}
const GUEST_RECONCILER_QUEUE_LEN: usize = 1000;
impl Daemon {
pub async fn new(store: String) -> Result<Self> {
let mut image_cache_dir = PathBuf::from(store.clone());
let store_dir = PathBuf::from(store.clone());
let mut config_path = store_dir.clone();
config_path.push("config.toml");
let config = DaemonConfig::load(&config_path).await?;
let config = Arc::new(config);
let devices = DaemonDeviceManager::new(config.clone());
let mut image_cache_dir = store_dir.clone();
image_cache_dir.push("cache");
image_cache_dir.push("image");
fs::create_dir_all(&image_cache_dir).await?;
let mut host_uuid_path = PathBuf::from(store.clone());
let mut host_uuid_path = store_dir.clone();
host_uuid_path.push("host.uuid");
let host_uuid = if host_uuid_path.is_file() {
let content = fs::read_to_string(&host_uuid_path).await?;
@ -74,11 +89,13 @@ impl Daemon {
generated
};
let initrd_path = detect_guest_file(&store, "initrd")?;
let kernel_path = detect_guest_file(&store, "kernel")?;
let initrd_path = detect_guest_path(&store, "initrd")?;
let kernel_path = detect_guest_path(&store, "kernel")?;
let addons_path = detect_guest_path(&store, "addons.squashfs")?;
let packer = OciPackerService::new(None, &image_cache_dir, OciPlatform::current()).await?;
let runtime = Runtime::new().await?;
let seed = config.oci.seed.clone().map(PathBuf::from);
let packer = OciPackerService::new(seed, &image_cache_dir, OciPlatform::current()).await?;
let runtime = Runtime::new(host_uuid).await?;
let glt = GuestLookupTable::new(0, host_uuid);
let guests_db_path = format!("{}/guests.db", store);
let guests = GuestStore::open(&PathBuf::from(guests_db_path))?;
@ -93,6 +110,7 @@ impl Daemon {
.await?;
let runtime_for_reconciler = runtime.dupe().await?;
let guest_reconciler = GuestReconciler::new(
devices.clone(),
glt.clone(),
guests.clone(),
events.clone(),
@ -101,14 +119,26 @@ impl Daemon {
guest_reconciler_notify.clone(),
kernel_path,
initrd_path,
addons_path,
)?;
let guest_reconciler_task = guest_reconciler.launch(guest_reconciler_receiver).await?;
let generator_task = generator.launch().await?;
// TODO: Create a way of abstracting early init tasks in kratad.
// TODO: Make initial power management policy configurable.
// FIXME: Power management hypercalls fail when running as an L1 hypervisor.
// let power = runtime.power_management_context().await?;
// power.set_smt_policy(true).await?;
// power
// .set_scheduler_policy("performance".to_string())
// .await?;
Ok(Self {
store,
_config: config,
glt,
devices,
guests,
events,
guest_reconciler_task,
@ -117,18 +147,21 @@ impl Daemon {
idm,
console,
packer,
runtime,
})
}
pub async fn listen(&mut self, addr: ControlDialAddress) -> Result<()> {
let control_service = DaemonControlService::new(
self.glt.clone(),
self.devices.clone(),
self.events.clone(),
self.console.clone(),
self.idm.clone(),
self.guests.clone(),
self.guest_reconciler_notify.clone(),
self.packer.clone(),
self.runtime.clone(),
);
let mut server = Server::builder();
@ -186,7 +219,7 @@ impl Drop for Daemon {
}
}
fn detect_guest_file(store: &str, name: &str) -> Result<PathBuf> {
fn detect_guest_path(store: &str, name: &str) -> Result<PathBuf> {
let mut path = PathBuf::from(format!("{}/guest/{}", store, name));
if path.is_file() {
return Ok(path);

View File

@ -26,6 +26,7 @@ use uuid::Uuid;
use crate::{
db::GuestStore,
devices::DaemonDeviceManager,
event::{DaemonEvent, DaemonEventContext},
glt::GuestLookupTable,
};
@ -55,6 +56,7 @@ impl Drop for GuestReconcilerEntry {
#[derive(Clone)]
pub struct GuestReconciler {
devices: DaemonDeviceManager,
glt: GuestLookupTable,
guests: GuestStore,
events: DaemonEventContext,
@ -62,6 +64,7 @@ pub struct GuestReconciler {
packer: OciPackerService,
kernel_path: PathBuf,
initrd_path: PathBuf,
addons_path: PathBuf,
tasks: Arc<Mutex<HashMap<Uuid, GuestReconcilerEntry>>>,
guest_reconciler_notify: Sender<Uuid>,
reconcile_lock: Arc<RwLock<()>>,
@ -70,6 +73,7 @@ pub struct GuestReconciler {
impl GuestReconciler {
#[allow(clippy::too_many_arguments)]
pub fn new(
devices: DaemonDeviceManager,
glt: GuestLookupTable,
guests: GuestStore,
events: DaemonEventContext,
@ -78,8 +82,10 @@ impl GuestReconciler {
guest_reconciler_notify: Sender<Uuid>,
kernel_path: PathBuf,
initrd_path: PathBuf,
modules_path: PathBuf,
) -> Result<Self> {
Ok(Self {
devices,
glt,
guests,
events,
@ -87,6 +93,7 @@ impl GuestReconciler {
packer,
kernel_path,
initrd_path,
addons_path: modules_path,
tasks: Arc::new(Mutex::new(HashMap::new())),
guest_reconciler_notify,
reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
@ -120,7 +127,7 @@ impl GuestReconciler {
}
},
_ = sleep(Duration::from_secs(5)) => {
_ = sleep(Duration::from_secs(15)) => {
if let Err(error) = self.reconcile_runtime(false).await {
error!("runtime reconciler failed: {}", error);
}
@ -152,6 +159,8 @@ impl GuestReconciler {
self.guests.remove(guest.uuid).await?;
}
let mut device_claims = HashMap::new();
for (uuid, mut stored_guest) in stored_guests {
let previous_guest = stored_guest.clone();
let runtime_guest = runtime_guests.iter().find(|x| x.uuid == uuid);
@ -173,6 +182,17 @@ impl GuestReconciler {
} else {
state.status = GuestStatus::Started.into();
}
for device in &stored_guest
.spec
.as_ref()
.cloned()
.unwrap_or_default()
.devices
{
device_claims.insert(device.name.clone(), uuid);
}
state.network = Some(guestinfo_to_networkstate(runtime));
stored_guest.state = Some(state);
}
@ -185,6 +205,9 @@ impl GuestReconciler {
let _ = self.guest_reconciler_notify.try_send(uuid);
}
}
self.devices.update_claims(device_claims).await?;
Ok(())
}
@ -255,8 +278,10 @@ impl GuestReconciler {
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
let starter = GuestStarter {
devices: &self.devices,
kernel_path: &self.kernel_path,
initrd_path: &self.initrd_path,
addons_path: &self.addons_path,
packer: &self.packer,
glt: &self.glt,
runtime: &self.runtime,
@ -293,6 +318,7 @@ impl GuestReconciler {
host: self.glt.host_uuid().to_string(),
domid: domid.unwrap_or(u32::MAX),
});
self.devices.release_all(uuid).await?;
Ok(GuestReconcilerResult::Changed { rerun: false })
}

View File

@ -1,5 +1,7 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::{anyhow, Result};
use futures::StreamExt;
@ -7,6 +9,7 @@ use krata::launchcfg::LaunchPackedFormat;
use krata::v1::common::GuestOciImageSpec;
use krata::v1::common::{guest_image_spec::Image, Guest, GuestState, GuestStatus, OciImageFormat};
use krataoci::packer::{service::OciPackerService, OciPackedFormat};
use kratart::launch::{PciBdf, PciDevice, PciRdmReservePolicy};
use kratart::{launch::GuestLaunchRequest, Runtime};
use log::info;
@ -15,17 +18,18 @@ use tokio::io::AsyncReadExt;
use tokio_tar::Archive;
use uuid::Uuid;
use crate::config::DaemonPciDeviceRdmReservePolicy;
use crate::devices::DaemonDeviceManager;
use crate::{
glt::GuestLookupTable,
reconcile::guest::{guestinfo_to_networkstate, GuestReconcilerResult},
};
// if a kernel is >= 100MB, that's kinda scary.
const OCI_SPEC_TAR_FILE_MAX_SIZE: usize = 100 * 1024 * 1024;
pub struct GuestStarter<'a> {
pub devices: &'a DaemonDeviceManager,
pub kernel_path: &'a Path,
pub initrd_path: &'a Path,
pub addons_path: &'a Path,
pub packer: &'a OciPackerService,
pub glt: &'a GuestLookupTable,
pub runtime: &'a Runtime,
@ -58,13 +62,6 @@ impl GuestStarter<'_> {
while let Some(entry) = entries.next().await {
let mut entry = entry?;
let path = entry.path()?;
if entry.header().size()? as usize > OCI_SPEC_TAR_FILE_MAX_SIZE {
return Err(anyhow!(
"file {} in image {} is larger than the size limit",
file.to_string_lossy(),
oci.digest
));
}
if path == file {
let mut buffer = Vec::new();
entry.read_to_end(&mut buffer).await?;
@ -135,6 +132,48 @@ impl GuestStarter<'_> {
fs::read(&self.initrd_path).await?
};
let success = AtomicBool::new(false);
let _device_release_guard = scopeguard::guard(
(spec.devices.clone(), self.devices.clone()),
|(devices, manager)| {
if !success.load(Ordering::Acquire) {
tokio::task::spawn(async move {
for device in devices {
let _ = manager.release(&device.name, uuid).await;
}
});
}
},
);
let mut pcis = Vec::new();
for device in &spec.devices {
let state = self.devices.claim(&device.name, uuid).await?;
if let Some(cfg) = state.pci {
for location in cfg.locations {
let pci = PciDevice {
bdf: PciBdf::from_str(&location)?.with_domain(0),
permissive: cfg.permissive,
msi_translate: cfg.msi_translate,
power_management: cfg.power_management,
rdm_reserve_policy: match cfg.rdm_reserve_policy {
DaemonPciDeviceRdmReservePolicy::Strict => PciRdmReservePolicy::Strict,
DaemonPciDeviceRdmReservePolicy::Relaxed => {
PciRdmReservePolicy::Relaxed
}
},
};
pcis.push(pci);
}
} else {
return Err(anyhow!(
"device '{}' isn't a known device type",
device.name
));
}
}
let info = self
.runtime
.launch(GuestLaunchRequest {
@ -150,6 +189,7 @@ impl GuestStarter<'_> {
initrd,
vcpus: spec.vcpus,
mem: spec.mem,
pcis,
env: task
.environment
.iter()
@ -157,6 +197,7 @@ impl GuestStarter<'_> {
.collect::<HashMap<_, _>>(),
run: empty_vec_optional(task.command.clone()),
debug: false,
addons_image: Some(self.addons_path.to_path_buf()),
})
.await?;
self.glt.associate(uuid, info.domid).await;
@ -169,6 +210,7 @@ impl GuestStarter<'_> {
host: self.glt.host_uuid().to_string(),
domid: info.domid,
});
success.store(true, Ordering::Release);
Ok(GuestReconcilerResult::Changed { rerun: false })
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-guest"
description = "Guest services for the krata hypervisor."
description = "Guest services for the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -14,13 +14,14 @@ cgroups-rs = { workspace = true }
env_logger = { workspace = true }
futures = { workspace = true }
ipnetwork = { workspace = true }
krata = { path = "../krata", version = "^0.0.10" }
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.10" }
krata = { path = "../krata", version = "^0.0.12" }
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.12" }
libc = { workspace = true }
log = { workspace = true }
nix = { workspace = true, features = ["ioctl", "process", "fs"] }
oci-spec = { workspace = true }
path-absolutize = { workspace = true }
platform-info = { workspace = true }
rtnetlink = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }

View File

@ -12,6 +12,7 @@ use nix::ioctl_write_int_bad;
use nix::unistd::{dup2, execve, fork, ForkResult, Pid};
use oci_spec::image::{Config, ImageConfiguration};
use path_absolutize::Absolutize;
use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI};
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::{File, OpenOptions, Permissions};
@ -50,6 +51,10 @@ const NEW_ROOT_DEV_PATH: &str = "/newroot/dev";
const IMAGE_CONFIG_JSON_PATH: &str = "/config/image/config.json";
const LAUNCH_CONFIG_JSON_PATH: &str = "/config/launch.json";
const ADDONS_DEVICE_PATH: &str = "/dev/xvdc";
const ADDONS_MOUNT_PATH: &str = "/addons";
const ADDONS_MODULES_PATH: &str = "/addons/modules";
ioctl_write_int_bad!(set_controlling_terminal, TIOCSCTTY);
pub struct GuestInit {}
@ -88,7 +93,10 @@ impl GuestInit {
self.mount_root_image(launch.root.format.clone()).await?;
self.mount_addons().await?;
self.mount_new_root().await?;
self.mount_kernel_modules().await?;
self.bind_new_root().await?;
if let Some(hostname) = launch.hostname.clone() {
@ -137,16 +145,60 @@ impl GuestInit {
self.create_dir("/root", Some(0o0700)).await?;
self.create_dir("/tmp", None).await?;
self.create_dir("/run", Some(0o0755)).await?;
self.mount_kernel_fs("devtmpfs", "/dev", "mode=0755", None)
self.mount_kernel_fs("devtmpfs", "/dev", "mode=0755", None, None)
.await?;
self.mount_kernel_fs("proc", "/proc", "", None, None)
.await?;
self.mount_kernel_fs("sysfs", "/sys", "", None, None)
.await?;
self.create_dir("/dev/pts", Some(0o0755)).await?;
self.mount_kernel_fs("devpts", "/dev/pts", "", None, Some("/dev/ptmx"))
.await?;
self.mount_kernel_fs("proc", "/proc", "", None).await?;
self.mount_kernel_fs("sysfs", "/sys", "", None).await?;
fs::symlink("/proc/self/fd", "/dev/fd").await?;
fs::symlink("/proc/self/fd/0", "/dev/stdin").await?;
fs::symlink("/proc/self/fd/1", "/dev/stdout").await?;
fs::symlink("/proc/self/fd/2", "/dev/stderr").await?;
self.mount_kernel_fs("cgroup2", "/sys/fs/cgroup", "", Some(MountFlags::RELATIME))
.await?;
self.mount_kernel_fs(
"cgroup2",
"/sys/fs/cgroup",
"",
Some(MountFlags::RELATIME),
None,
)
.await?;
Ok(())
}
async fn mount_addons(&mut self) -> Result<()> {
if !fs::try_exists(ADDONS_DEVICE_PATH).await? {
return Ok(());
}
self.mount_image(
&PathBuf::from(ADDONS_DEVICE_PATH),
&PathBuf::from(ADDONS_MOUNT_PATH),
LaunchPackedFormat::Squashfs,
)
.await?;
Ok(())
}
async fn mount_kernel_modules(&mut self) -> Result<()> {
if !fs::try_exists(ADDONS_MODULES_PATH).await? {
return Ok(());
}
let Some(platform_info) = PlatformInfo::new().ok() else {
return Ok(());
};
let kernel_release = platform_info.release().to_string_lossy().to_string();
let modules_path = format!("/newroot/lib/modules/{}", kernel_release);
fs::create_dir_all(&modules_path).await?;
Mount::builder()
.fstype(FilesystemType::Manual("none"))
.flags(MountFlags::BIND | MountFlags::RDONLY)
.mount(ADDONS_MODULES_PATH, modules_path)?;
Ok(())
}
@ -170,13 +222,14 @@ impl GuestInit {
path: &str,
data: &str,
flags: Option<MountFlags>,
source: Option<&str>,
) -> Result<()> {
trace!("mounting kernel fs {} to {}", fstype, path);
Mount::builder()
.fstype(FilesystemType::Manual(fstype))
.flags(MountFlags::NOEXEC | MountFlags::NOSUID | flags.unwrap_or(MountFlags::empty()))
.data(data)
.mount(fstype, path)?;
.mount(source.unwrap_or(fstype), path)?;
Ok(())
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata"
description = "Client library and common services for the krata hypervisor."
description = "Client library and common services for the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true

View File

@ -25,6 +25,7 @@ message GuestSpec {
uint64 mem = 6;
GuestTaskSpec task = 7;
repeated GuestSpecAnnotation annotations = 8;
repeated GuestSpecDevice devices = 9;
}
message GuestImageSpec {
@ -62,6 +63,10 @@ message GuestSpecAnnotation {
string value = 2;
}
message GuestSpecDevice {
string name = 1;
}
message GuestState {
GuestStatus status = 1;
GuestNetworkState network = 2;

View File

@ -16,6 +16,7 @@ service ControlService {
rpc DestroyGuest(DestroyGuestRequest) returns (DestroyGuestReply);
rpc ResolveGuest(ResolveGuestRequest) returns (ResolveGuestReply);
rpc ListGuests(ListGuestsRequest) returns (ListGuestsReply);
rpc ListDevices(ListDevicesRequest) returns (ListDevicesReply);
rpc ExecGuest(stream ExecGuestRequest) returns (stream ExecGuestReply);
@ -26,6 +27,9 @@ service ControlService {
rpc WatchEvents(WatchEventsRequest) returns (stream WatchEventsReply);
rpc PullImage(PullImageRequest) returns (stream PullImageReply);
rpc GetHostCpuTopology(HostCpuTopologyRequest) returns (HostCpuTopologyReply);
rpc SetHostPowerManagementPolicy(HostPowerManagementPolicy) returns (HostPowerManagementPolicy);
}
message IdentifyHostRequest {}
@ -187,3 +191,42 @@ message PullImageReply {
string digest = 2;
krata.v1.common.OciImageFormat format = 3;
}
message DeviceInfo {
string name = 1;
bool claimed = 2;
string owner = 3;
}
message ListDevicesRequest {}
message ListDevicesReply {
repeated DeviceInfo devices = 1;
}
enum HostCpuTopologyClass {
CPU_CLASS_STANDARD = 0;
CPU_CLASS_PERFORMANCE = 1;
CPU_CLASS_EFFICIENCY = 2;
}
message HostCpuTopologyInfo {
uint32 core = 1;
uint32 socket = 2;
uint32 node = 3;
uint32 thread = 4;
HostCpuTopologyClass class = 5;
}
message HostCpuTopologyRequest {}
message HostCpuTopologyReply {
repeated HostCpuTopologyInfo cpus = 1;
}
message HostPowerManagementPolicyRequest {}
message HostPowerManagementPolicy {
string scheduler = 1;
bool smt_awareness = 2;
}

15
crates/loopdev/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "krata-loopdev"
description = "Loop device handling library for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[lib]
name = "krataloopdev"
[dependencies]
libc.workspace = true

348
crates/loopdev/src/lib.rs Normal file
View File

@ -0,0 +1,348 @@
use libc::{c_int, ioctl};
use std::{
fs::{File, OpenOptions},
io,
os::fd::{AsRawFd, IntoRawFd, RawFd},
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
};
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
type IoctlRequest = libc::c_ulong;
#[cfg(any(target_os = "android", target_env = "musl"))]
type IoctlRequest = libc::c_int;
const LOOP_CONTROL: &str = "/dev/loop-control";
const LOOP_PREFIX: &str = "/dev/loop";
/// Loop control interface IOCTLs.
const LOOP_CTL_GET_FREE: IoctlRequest = 0x4C82;
/// Loop device flags.
const LO_FLAGS_READ_ONLY: u32 = 1;
const LO_FLAGS_AUTOCLEAR: u32 = 4;
const LO_FLAGS_PARTSCAN: u32 = 8;
const LO_FLAGS_DIRECT_IO: u32 = 16;
/// Loop device IOCTLs.
const LOOP_SET_FD: IoctlRequest = 0x4C00;
const LOOP_CLR_FD: IoctlRequest = 0x4C01;
const LOOP_SET_STATUS64: IoctlRequest = 0x4C04;
const LOOP_SET_CAPACITY: IoctlRequest = 0x4C07;
const LOOP_SET_DIRECT_IO: IoctlRequest = 0x4C08;
/// Interface which wraps a handle to the loop control device.
#[derive(Debug)]
pub struct LoopControl {
dev_file: File,
}
/// Translate ioctl results to errors if appropriate.
fn translate_error(ret: i32) -> io::Result<i32> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
impl LoopControl {
/// Open the loop control device.
///
/// # Errors
///
/// Any errors from physically opening the loop control device are
/// bubbled up.
pub fn open() -> io::Result<Self> {
Ok(Self {
dev_file: OpenOptions::new()
.read(true)
.write(true)
.open(LOOP_CONTROL)?,
})
}
/// Requests the next available loop device from the kernel and opens it.
///
/// # Examples
///
/// ```no_run
/// use krataloopdev::LoopControl;
/// let lc = LoopControl::open().unwrap();
/// let ld = lc.next_free().unwrap();
/// println!("{}", ld.path().unwrap().display());
/// ```
///
/// # Errors
///
/// Any errors from opening the loop device are bubbled up.
pub fn next_free(&self) -> io::Result<LoopDevice> {
let dev_num = translate_error(unsafe {
ioctl(
self.dev_file.as_raw_fd() as c_int,
LOOP_CTL_GET_FREE as IoctlRequest,
)
})?;
LoopDevice::open(format!("{}{}", LOOP_PREFIX, dev_num))
}
}
/// Interface to a loop device itself, e.g. `/dev/loop0`.
#[derive(Debug)]
pub struct LoopDevice {
device: File,
}
impl AsRawFd for LoopDevice {
fn as_raw_fd(&self) -> RawFd {
self.device.as_raw_fd()
}
}
impl IntoRawFd for LoopDevice {
fn into_raw_fd(self) -> RawFd {
self.device.into_raw_fd()
}
}
impl LoopDevice {
/// Opens a loop device.
///
/// # Errors
///
/// Any errors from opening the underlying physical loop device are bubbled up.
pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
Ok(Self {
device: OpenOptions::new().read(true).write(true).open(dev)?,
})
}
/// Attach a loop device to a file with the given options.
pub fn with(&self) -> AttachOptions<'_> {
AttachOptions {
device: self,
info: LoopInfo64::default(),
}
}
/// Enables or disables Direct I/O mode.
pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_DIRECT_IO as IoctlRequest,
if direct_io { 1 } else { 0 },
)
})?;
Ok(())
}
/// Attach the loop device to a fully-mapped file.
pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
let info = LoopInfo64 {
..Default::default()
};
Self::attach_with_loop_info(self, backing_file, info)
}
/// Attach the loop device to a file with `LoopInfo64`.
fn attach_with_loop_info(
&self,
backing_file: impl AsRef<Path>,
info: LoopInfo64,
) -> io::Result<()> {
let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;
let bf = OpenOptions::new()
.read(true)
.write(write_access)
.open(backing_file)?;
self.attach_fd_with_loop_info(bf, info)
}
/// Attach the loop device to a file descriptor with `LoopInfo64`.
fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: LoopInfo64) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_FD as IoctlRequest,
bf.as_raw_fd() as c_int,
)
})?;
let result = unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_STATUS64 as IoctlRequest,
&info,
)
};
match translate_error(result) {
Err(err) => {
let _detach_err = self.detach();
Err(err)
}
Ok(_) => Ok(()),
}
}
/// Get the path for the loop device.
pub fn path(&self) -> Option<PathBuf> {
let mut p = PathBuf::from("/proc/self/fd");
p.push(self.device.as_raw_fd().to_string());
std::fs::read_link(&p).ok()
}
/// Detach a loop device.
pub fn detach(&self) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_CLR_FD as IoctlRequest,
0,
)
})?;
Ok(())
}
/// Update a loop device's capacity.
pub fn set_capacity(&self) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_CAPACITY as IoctlRequest,
0,
)
})?;
Ok(())
}
/// Return the major device node number.
pub fn major(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| unsafe { libc::major(m.rdev()) })
}
/// Return the minor device node number.
pub fn minor(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| unsafe { libc::minor(m.rdev()) })
}
}
#[allow(dead_code)]
#[derive(Clone)]
pub struct LoopInfo64 {
lo_device: u64,
lo_inode: u64,
lo_rdevice: u64,
lo_offset: u64,
lo_sizelimit: u64,
lo_number: u32,
lo_encrypt_type: u32,
lo_encrypt_key_size: u32,
lo_flags: u32,
lo_file_name: [u8; 64],
lo_crypt_name: [u8; 64],
lo_encrypt_key: [u8; 32],
lo_init: [u64; 2],
}
impl Default for LoopInfo64 {
fn default() -> Self {
Self {
lo_device: 0,
lo_inode: 0,
lo_rdevice: 0,
lo_offset: 0,
lo_sizelimit: 0,
lo_number: 0,
lo_encrypt_type: 0,
lo_encrypt_key_size: 0,
lo_flags: 0,
lo_file_name: [0; 64],
lo_crypt_name: [0; 64],
lo_encrypt_key: [0; 32],
lo_init: [0, 2],
}
}
}
#[must_use]
pub struct AttachOptions<'d> {
device: &'d LoopDevice,
info: LoopInfo64,
}
impl AttachOptions<'_> {
pub fn offset(mut self, offset: u64) -> Self {
self.info.lo_offset = offset;
self
}
pub fn size_limit(mut self, size_limit: u64) -> Self {
self.info.lo_sizelimit = size_limit;
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
if read_only {
self.info.lo_flags |= LO_FLAGS_READ_ONLY;
} else {
self.info.lo_flags &= !LO_FLAGS_READ_ONLY;
}
self
}
pub fn autoclear(mut self, autoclear: bool) -> Self {
if autoclear {
self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
} else {
self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
}
self
}
pub fn part_scan(mut self, part_scan: bool) -> Self {
if part_scan {
self.info.lo_flags |= LO_FLAGS_PARTSCAN;
} else {
self.info.lo_flags &= !LO_FLAGS_PARTSCAN;
}
self
}
pub fn set_direct_io(mut self, direct_io: bool) -> Self {
if direct_io {
self.info.lo_flags |= LO_FLAGS_DIRECT_IO;
} else {
self.info.lo_flags &= !LO_FLAGS_DIRECT_IO;
}
self
}
pub fn direct_io(&self) -> bool {
(self.info.lo_flags & LO_FLAGS_DIRECT_IO) == LO_FLAGS_DIRECT_IO
}
pub fn attach(&self, backing_file: impl AsRef<Path>) -> io::Result<()> {
self.device
.attach_with_loop_info(backing_file, self.info.clone())?;
if self.direct_io() {
self.device.set_direct_io(self.direct_io())?;
}
Ok(())
}
pub fn attach_fd(&self, backing_file_fd: impl AsRawFd) -> io::Result<()> {
self.device
.attach_fd_with_loop_info(backing_file_fd, self.info.clone())?;
if self.direct_io() {
self.device.set_direct_io(self.direct_io())?;
}
Ok(())
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-network"
description = "Networking services for the krata hypervisor."
description = "Networking services for the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -16,7 +16,7 @@ clap = { workspace = true }
env_logger = { workspace = true }
etherparse = { workspace = true }
futures = { workspace = true }
krata = { path = "../krata", version = "^0.0.10" }
krata = { path = "../krata", version = "^0.0.12" }
krata-advmac = { workspace = true }
libc = { workspace = true }
log = { workspace = true }

View File

@ -1,6 +1,6 @@
[package]
name = "krata-oci"
description = "OCI services for the krata hypervisor."
description = "OCI services for the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true

View File

@ -47,7 +47,7 @@ impl Default for ImageName {
}
impl ImageName {
pub const DOCKER_HUB_MIRROR: &'static str = "registry.docker.io";
pub const DOCKER_HUB_MIRROR: &'static str = "mirror.gcr.io";
pub const DEFAULT_IMAGE_TAG: &'static str = "latest";
pub fn parse(name: &str) -> Result<Self> {

View File

@ -1,6 +1,6 @@
[package]
name = "krata-runtime"
description = "Runtime for running guests on the krata hypervisor."
description = "Runtime for running guests on the krata isolation engine"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -12,18 +12,22 @@ resolver = "2"
anyhow = { workspace = true }
backhand = { workspace = true }
ipnetwork = { workspace = true }
krata = { path = "../krata", version = "^0.0.10" }
krata = { path = "../krata", version = "^0.0.12" }
krata-advmac = { workspace = true }
krata-oci = { path = "../oci", version = "^0.0.10" }
krata-oci = { path = "../oci", version = "^0.0.12" }
log = { workspace = true }
loopdev-3 = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
uuid = { workspace = true }
krata-xenclient = { path = "../xen/xenclient", version = "^0.0.10" }
krata-xenevtchn = { path = "../xen/xenevtchn", version = "^0.0.10" }
krata-xengnt = { path = "../xen/xengnt", version = "^0.0.10" }
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.10" }
krata-loopdev = { path = "../loopdev", version = "^0.0.12" }
krata-xencall = { path = "../xen/xencall", version = "^0.0.12" }
krata-xenclient = { path = "../xen/xenclient", version = "^0.0.12" }
krata-xenevtchn = { path = "../xen/xenevtchn", version = "^0.0.12" }
krata-xengnt = { path = "../xen/xengnt", version = "^0.0.12" }
krata-xenplatform = { path = "../xen/xenplatform", version = "^0.0.12" }
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.12" }
walkdir = { workspace = true }
indexmap = { workspace = true }
[lib]
name = "kratart"

View File

@ -1,8 +1,8 @@
use std::{sync::Arc, time::Duration};
use anyhow::{anyhow, Result};
use krataloopdev::{LoopControl, LoopDevice};
use log::debug;
use loopdev::{LoopControl, LoopDevice};
use tokio::time::sleep;
use xenclient::BlockDeviceRef;

View File

@ -1,5 +1,6 @@
use anyhow::Result;
use backhand::{FilesystemWriter, NodeHeader};
use backhand::compression::Compressor;
use backhand::{FilesystemCompressor, FilesystemWriter, NodeHeader};
use krata::launchcfg::LaunchInfo;
use krataoci::packer::OciPackedImage;
use log::trace;
@ -8,14 +9,14 @@ use std::fs::File;
use std::path::PathBuf;
use uuid::Uuid;
pub struct ConfigBlock<'a> {
pub image: &'a OciPackedImage,
pub struct ConfigBlock {
pub image: OciPackedImage,
pub file: PathBuf,
pub dir: PathBuf,
}
impl ConfigBlock<'_> {
pub fn new<'a>(uuid: &Uuid, image: &'a OciPackedImage) -> Result<ConfigBlock<'a>> {
impl ConfigBlock {
pub fn new(uuid: &Uuid, image: OciPackedImage) -> Result<ConfigBlock> {
let mut dir = std::env::temp_dir().clone();
dir.push(format!("krata-cfg-{}", uuid));
fs::create_dir_all(&dir)?;
@ -29,6 +30,7 @@ impl ConfigBlock<'_> {
let config = self.image.config.raw();
let launch = serde_json::to_string(launch_config)?;
let mut writer = FilesystemWriter::default();
writer.set_compressor(FilesystemCompressor::new(Compressor::Gzip, None)?);
writer.push_dir(
"/image",
NodeHeader {

View File

@ -375,7 +375,10 @@ impl KrataChannelBackendProcessor {
};
ring_ref = self.use_reserved_ref.unwrap_or(ring_ref);
debug!(
"channel backend for domain {} channel {}: ring-ref={} port={}",
self.domid, self.id, ring_ref, port,
);
break (ring_ref, port);
}
}
@ -389,14 +392,24 @@ impl KrataChannelBackendProcessor {
self.store
.write_string(format!("{}/state", self.backend), "4")
.await?;
let memory = self.gnttab.map_grant_refs(
vec![GrantRef {
domid: self.domid,
reference: ring_ref as u32,
}],
true,
true,
)?;
let memory = self
.gnttab
.map_grant_refs(
vec![GrantRef {
domid: self.domid,
reference: ring_ref as u32,
}],
true,
true,
)
.map_err(|e| {
anyhow!(
"failed to map grant ref {} for domid {}: {}",
ring_ref,
self.domid,
e
)
})?;
let mut channel = self.evtchn.bind(self.domid, port).await?;
unsafe {
let buffer = self.read_output_buffer(channel.local_port, &memory).await?;

331
crates/runtime/src/ip.rs Normal file
View File

@ -0,0 +1,331 @@
use std::{
collections::HashMap,
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
sync::Arc,
};
use anyhow::{anyhow, Result};
use ipnetwork::{Ipv4Network, Ipv6Network};
use log::error;
use tokio::sync::RwLock;
use uuid::Uuid;
use xenstore::{XsdClient, XsdInterface};
#[derive(Default, Clone)]
pub struct IpVendorState {
pub ipv4: HashMap<Ipv4Addr, Uuid>,
pub ipv6: HashMap<Ipv6Addr, Uuid>,
pub pending_ipv4: HashMap<Ipv4Addr, Uuid>,
pub pending_ipv6: HashMap<Ipv6Addr, Uuid>,
}
#[derive(Clone)]
pub struct IpVendor {
store: XsdClient,
host_uuid: Uuid,
ipv4_network: Ipv4Network,
ipv6_network: Ipv6Network,
gateway_ipv4: Ipv4Addr,
gateway_ipv6: Ipv6Addr,
state: Arc<RwLock<IpVendorState>>,
}
pub struct IpAssignment {
vendor: IpVendor,
pub uuid: Uuid,
pub ipv4: Ipv4Addr,
pub ipv6: Ipv6Addr,
pub ipv4_prefix: u8,
pub ipv6_prefix: u8,
pub gateway_ipv4: Ipv4Addr,
pub gateway_ipv6: Ipv6Addr,
pub committed: bool,
}
impl IpAssignment {
pub async fn commit(&mut self) -> Result<()> {
self.vendor.commit(self).await?;
self.committed = true;
Ok(())
}
}
impl Drop for IpAssignment {
fn drop(&mut self) {
if !self.committed {
let ipv4 = self.ipv4;
let ipv6 = self.ipv6;
let uuid = self.uuid;
let vendor = self.vendor.clone();
tokio::task::spawn(async move {
let _ = vendor.recall_raw(ipv4, ipv6, uuid, true).await;
});
}
}
}
impl IpVendor {
pub async fn new(
store: XsdClient,
host_uuid: Uuid,
ipv4_network: Ipv4Network,
ipv6_network: Ipv6Network,
) -> Result<Self> {
let mut state = IpVendor::fetch_stored_state(&store).await?;
let (gateway_ipv4, gateway_ipv6) =
IpVendor::allocate_ipset(&mut state, host_uuid, ipv4_network, ipv6_network)?;
let vend = IpVendor {
store,
host_uuid,
ipv4_network,
ipv6_network,
gateway_ipv4,
gateway_ipv6,
state: Arc::new(RwLock::new(state)),
};
Ok(vend)
}
async fn fetch_stored_state(store: &XsdClient) -> Result<IpVendorState> {
let mut state = IpVendorState::default();
for domid_candidate in store.list("/local/domain").await? {
let dom_path = format!("/local/domain/{}", domid_candidate);
let Some(uuid) = store
.read_string(format!("{}/krata/uuid", dom_path))
.await?
.and_then(|x| Uuid::from_str(&x).ok())
else {
continue;
};
let assigned_ipv4 = store
.read_string(format!("{}/krata/network/guest/ipv4", dom_path))
.await?
.and_then(|x| Ipv4Network::from_str(&x).ok());
let assigned_ipv6 = store
.read_string(format!("{}/krata/network/guest/ipv6", dom_path))
.await?
.and_then(|x| Ipv6Network::from_str(&x).ok());
if let Some(existing_ipv4) = assigned_ipv4 {
if let Some(previous) = state.ipv4.insert(existing_ipv4.ip(), uuid) {
error!("ipv4 conflict detected: guest {} owned {} but {} also claimed to own it, giving it to {}", previous, existing_ipv4.ip(), uuid, uuid);
}
}
if let Some(existing_ipv6) = assigned_ipv6 {
if let Some(previous) = state.ipv6.insert(existing_ipv6.ip(), uuid) {
error!("ipv6 conflict detected: guest {} owned {} but {} also claimed to own it, giving it to {}", previous, existing_ipv6.ip(), uuid, uuid);
}
}
}
Ok(state)
}
fn allocate_ipset(
state: &mut IpVendorState,
uuid: Uuid,
ipv4_network: Ipv4Network,
ipv6_network: Ipv6Network,
) -> Result<(Ipv4Addr, Ipv6Addr)> {
let mut found_ipv4: Option<Ipv4Addr> = None;
for ip in ipv4_network.iter() {
if ip.is_loopback() || ip.is_multicast() || ip.is_broadcast() {
continue;
}
if !ip.is_private() {
continue;
}
let last = ip.octets()[3];
if last == 0 || last > 250 {
continue;
}
if state.ipv4.contains_key(&ip) {
continue;
}
found_ipv4 = Some(ip);
break;
}
let mut found_ipv6: Option<Ipv6Addr> = None;
for ip in ipv6_network.iter() {
if ip.is_loopback() || ip.is_multicast() {
continue;
}
if state.ipv6.contains_key(&ip) {
continue;
}
found_ipv6 = Some(ip);
break;
}
let Some(ipv4) = found_ipv4 else {
return Err(anyhow!(
"unable to allocate ipv4 address, assigned network is exhausted"
));
};
let Some(ipv6) = found_ipv6 else {
return Err(anyhow!(
"unable to allocate ipv6 address, assigned network is exhausted"
));
};
state.ipv4.insert(ipv4, uuid);
state.ipv6.insert(ipv6, uuid);
Ok((ipv4, ipv6))
}
pub async fn assign(&self, uuid: Uuid) -> Result<IpAssignment> {
let mut state = self.state.write().await;
let (ipv4, ipv6) =
IpVendor::allocate_ipset(&mut state, uuid, self.ipv4_network, self.ipv6_network)?;
state.pending_ipv4.insert(ipv4, uuid);
state.pending_ipv6.insert(ipv6, uuid);
Ok(IpAssignment {
vendor: self.clone(),
uuid,
ipv4,
ipv6,
ipv4_prefix: self.ipv4_network.prefix(),
ipv6_prefix: self.ipv6_network.prefix(),
gateway_ipv4: self.gateway_ipv4,
gateway_ipv6: self.gateway_ipv6,
committed: false,
})
}
pub async fn commit(&self, assignment: &IpAssignment) -> Result<()> {
let mut state = self.state.write().await;
if state.pending_ipv4.remove(&assignment.ipv4) != Some(assignment.uuid) {
return Err(anyhow!("matching pending ipv4 assignment was not found"));
}
if state.pending_ipv6.remove(&assignment.ipv6) != Some(assignment.uuid) {
return Err(anyhow!("matching pending ipv6 assignment was not found"));
}
Ok(())
}
async fn recall_raw(
&self,
ipv4: Ipv4Addr,
ipv6: Ipv6Addr,
uuid: Uuid,
pending: bool,
) -> Result<()> {
let mut state = self.state.write().await;
if pending {
if state.pending_ipv4.remove(&ipv4) != Some(uuid) {
return Err(anyhow!("matching pending ipv4 assignment was not found"));
}
if state.pending_ipv6.remove(&ipv6) != Some(uuid) {
return Err(anyhow!("matching pending ipv6 assignment was not found"));
}
}
if state.ipv4.remove(&ipv4) != Some(uuid) {
return Err(anyhow!("matching allocated ipv4 assignment was not found"));
}
if state.ipv6.remove(&ipv6) != Some(uuid) {
return Err(anyhow!("matching allocated ipv6 assignment was not found"));
}
Ok(())
}
pub async fn recall(&self, assignment: &IpAssignment) -> Result<()> {
self.recall_raw(assignment.ipv4, assignment.ipv6, assignment.uuid, false)
.await?;
Ok(())
}
pub async fn reload(&self) -> Result<()> {
let mut state = self.state.write().await;
let mut intermediate = IpVendor::fetch_stored_state(&self.store).await?;
intermediate.ipv4.insert(self.gateway_ipv4, self.host_uuid);
intermediate.ipv6.insert(self.gateway_ipv6, self.host_uuid);
for (ipv4, uuid) in &state.pending_ipv4 {
if let Some(previous) = intermediate.ipv4.insert(*ipv4, *uuid) {
error!("ipv4 conflict detected: guest {} owned (pending) {} but {} also claimed to own it, giving it to {}", previous, ipv4, uuid, uuid);
}
intermediate.pending_ipv4.insert(*ipv4, *uuid);
}
for (ipv6, uuid) in &state.pending_ipv6 {
if let Some(previous) = intermediate.ipv6.insert(*ipv6, *uuid) {
error!("ipv6 conflict detected: guest {} owned (pending) {} but {} also claimed to own it, giving it to {}", previous, ipv6, uuid, uuid);
}
intermediate.pending_ipv6.insert(*ipv6, *uuid);
}
*state = intermediate;
Ok(())
}
pub async fn read_domain_assignment(
&self,
uuid: Uuid,
domid: u32,
) -> Result<Option<IpAssignment>> {
let dom_path = format!("/local/domain/{}", domid);
let Some(guest_ipv4) = self
.store
.read_string(format!("{}/krata/network/guest/ipv4", dom_path))
.await?
else {
return Ok(None);
};
let Some(guest_ipv6) = self
.store
.read_string(format!("{}/krata/network/guest/ipv6", dom_path))
.await?
else {
return Ok(None);
};
let Some(gateway_ipv4) = self
.store
.read_string(format!("{}/krata/network/gateway/ipv4", dom_path))
.await?
else {
return Ok(None);
};
let Some(gateway_ipv6) = self
.store
.read_string(format!("{}/krata/network/gateway/ipv6", dom_path))
.await?
else {
return Ok(None);
};
let Some(guest_ipv4) = Ipv4Network::from_str(&guest_ipv4).ok() else {
return Ok(None);
};
let Some(guest_ipv6) = Ipv6Network::from_str(&guest_ipv6).ok() else {
return Ok(None);
};
let Some(gateway_ipv4) = Ipv4Network::from_str(&gateway_ipv4).ok() else {
return Ok(None);
};
let Some(gateway_ipv6) = Ipv6Network::from_str(&gateway_ipv6).ok() else {
return Ok(None);
};
Ok(Some(IpAssignment {
vendor: self.clone(),
uuid,
ipv4: guest_ipv4.ip(),
ipv4_prefix: guest_ipv4.prefix(),
ipv6: guest_ipv6.ip(),
ipv6_prefix: guest_ipv6.prefix(),
gateway_ipv4: gateway_ipv4.ip(),
gateway_ipv6: gateway_ipv6.ip(),
committed: true,
}))
}
pub async fn read(&self) -> Result<IpVendorState> {
Ok(self.state.read().await.clone())
}
}

View File

@ -1,11 +1,12 @@
use std::collections::HashMap;
use std::net::{IpAddr, Ipv6Addr};
use std::fs;
use std::net::IpAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::{fs, net::Ipv4Addr, str::FromStr};
use advmac::MacAddr6;
use anyhow::{anyhow, Result};
use ipnetwork::{IpNetwork, Ipv4Network};
use ipnetwork::IpNetwork;
use krata::launchcfg::{
LaunchInfo, LaunchNetwork, LaunchNetworkIpv4, LaunchNetworkIpv6, LaunchNetworkResolver,
LaunchPackedFormat, LaunchRoot,
@ -14,13 +15,17 @@ use krataoci::packer::OciPackedImage;
use tokio::sync::Semaphore;
use uuid::Uuid;
use xenclient::{DomainChannel, DomainConfig, DomainDisk, DomainNetworkInterface};
use xenstore::XsdInterface;
use xenplatform::domain::BaseDomainConfig;
use crate::cfgblk::ConfigBlock;
use crate::RuntimeContext;
use super::{GuestInfo, GuestState};
pub use xenclient::{
pci::PciBdf, DomainPciDevice as PciDevice, DomainPciRdmReservePolicy as PciRdmReservePolicy,
};
pub struct GuestLaunchRequest {
pub format: LaunchPackedFormat,
pub kernel: Vec<u8>,
@ -31,8 +36,10 @@ pub struct GuestLaunchRequest {
pub mem: u64,
pub env: HashMap<String, String>,
pub run: Option<Vec<String>>,
pub pcis: Vec<PciDevice>,
pub debug: bool,
pub image: OciPackedImage,
pub addons_image: Option<PathBuf>,
}
pub struct GuestLauncher {
@ -59,13 +66,7 @@ impl GuestLauncher {
container_mac.set_multicast(false);
let _launch_permit = self.launch_semaphore.acquire().await?;
let guest_ipv4 = self.allocate_ipv4(context).await?;
let guest_ipv6 = container_mac.to_link_local_ipv6();
let gateway_ipv4 = "10.75.70.1";
let gateway_ipv6 = "fe80::1";
let ipv4_network_mask: u32 = 16;
let ipv6_network_mask: u32 = 10;
let mut ip = context.ipvendor.assign(uuid).await?;
let launch_config = LaunchInfo {
root: LaunchRoot {
format: request.format.clone(),
@ -80,12 +81,12 @@ impl GuestLauncher {
network: Some(LaunchNetwork {
link: "eth0".to_string(),
ipv4: LaunchNetworkIpv4 {
address: format!("{}/{}", guest_ipv4, ipv4_network_mask),
gateway: gateway_ipv4.to_string(),
address: format!("{}/{}", ip.ipv4, ip.ipv4_prefix),
gateway: ip.gateway_ipv4.to_string(),
},
ipv6: LaunchNetworkIpv6 {
address: format!("{}/{}", guest_ipv6, ipv6_network_mask),
gateway: gateway_ipv6.to_string(),
address: format!("{}/{}", ip.ipv6, ip.ipv6_prefix),
gateway: ip.gateway_ipv6.to_string(),
},
resolver: LaunchNetworkResolver {
nameservers: vec![
@ -100,8 +101,10 @@ impl GuestLauncher {
run: request.run,
};
let cfgblk = ConfigBlock::new(&uuid, &request.image)?;
cfgblk.build(&launch_config)?;
let cfgblk = ConfigBlock::new(&uuid, request.image.clone())?;
let cfgblk_file = cfgblk.file.clone();
let cfgblk_dir = cfgblk.dir.clone();
tokio::task::spawn_blocking(move || cfgblk.build(&launch_config)).await??;
let image_squashfs_path = request
.image
@ -109,47 +112,91 @@ impl GuestLauncher {
.to_str()
.ok_or_else(|| anyhow!("failed to convert image path to string"))?;
let cfgblk_dir_path = cfgblk
.dir
let cfgblk_dir_path = cfgblk_dir
.to_str()
.ok_or_else(|| anyhow!("failed to convert cfgblk directory path to string"))?;
let cfgblk_squashfs_path = cfgblk
.file
let cfgblk_squashfs_path = cfgblk_file
.to_str()
.ok_or_else(|| anyhow!("failed to convert cfgblk squashfs path to string"))?;
let addons_squashfs_path = request
.addons_image
.map(|x| x.to_str().map(|x| x.to_string()))
.map(|x| {
Some(x.ok_or_else(|| anyhow!("failed to convert addons squashfs path to string")))
})
.unwrap_or(None);
let addons_squashfs_path = if let Some(path) = addons_squashfs_path {
Some(path?)
} else {
None
};
let image_squashfs_loop = context.autoloop.loopify(image_squashfs_path)?;
let cfgblk_squashfs_loop = context.autoloop.loopify(cfgblk_squashfs_path)?;
let cmdline_options = [
if request.debug { "debug" } else { "quiet" },
"elevator=noop",
];
let addons_squashfs_loop = if let Some(ref addons_squashfs_path) = addons_squashfs_path {
Some(context.autoloop.loopify(addons_squashfs_path)?)
} else {
None
};
let mut cmdline_options = ["console=hvc0"].to_vec();
if !request.debug {
cmdline_options.push("quiet");
}
let cmdline = cmdline_options.join(" ");
let guest_mac_string = container_mac.to_string().replace('-', ":");
let gateway_mac_string = gateway_mac.to_string().replace('-', ":");
let mut disks = vec![
DomainDisk {
vdev: "xvda".to_string(),
block: image_squashfs_loop.clone(),
writable: false,
},
DomainDisk {
vdev: "xvdb".to_string(),
block: cfgblk_squashfs_loop.clone(),
writable: false,
},
];
if let Some(ref addons) = addons_squashfs_loop {
disks.push(DomainDisk {
vdev: "xvdc".to_string(),
block: addons.clone(),
writable: false,
});
}
let mut loops = vec![
format!("{}:{}:none", image_squashfs_loop.path, image_squashfs_path),
format!(
"{}:{}:{}",
cfgblk_squashfs_loop.path, cfgblk_squashfs_path, cfgblk_dir_path
),
];
if let Some(ref addons) = addons_squashfs_loop {
loops.push(format!(
"{}:{}:none",
addons.path,
addons_squashfs_path
.clone()
.ok_or_else(|| anyhow!("addons squashfs path missing"))?
));
}
let mut extra_keys = vec![
("krata/uuid".to_string(), uuid.to_string()),
(
"krata/loops".to_string(),
format!(
"{}:{}:none,{}:{}:{}",
&image_squashfs_loop.path,
image_squashfs_path,
&cfgblk_squashfs_loop.path,
cfgblk_squashfs_path,
cfgblk_dir_path,
),
),
("krata/loops".to_string(), loops.join(",")),
(
"krata/network/guest/ipv4".to_string(),
format!("{}/{}", guest_ipv4, ipv4_network_mask),
format!("{}/{}", ip.ipv4, ip.ipv4_prefix),
),
(
"krata/network/guest/ipv6".to_string(),
format!("{}/{}", guest_ipv6, ipv6_network_mask),
format!("{}/{}", ip.ipv6, ip.ipv6_prefix),
),
(
"krata/network/guest/mac".to_string(),
@ -157,11 +204,11 @@ impl GuestLauncher {
),
(
"krata/network/gateway/ipv4".to_string(),
format!("{}/{}", gateway_ipv4, ipv4_network_mask),
format!("{}/{}", ip.gateway_ipv4, ip.ipv4_prefix),
),
(
"krata/network/gateway/ipv6".to_string(),
format!("{}/{}", gateway_ipv6, ipv6_network_mask),
format!("{}/{}", ip.gateway_ipv6, ip.ipv6_prefix),
),
(
"krata/network/gateway/mac".to_string(),
@ -174,26 +221,20 @@ impl GuestLauncher {
}
let config = DomainConfig {
base: BaseDomainConfig {
max_vcpus: request.vcpus,
mem_mb: request.mem,
kernel: request.kernel,
initrd: request.initrd,
cmdline,
uuid,
owner_domid: 0,
enable_iommu: !request.pcis.is_empty(),
},
backend_domid: 0,
name: xen_name,
max_vcpus: request.vcpus,
mem_mb: request.mem,
kernel: request.kernel,
initrd: request.initrd,
cmdline,
use_console_backend: Some("krata-console".to_string()),
disks: vec![
DomainDisk {
vdev: "xvda".to_string(),
block: image_squashfs_loop.clone(),
writable: false,
},
DomainDisk {
vdev: "xvdb".to_string(),
block: cfgblk_squashfs_loop.clone(),
writable: false,
},
],
swap_console_backend: Some("krata-console".to_string()),
disks,
channels: vec![DomainChannel {
typ: "krata-channel".to_string(),
initialized: false,
@ -204,78 +245,41 @@ impl GuestLauncher {
bridge: None,
script: None,
}],
pcis: request.pcis.clone(),
filesystems: vec![],
event_channels: vec![],
extra_keys,
extra_rw_paths: vec!["krata/guest".to_string()],
};
match context.xen.create(&config).await {
Ok(created) => Ok(GuestInfo {
name: request.name.as_ref().map(|x| x.to_string()),
uuid,
domid: created.domid,
image: request.image.digest,
loops: vec![],
guest_ipv4: Some(IpNetwork::new(
IpAddr::V4(guest_ipv4),
ipv4_network_mask as u8,
)?),
guest_ipv6: Some(IpNetwork::new(
IpAddr::V6(guest_ipv6),
ipv6_network_mask as u8,
)?),
guest_mac: Some(guest_mac_string.clone()),
gateway_ipv4: Some(IpNetwork::new(
IpAddr::V4(Ipv4Addr::from_str(gateway_ipv4)?),
ipv4_network_mask as u8,
)?),
gateway_ipv6: Some(IpNetwork::new(
IpAddr::V6(Ipv6Addr::from_str(gateway_ipv6)?),
ipv6_network_mask as u8,
)?),
gateway_mac: Some(gateway_mac_string.clone()),
state: GuestState { exit_code: None },
}),
Ok(created) => {
ip.commit().await?;
Ok(GuestInfo {
name: request.name.as_ref().map(|x| x.to_string()),
uuid,
domid: created.domid,
image: request.image.digest,
loops: vec![],
guest_ipv4: Some(IpNetwork::new(IpAddr::V4(ip.ipv4), ip.ipv4_prefix)?),
guest_ipv6: Some(IpNetwork::new(IpAddr::V6(ip.ipv6), ip.ipv6_prefix)?),
guest_mac: Some(guest_mac_string.clone()),
gateway_ipv4: Some(IpNetwork::new(
IpAddr::V4(ip.gateway_ipv4),
ip.ipv4_prefix,
)?),
gateway_ipv6: Some(IpNetwork::new(
IpAddr::V6(ip.gateway_ipv6),
ip.ipv6_prefix,
)?),
gateway_mac: Some(gateway_mac_string.clone()),
state: GuestState { exit_code: None },
})
}
Err(error) => {
let _ = context.autoloop.unloop(&image_squashfs_loop.path).await;
let _ = context.autoloop.unloop(&cfgblk_squashfs_loop.path).await;
let _ = fs::remove_dir(&cfgblk.dir);
let _ = fs::remove_dir(&cfgblk_dir);
Err(error.into())
}
}
}
async fn allocate_ipv4(&self, context: &RuntimeContext) -> Result<Ipv4Addr> {
let network = Ipv4Network::new(Ipv4Addr::new(10, 75, 80, 0), 24)?;
let mut used: Vec<Ipv4Addr> = vec![];
for domid_candidate in context.xen.store.list("/local/domain").await? {
let dom_path = format!("/local/domain/{}", domid_candidate);
let ip_path = format!("{}/krata/network/guest/ipv4", dom_path);
let existing_ip = context.xen.store.read_string(&ip_path).await?;
if let Some(existing_ip) = existing_ip {
let ipv4_network = Ipv4Network::from_str(&existing_ip)?;
used.push(ipv4_network.ip());
}
}
let mut found: Option<Ipv4Addr> = None;
for ip in network.iter() {
let last = ip.octets()[3];
if last == 0 || last == 255 {
continue;
}
if !used.contains(&ip) {
found = Some(ip);
break;
}
}
if found.is_none() {
return Err(anyhow!(
"unable to find ipv4 to allocate to guest, ipv4 addresses are exhausted"
));
}
Ok(found.unwrap())
}
}

View File

@ -1,8 +1,10 @@
use std::{fs, path::PathBuf, str::FromStr, sync::Arc};
use std::{fs, net::Ipv4Addr, path::PathBuf, str::FromStr, sync::Arc};
use anyhow::{anyhow, Result};
use ipnetwork::IpNetwork;
use loopdev::LoopControl;
use ip::IpVendor;
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use krataloopdev::LoopControl;
use log::error;
use tokio::sync::Semaphore;
use uuid::Uuid;
use xenclient::XenClient;
@ -11,12 +13,21 @@ use xenstore::{XsdClient, XsdInterface};
use self::{
autoloop::AutoLoop,
launch::{GuestLaunchRequest, GuestLauncher},
power::PowerManagementContext,
};
pub mod autoloop;
pub mod cfgblk;
pub mod channel;
pub mod ip;
pub mod launch;
pub mod power;
#[cfg(target_arch = "x86_64")]
type RuntimePlatform = xenplatform::x86pv::X86PvPlatform;
#[cfg(not(target_arch = "x86_64"))]
type RuntimePlatform = xenplatform::unsupported::UnsupportedPlatform;
pub struct GuestLoopInfo {
pub device: String,
@ -46,15 +57,21 @@ pub struct GuestInfo {
#[derive(Clone)]
pub struct RuntimeContext {
pub autoloop: AutoLoop,
pub xen: XenClient,
pub xen: XenClient<RuntimePlatform>,
pub ipvendor: IpVendor,
}
impl RuntimeContext {
pub async fn new() -> Result<Self> {
let xen = XenClient::open(0).await?;
pub async fn new(host_uuid: Uuid) -> Result<Self> {
let xen = XenClient::new(0, RuntimePlatform::new()).await?;
let ipv4_network = Ipv4Network::new(Ipv4Addr::new(10, 75, 80, 0), 24)?;
let ipv6_network = Ipv6Network::from_str("fdd4:1476:6c7e::/48")?;
let ipvend =
IpVendor::new(xen.store.clone(), host_uuid, ipv4_network, ipv6_network).await?;
Ok(RuntimeContext {
autoloop: AutoLoop::new(LoopControl::open()?),
xen,
ipvendor: ipvend,
})
}
@ -217,16 +234,18 @@ impl RuntimeContext {
#[derive(Clone)]
pub struct Runtime {
host_uuid: Uuid,
context: RuntimeContext,
launch_semaphore: Arc<Semaphore>,
}
impl Runtime {
pub async fn new() -> Result<Self> {
let context = RuntimeContext::new().await?;
pub async fn new(host_uuid: Uuid) -> Result<Self> {
let context = RuntimeContext::new(host_uuid).await?;
Ok(Self {
host_uuid,
context,
launch_semaphore: Arc::new(Semaphore::new(1)),
launch_semaphore: Arc::new(Semaphore::new(10)),
})
}
@ -260,6 +279,11 @@ impl Runtime {
return Err(anyhow!("unable to find krata uuid based on the domain",));
}
let uuid = Uuid::parse_str(&uuid)?;
let ip = self
.context
.ipvendor
.read_domain_assignment(uuid, domid)
.await?;
let loops = store
.read_string(format!("{}/krata/loops", dom_path).as_str())
.await?;
@ -279,6 +303,16 @@ impl Runtime {
}
}
}
if let Some(ip) = ip {
if let Err(error) = self.context.ipvendor.recall(&ip).await {
error!(
"failed to recall ip assignment for guest {}: {}",
uuid, error
);
}
}
Ok(uuid)
}
@ -287,6 +321,11 @@ impl Runtime {
}
pub async fn dupe(&self) -> Result<Runtime> {
Runtime::new().await
Runtime::new(self.host_uuid).await
}
pub async fn power_management_context(&self) -> Result<PowerManagementContext> {
let context = RuntimeContext::new(self.host_uuid).await?;
Ok(PowerManagementContext { context })
}
}

167
crates/runtime/src/power.rs Normal file
View File

@ -0,0 +1,167 @@
use anyhow::Result;
use indexmap::IndexMap;
use xencall::sys::{CpuId, SysctlCputopo};
use crate::RuntimeContext;
#[derive(Clone)]
pub struct PowerManagementContext {
pub context: RuntimeContext,
}
#[derive(Clone, Copy, Debug)]
pub enum CpuClass {
Standard,
Performance,
Efficiency,
}
#[derive(Clone, Copy, Debug)]
pub struct CpuTopologyInfo {
pub core: u32,
pub socket: u32,
pub node: u32,
pub thread: u32,
pub class: CpuClass,
}
fn labelled_topo(input: &[SysctlCputopo]) -> Vec<CpuTopologyInfo> {
let mut cores: IndexMap<(u32, u32, u32), Vec<CpuTopologyInfo>> = IndexMap::new();
let mut pe_cores = false;
let mut last: Option<SysctlCputopo> = None;
for item in input {
if cores.is_empty() {
cores.insert(
(item.core, item.socket, item.node),
vec![CpuTopologyInfo {
core: item.core,
socket: item.socket,
thread: 0,
node: item.node,
class: CpuClass::Standard,
}],
);
last = Some(*item);
continue;
}
if last
.map(|last| {
item.core
.checked_sub(last.core)
.map(|diff| diff >= 3)
.unwrap_or(false)
})
.unwrap_or(false)
{
// detect if performance cores seem to be kicking in.
if let Some(last) = last {
if let Some(list) = cores.get_mut(&(last.core, last.socket, last.node)) {
for other in list {
other.class = CpuClass::Performance;
}
}
}
let list = cores
.entry((item.core, item.socket, item.node))
.or_default();
for old in &mut *list {
old.class = CpuClass::Performance;
}
list.push(CpuTopologyInfo {
core: item.core,
socket: item.socket,
thread: 0,
node: item.node,
class: CpuClass::Performance,
});
pe_cores = true;
} else if pe_cores && last.map(|last| item.core == last.core + 1).unwrap_or(false) {
// detect efficiency cores if P/E cores are in use.
if let Some(last) = last {
if let Some(list) = cores.get_mut(&(last.core, last.socket, last.node)) {
for other in list {
other.class = CpuClass::Efficiency;
}
}
}
let list = cores
.entry((item.core, item.socket, item.node))
.or_default();
list.push(CpuTopologyInfo {
core: item.core,
socket: item.socket,
thread: 0,
node: item.node,
class: CpuClass::Efficiency,
});
} else {
let list = cores
.entry((item.core, item.socket, item.node))
.or_default();
if list.is_empty() {
list.push(CpuTopologyInfo {
core: item.core,
socket: item.socket,
thread: 0,
node: item.node,
class: CpuClass::Standard,
});
} else {
list.push(CpuTopologyInfo {
core: item.core,
socket: item.socket,
thread: 0,
node: item.node,
class: list
.first()
.map(|first| first.class)
.unwrap_or(CpuClass::Standard),
});
}
}
last = Some(*item);
}
for threads in cores.values_mut() {
for (index, thread) in threads.iter_mut().enumerate() {
thread.thread = index as u32;
}
}
cores.into_values().flatten().collect::<Vec<_>>()
}
impl PowerManagementContext {
/// Get the CPU topology, with SMT awareness.
/// Also translates Intel p-core/e-core nonsense: non-sequential core identifiers
/// are treated as p-cores, while e-cores behave as standard cores.
/// If there is a p-core/e-core split, then CPU class will be defined as
/// `CpuClass::Performance` or `CpuClass::Efficiency`, else `CpuClass::Standard`.
pub async fn cpu_topology(&self) -> Result<Vec<CpuTopologyInfo>> {
let xentopo = self.context.xen.call.cpu_topology().await?;
let logicaltopo = labelled_topo(&xentopo);
Ok(logicaltopo)
}
/// Enable or disable SMT awareness in the scheduler.
pub async fn set_smt_policy(&self, enable: bool) -> Result<()> {
self.context
.xen
.call
.set_turbo_mode(CpuId::All, enable)
.await?;
Ok(())
}
/// Set scheduler policy name.
pub async fn set_scheduler_policy(&self, policy: impl AsRef<str>) -> Result<()> {
self.context
.xen
.call
.set_cpufreq_gov(CpuId::All, policy)
.await?;
Ok(())
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-xencall"
description = "An implementation of direct interfacing to xen privcmd for krata."
description = "An implementation of direct interfacing to Xen privcmd for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -35,5 +35,5 @@ name = "xencall-version-capabilities"
path = "examples/version_capabilities.rs"
[[example]]
name = "xencall-vcpu-context"
path = "examples/vcpu_context.rs"
name = "xencall-power-management"
path = "examples/power_management.rs"

View File

@ -0,0 +1,19 @@
use xencall::error::Result;
use xencall::sys::CpuId;
use xencall::XenCall;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
let call = XenCall::open(0)?;
let physinfo = call.phys_info().await?;
println!("{:?}", physinfo);
let topology = call.cpu_topology().await?;
println!("{:?}", topology);
call.set_cpufreq_gov(CpuId::All, "performance").await?;
call.set_cpufreq_gov(CpuId::Single(0), "performance")
.await?;
call.set_turbo_mode(CpuId::All, true).await?;
Ok(())
}

View File

@ -0,0 +1,19 @@
use xencall::error::Result;
use xencall::sys::CpuId;
use xencall::XenCall;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
let call = XenCall::open(0)?;
let physinfo = call.phys_info().await?;
println!("{:?}", physinfo);
let topology = call.cpu_topology().await?;
println!("{:?}", topology);
call.set_cpufreq_gov(CpuId::All, "performance").await?;
call.set_cpufreq_gov(CpuId::Single(0), "performance")
.await?;
call.set_turbo_mode(CpuId::All, true).await?;
Ok(())
}

View File

@ -1,12 +0,0 @@
use xencall::error::Result;
use xencall::XenCall;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
let call = XenCall::open(0)?;
let context = call.get_vcpu_context(224, 0).await?;
println!("{:?}", context);
Ok(())
}

View File

@ -14,6 +14,8 @@ pub enum Error {
PopulatePhysmapFailed,
#[error("mmap batch failed: {0}")]
MmapBatchFailed(nix::errno::Errno),
#[error("specified value is too long")]
ValueTooLong,
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -3,28 +3,42 @@ pub mod sys;
use crate::error::{Error, Result};
use crate::sys::{
AddressSize, CreateDomain, DomCtl, DomCtlValue, DomCtlVcpuContext, EvtChnAllocUnbound,
GetDomainInfo, GetPageFrameInfo3, Hypercall, HypercallInit, MaxMem, MaxVcpus, MemoryMap,
MemoryReservation, MmapBatch, MmapResource, MmuExtOp, MultiCallEntry, VcpuGuestContext,
VcpuGuestContextAny, XenCapabilitiesInfo, HYPERVISOR_DOMCTL, HYPERVISOR_EVENT_CHANNEL_OP,
HYPERVISOR_MEMORY_OP, HYPERVISOR_MMUEXT_OP, HYPERVISOR_MULTICALL, HYPERVISOR_XEN_VERSION,
XENVER_CAPABILITIES, XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_DESTROYDOMAIN,
XEN_DOMCTL_GETDOMAININFO, XEN_DOMCTL_GETPAGEFRAMEINFO3, XEN_DOMCTL_GETVCPUCONTEXT,
XEN_DOMCTL_HYPERCALL_INIT, XEN_DOMCTL_MAX_MEM, XEN_DOMCTL_MAX_VCPUS, XEN_DOMCTL_PAUSEDOMAIN,
XEN_DOMCTL_SETVCPUCONTEXT, XEN_DOMCTL_SET_ADDRESS_SIZE, XEN_DOMCTL_UNPAUSEDOMAIN,
XEN_MEM_CLAIM_PAGES, XEN_MEM_MEMORY_MAP, XEN_MEM_POPULATE_PHYSMAP,
AddToPhysmap, AddressSize, AssignDevice, CreateDomain, DomCtl, DomCtlValue, DomCtlVcpuContext,
EvtChnAllocUnbound, GetDomainInfo, GetPageFrameInfo3, HvmContext, HvmParam, Hypercall,
HypercallInit, IoMemPermission, IoPortPermission, IrqPermission, MaxMem, MaxVcpus, MemoryMap,
MemoryReservation, MmapBatch, MmapResource, MmuExtOp, MultiCallEntry, PagingMempool,
PciAssignDevice, XenCapabilitiesInfo, DOMCTL_DEV_PCI, HYPERVISOR_DOMCTL,
HYPERVISOR_EVENT_CHANNEL_OP, HYPERVISOR_HVM_OP, HYPERVISOR_MEMORY_OP, HYPERVISOR_MMUEXT_OP,
HYPERVISOR_MULTICALL, HYPERVISOR_XEN_VERSION, XENVER_CAPABILITIES, XEN_DOMCTL_ASSIGN_DEVICE,
XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_DESTROYDOMAIN, XEN_DOMCTL_GETDOMAININFO,
XEN_DOMCTL_GETHVMCONTEXT, XEN_DOMCTL_GETPAGEFRAMEINFO3, XEN_DOMCTL_HYPERCALL_INIT,
XEN_DOMCTL_IOMEM_PERMISSION, XEN_DOMCTL_IOPORT_PERMISSION, XEN_DOMCTL_IRQ_PERMISSION,
XEN_DOMCTL_MAX_MEM, XEN_DOMCTL_MAX_VCPUS, XEN_DOMCTL_PAUSEDOMAIN, XEN_DOMCTL_SETHVMCONTEXT,
XEN_DOMCTL_SETVCPUCONTEXT, XEN_DOMCTL_SET_ADDRESS_SIZE, XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE,
XEN_DOMCTL_UNPAUSEDOMAIN, XEN_MEM_ADD_TO_PHYSMAP, XEN_MEM_CLAIM_PAGES, XEN_MEM_MEMORY_MAP,
XEN_MEM_POPULATE_PHYSMAP,
};
use libc::{c_int, mmap, usleep, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
use libc::{c_int, mmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
use log::trace;
use nix::errno::Errno;
use std::ffi::{c_long, c_uint, c_ulong, c_void};
use std::sync::Arc;
use sys::{XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION};
use std::time::Duration;
use sys::{
CpuId, E820Entry, ForeignMemoryMap, PhysdevMapPirq, Sysctl, SysctlCputopo, SysctlCputopoinfo,
SysctlPhysinfo, SysctlPmOp, SysctlPmOpValue, SysctlSetCpuFreqGov, SysctlValue,
VcpuGuestContextAny, HYPERVISOR_PHYSDEV_OP, HYPERVISOR_SYSCTL, PHYSDEVOP_MAP_PIRQ,
XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION, XEN_MEM_SET_MEMORY_MAP,
XEN_SYSCTL_CPUTOPOINFO, XEN_SYSCTL_MAX_INTERFACE_VERSION, XEN_SYSCTL_MIN_INTERFACE_VERSION,
XEN_SYSCTL_PHYSINFO, XEN_SYSCTL_PM_OP, XEN_SYSCTL_PM_OP_DISABLE_TURBO,
XEN_SYSCTL_PM_OP_ENABLE_TURBO,
};
use tokio::sync::Semaphore;
use tokio::time::sleep;
use std::fs::{File, OpenOptions};
use std::os::fd::AsRawFd;
use std::ptr::addr_of_mut;
use std::ptr::{addr_of_mut, null_mut};
use std::slice;
#[derive(Clone)]
@ -32,6 +46,7 @@ pub struct XenCall {
pub handle: Arc<File>,
semaphore: Arc<Semaphore>,
domctl_interface_version: u32,
sysctl_interface_version: u32,
}
impl XenCall {
@ -42,10 +57,12 @@ impl XenCall {
.open("/dev/xen/privcmd")?;
let domctl_interface_version =
XenCall::detect_domctl_interface_version(&handle, current_domid)?;
let sysctl_interface_version = XenCall::detect_sysctl_interface_version(&handle)?;
Ok(XenCall {
handle: Arc::new(handle),
semaphore: Arc::new(Semaphore::new(1)),
domctl_interface_version,
sysctl_interface_version,
})
}
@ -73,6 +90,32 @@ impl XenCall {
Err(Error::XenVersionUnsupported)
}
fn detect_sysctl_interface_version(handle: &File) -> Result<u32> {
for version in XEN_SYSCTL_MIN_INTERFACE_VERSION..XEN_SYSCTL_MAX_INTERFACE_VERSION + 1 {
let mut sysctl = Sysctl {
cmd: XEN_SYSCTL_CPUTOPOINFO,
interface_version: version,
value: SysctlValue {
cputopoinfo: SysctlCputopoinfo {
num_cpus: 0,
handle: 0,
},
},
};
unsafe {
let mut call = Hypercall {
op: HYPERVISOR_SYSCTL,
arg: [addr_of_mut!(sysctl) as u64, 0, 0, 0, 0],
};
let result = sys::hypercall(handle.as_raw_fd(), &mut call).unwrap_or(-1);
if result == 0 {
return Ok(version);
}
}
}
Err(Error::XenVersionUnsupported)
}
pub async fn mmap(&self, addr: u64, len: u64) -> Option<u64> {
let _permit = self.semaphore.acquire().await.ok()?;
trace!(
@ -227,8 +270,8 @@ impl XenCall {
num: num as u32,
domid: domid as u16,
addr,
mfns: mfns.as_mut_ptr(),
errors: errors.as_mut_ptr(),
mfns: mfns.as_mut_ptr() as u64,
errors: errors.as_mut_ptr() as u64,
};
let result = sys::mmapbatch(self.handle.as_raw_fd(), &mut batch);
@ -237,7 +280,7 @@ impl XenCall {
return Err(Error::MmapBatchFailed(errno))?;
}
usleep(100);
sleep(Duration::from_micros(100)).await;
let mut i: usize = 0;
let mut paged: usize = 0;
@ -252,8 +295,8 @@ impl XenCall {
num: 1,
domid: domid as u16,
addr: addr + ((i as u64) << 12),
mfns: mfns.as_mut_ptr().add(i),
errors: errors.as_mut_ptr().add(i),
mfns: mfns.as_mut_ptr().add(i) as u64,
errors: errors.as_mut_ptr().add(i) as u64,
};
loop {
@ -453,45 +496,19 @@ impl XenCall {
Ok(())
}
pub async fn get_vcpu_context(&self, domid: u32, vcpu: u32) -> Result<VcpuGuestContext> {
trace!(
"domctl fd={} get_vcpu_context domid={}",
self.handle.as_raw_fd(),
domid,
);
let mut wrapper = VcpuGuestContextAny {
value: VcpuGuestContext::default(),
};
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_GETVCPUCONTEXT,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
vcpu_context: DomCtlVcpuContext {
vcpu,
ctx: addr_of_mut!(wrapper) as c_ulong,
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(unsafe { wrapper.value })
}
pub async fn set_vcpu_context(
&self,
domid: u32,
vcpu: u32,
context: &VcpuGuestContext,
mut context: VcpuGuestContextAny,
) -> Result<()> {
trace!(
"domctl fd={} set_vcpu_context domid={} context={:?}",
self.handle.as_raw_fd(),
domid,
context,
unsafe { context.value }
);
let mut value = VcpuGuestContextAny { value: *context };
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_SETVCPUCONTEXT,
interface_version: self.domctl_interface_version,
@ -499,7 +516,7 @@ impl XenCall {
value: DomCtlValue {
vcpu_context: DomCtlVcpuContext {
vcpu,
ctx: addr_of_mut!(value) as c_ulong,
ctx: addr_of_mut!(context) as c_ulong,
},
},
};
@ -569,26 +586,48 @@ impl XenCall {
Ok(())
}
pub async fn get_memory_map(&self, size_of_entry: usize) -> Result<Vec<u8>> {
pub async fn get_memory_map(&self, max_entries: u32) -> Result<Vec<E820Entry>> {
let mut memory_map = MemoryMap {
count: 0,
count: max_entries,
buffer: 0,
};
let mut entries = vec![E820Entry::default(); max_entries as usize];
memory_map.buffer = entries.as_mut_ptr() as c_ulong;
self.hypercall2(
HYPERVISOR_MEMORY_OP,
XEN_MEM_MEMORY_MAP as c_ulong,
addr_of_mut!(memory_map) as c_ulong,
)
.await?;
entries.truncate(memory_map.count as usize);
Ok(entries)
}
pub async fn set_memory_map(
&self,
domid: u32,
entries: Vec<E820Entry>,
) -> Result<Vec<E820Entry>> {
trace!(
"fd={} set_memory_map domid={} entries={:?}",
self.handle.as_raw_fd(),
domid,
entries
);
let mut memory_map = ForeignMemoryMap {
domid: domid as u16,
map: MemoryMap {
count: entries.len() as u32,
buffer: entries.as_ptr() as u64,
},
};
self.hypercall2(
HYPERVISOR_MEMORY_OP,
XEN_MEM_MEMORY_MAP as c_ulong,
XEN_MEM_SET_MEMORY_MAP as c_ulong,
addr_of_mut!(memory_map) as c_ulong,
)
.await?;
let mut buffer = vec![0u8; memory_map.count as usize * size_of_entry];
memory_map.buffer = buffer.as_mut_ptr() as c_ulong;
self.hypercall2(
HYPERVISOR_MEMORY_OP,
XEN_MEM_MEMORY_MAP as c_ulong,
addr_of_mut!(memory_map) as c_ulong,
)
.await?;
Ok(buffer)
Ok(entries)
}
pub async fn populate_physmap(
@ -611,24 +650,14 @@ impl XenCall {
domid: domid as u16,
};
let calls = &mut [MultiCallEntry {
op: HYPERVISOR_MEMORY_OP,
result: 0,
args: [
let code = self
.hypercall2(
HYPERVISOR_MEMORY_OP,
XEN_MEM_POPULATE_PHYSMAP as c_ulong,
addr_of_mut!(reservation) as c_ulong,
0,
0,
0,
0,
],
}];
self.multicall(calls).await?;
let code = calls[0].result;
if code > !0xfff {
return Err(Error::PopulatePhysmapFailed);
}
if code as usize > extent_starts.len() {
)
.await?;
if code as usize != extent_starts.len() {
return Err(Error::PopulatePhysmapFailed);
}
let extents = extent_starts[0..code as usize].to_vec();
@ -658,6 +687,31 @@ impl XenCall {
Ok(())
}
pub async fn add_to_physmap(&self, domid: u32, space: u32, idx: u64, pfn: u64) -> Result<()> {
trace!(
"memory fd={} add_to_physmap domid={} space={} idx={} pfn={}",
self.handle.as_raw_fd(),
domid,
space,
idx,
pfn,
);
let mut add = AddToPhysmap {
domid: domid as u16,
size: 0,
space,
idx,
gpfn: pfn,
};
self.hypercall2(
HYPERVISOR_MEMORY_OP,
XEN_MEM_ADD_TO_PHYSMAP as c_ulong,
addr_of_mut!(add) as c_ulong,
)
.await?;
Ok(())
}
pub async fn mmuext(&self, domid: u32, cmd: c_uint, arg1: u64, arg2: u64) -> Result<()> {
let mut ops = MmuExtOp { cmd, arg1, arg2 };
@ -671,4 +725,366 @@ impl XenCall {
.await
.map(|_| ())
}
pub async fn iomem_permission(
&self,
domid: u32,
first_mfn: u64,
nr_mfns: u64,
allow: bool,
) -> Result<()> {
trace!(
"domctl fd={} iomem_permission domid={} first_mfn={:#x}, nr_mfns={:#x} allow={}",
self.handle.as_raw_fd(),
domid,
first_mfn,
nr_mfns,
allow,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_IOMEM_PERMISSION,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
iomem_permission: IoMemPermission {
first_mfn,
nr_mfns,
allow: if allow { 1 } else { 0 },
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(())
}
pub async fn ioport_permission(
&self,
domid: u32,
first_port: u32,
nr_ports: u32,
allow: bool,
) -> Result<()> {
trace!(
"domctl fd={} ioport_permission domid={} first_port={:#x}, nr_ports={:#x} allow={}",
self.handle.as_raw_fd(),
domid,
first_port,
nr_ports,
allow,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_IOPORT_PERMISSION,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
ioport_permission: IoPortPermission {
first_port,
nr_ports,
allow: if allow { 1 } else { 0 },
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(())
}
pub async fn irq_permission(&self, domid: u32, irq: u32, allow: bool) -> Result<()> {
trace!(
"domctl fd={} irq_permission domid={} irq={} allow={}",
self.handle.as_raw_fd(),
domid,
irq,
allow,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_IRQ_PERMISSION,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
irq_permission: IrqPermission {
pirq: irq,
allow: if allow { 1 } else { 0 },
pad: [0; 3],
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(())
}
#[allow(clippy::field_reassign_with_default)]
pub async fn map_pirq(&self, domid: u32, index: isize, pirq: Option<u32>) -> Result<u32> {
trace!(
"physdev fd={} map_pirq domid={} index={} pirq={:?}",
self.handle.as_raw_fd(),
domid,
index,
pirq,
);
let mut physdev = PhysdevMapPirq {
domid: domid as u16,
typ: 0x1,
index: index as c_int,
pirq: pirq.map(|x| x as c_int).unwrap_or(index as c_int),
..Default::default()
};
physdev.domid = domid as u16;
physdev.typ = 0x1;
physdev.index = index as c_int;
physdev.pirq = pirq.map(|x| x as c_int).unwrap_or(index as c_int);
self.hypercall2(
HYPERVISOR_PHYSDEV_OP,
PHYSDEVOP_MAP_PIRQ,
addr_of_mut!(physdev) as c_ulong,
)
.await?;
Ok(physdev.pirq as u32)
}
pub async fn assign_device(&self, domid: u32, sbdf: u32, flags: u32) -> Result<()> {
trace!(
"domctl fd={} assign_device domid={} sbdf={} flags={}",
self.handle.as_raw_fd(),
domid,
sbdf,
flags,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_ASSIGN_DEVICE,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
assign_device: AssignDevice {
device: DOMCTL_DEV_PCI,
flags,
pci_assign_device: PciAssignDevice { sbdf, padding: 0 },
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(())
}
#[allow(clippy::field_reassign_with_default)]
pub async fn set_hvm_param(&self, domid: u32, index: u32, value: u64) -> Result<()> {
trace!(
"set_hvm_param fd={} domid={} index={} value={:?}",
self.handle.as_raw_fd(),
domid,
index,
value,
);
let mut param = HvmParam::default();
param.domid = domid as u16;
param.index = index;
param.value = value;
self.hypercall2(HYPERVISOR_HVM_OP, 0, addr_of_mut!(param) as c_ulong)
.await?;
Ok(())
}
pub async fn get_hvm_context(&self, domid: u32, buffer: Option<&mut [u8]>) -> Result<u32> {
trace!(
"domctl fd={} get_hvm_context domid={}",
self.handle.as_raw_fd(),
domid,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_GETHVMCONTEXT,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
hvm_context: HvmContext {
size: buffer.as_ref().map(|x| x.len()).unwrap_or(0) as u32,
buffer: buffer.map(|x| x.as_mut_ptr()).unwrap_or(null_mut()) as u64,
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(unsafe { domctl.value.hvm_context.size })
}
pub async fn set_hvm_context(&self, domid: u32, buffer: &mut [u8]) -> Result<u32> {
trace!(
"domctl fd={} set_hvm_context domid={}",
self.handle.as_raw_fd(),
domid,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_SETHVMCONTEXT,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
hvm_context: HvmContext {
size: buffer.len() as u32,
buffer: buffer.as_ptr() as u64,
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(unsafe { domctl.value.hvm_context.size })
}
pub async fn set_paging_mempool_size(&self, domid: u32, size: u64) -> Result<()> {
trace!(
"domctl fd={} set_paging_mempool_size domid={} size={}",
self.handle.as_raw_fd(),
domid,
size,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
paging_mempool: PagingMempool { size },
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(())
}
pub async fn cpu_topology(&self) -> Result<Vec<SysctlCputopo>> {
let mut sysctl = Sysctl {
cmd: XEN_SYSCTL_CPUTOPOINFO,
interface_version: self.sysctl_interface_version,
value: SysctlValue {
cputopoinfo: SysctlCputopoinfo {
num_cpus: 0,
handle: 0,
},
},
};
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
.await?;
let cpus = unsafe { sysctl.value.cputopoinfo.num_cpus };
let mut topos = vec![
SysctlCputopo {
core: 0,
socket: 0,
node: 0
};
cpus as usize
];
let mut sysctl = Sysctl {
cmd: XEN_SYSCTL_CPUTOPOINFO,
interface_version: self.sysctl_interface_version,
value: SysctlValue {
cputopoinfo: SysctlCputopoinfo {
num_cpus: cpus,
handle: topos.as_mut_ptr() as c_ulong,
},
},
};
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
.await?;
Ok(topos)
}
pub async fn phys_info(&self) -> Result<SysctlPhysinfo> {
let mut sysctl = Sysctl {
cmd: XEN_SYSCTL_PHYSINFO,
interface_version: self.sysctl_interface_version,
value: SysctlValue {
phys_info: SysctlPhysinfo::default(),
},
};
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
.await?;
Ok(unsafe { sysctl.value.phys_info })
}
pub async fn set_cpufreq_gov(&self, cpuid: CpuId, gov: impl AsRef<str>) -> Result<()> {
match cpuid {
CpuId::All => {
let phys_info = self.phys_info().await?;
for cpuid in 0..phys_info.max_cpu_id + 1 {
self.do_set_cpufreq_gov(cpuid, gov.as_ref()).await?;
}
}
CpuId::Single(id) => {
self.do_set_cpufreq_gov(id, gov).await?;
}
}
Ok(())
}
async fn do_set_cpufreq_gov(&self, cpuid: u32, gov: impl AsRef<str>) -> Result<()> {
let governor = gov.as_ref().as_bytes().to_vec();
if governor.len() > 15 {
return Err(Error::ValueTooLong);
}
let mut scaling_governor = [0u8; 16];
// leave space for the last byte to be zero at all times.
for i in 0..15usize {
if i >= governor.len() {
break;
}
scaling_governor[i] = governor[i];
}
let mut sysctl = Sysctl {
cmd: XEN_SYSCTL_PM_OP,
interface_version: self.sysctl_interface_version,
value: SysctlValue {
pm_op: SysctlPmOp {
cmd: XEN_SYSCTL_PM_OP_ENABLE_TURBO,
cpuid,
value: SysctlPmOpValue {
set_gov: SysctlSetCpuFreqGov { scaling_governor },
},
},
},
};
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
.await?;
Ok(())
}
pub async fn set_turbo_mode(&self, cpuid: CpuId, enable: bool) -> Result<()> {
match cpuid {
CpuId::All => {
let phys_info = self.phys_info().await?;
for cpuid in 0..phys_info.max_cpu_id + 1 {
self.do_set_turbo_mode(cpuid, enable).await?;
}
}
CpuId::Single(id) => {
self.do_set_turbo_mode(id, enable).await?;
}
}
Ok(())
}
async fn do_set_turbo_mode(&self, cpuid: u32, enable: bool) -> Result<()> {
let mut sysctl = Sysctl {
cmd: XEN_SYSCTL_PM_OP,
interface_version: self.sysctl_interface_version,
value: SysctlValue {
pm_op: SysctlPmOp {
cmd: if enable {
XEN_SYSCTL_PM_OP_ENABLE_TURBO
} else {
XEN_SYSCTL_PM_OP_DISABLE_TURBO
},
cpuid,
value: SysctlPmOpValue { pad: [0u8; 128] },
},
},
};
self.hypercall1(HYPERVISOR_SYSCTL, addr_of_mut!(sysctl) as c_ulong)
.await?;
Ok(())
}
}

View File

@ -1,7 +1,6 @@
/// Handwritten hypercall bindings.
use nix::ioctl_readwrite_bad;
use std::ffi::{c_char, c_int, c_uint, c_ulong};
use uuid::Uuid;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
@ -35,8 +34,8 @@ pub struct MmapBatch {
pub num: u32,
pub domid: u16,
pub addr: u64,
pub mfns: *mut u64,
pub errors: *mut c_int,
pub mfns: u64,
pub errors: u64,
}
#[repr(C)]
@ -104,6 +103,7 @@ pub const XEN_DOMCTL_CDF_HAP: u32 = 1u32 << 1;
pub const XEN_DOMCTL_CDF_S3_INTEGRITY: u32 = 1u32 << 2;
pub const XEN_DOMCTL_CDF_OOS_OFF: u32 = 1u32 << 3;
pub const XEN_DOMCTL_CDF_XS_DOMAIN: u32 = 1u32 << 4;
pub const XEN_DOMCTL_CDF_IOMMU: u32 = 1u32 << 5;
pub const XEN_X86_EMU_LAPIC: u32 = 1 << 0;
pub const XEN_X86_EMU_HPET: u32 = 1 << 1;
@ -199,6 +199,7 @@ pub const XEN_DOMCTL_PSR_CAT_OP: u32 = 78;
pub const XEN_DOMCTL_SOFT_RESET: u32 = 79;
pub const XEN_DOMCTL_SET_GNTTAB_LIMITS: u32 = 80;
pub const XEN_DOMCTL_VUART_OP: u32 = 81;
pub const XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE: u32 = 86;
pub const XEN_DOMCTL_GDBSX_GUESTMEMIO: u32 = 1000;
pub const XEN_DOMCTL_GDBSX_PAUSEVCPU: u32 = 1001;
pub const XEN_DOMCTL_GDBSX_UNPAUSEVCPU: u32 = 1002;
@ -237,6 +238,12 @@ pub union DomCtlValue {
pub vcpu_context: DomCtlVcpuContext,
pub address_size: AddressSize,
pub get_page_frame_info: GetPageFrameInfo3,
pub ioport_permission: IoPortPermission,
pub iomem_permission: IoMemPermission,
pub irq_permission: IrqPermission,
pub assign_device: AssignDevice,
pub hvm_context: HvmContext,
pub paging_mempool: PagingMempool,
pub pad: [u8; 128],
}
@ -261,11 +268,8 @@ impl Default for CreateDomain {
fn default() -> Self {
CreateDomain {
ssidref: SECINITSID_DOMU,
handle: Uuid::new_v4().into_bytes(),
#[cfg(target_arch = "x86_64")]
handle: [0; 16],
flags: 0,
#[cfg(target_arch = "aarch64")]
flags: 1 << XEN_DOMCTL_CDF_HVM_GUEST,
iommu_opts: 0,
max_vcpus: 1,
max_evtchn_port: 1023,
@ -309,6 +313,30 @@ pub struct GetPageFrameInfo3 {
pub array: c_ulong,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct IoPortPermission {
pub first_port: u32,
pub nr_ports: u32,
pub allow: u8,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct IoMemPermission {
pub first_mfn: u64,
pub nr_mfns: u64,
pub allow: u8,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct IrqPermission {
pub pirq: u32,
pub allow: u8,
pub pad: [u8; 3],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
#[cfg(target_arch = "x86_64")]
@ -317,6 +345,8 @@ pub struct ArchDomainConfig {
pub misc_flags: u32,
}
pub const X86_EMU_LAPIC: u32 = 1 << 0;
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
#[cfg(target_arch = "aarch64")]
@ -369,6 +399,16 @@ pub struct MemoryReservation {
pub domid: u16,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct AddToPhysmap {
pub domid: u16,
pub size: u16,
pub space: u32,
pub idx: u64,
pub gpfn: u64,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct MultiCallEntry {
@ -378,8 +418,10 @@ pub struct MultiCallEntry {
}
pub const XEN_MEM_POPULATE_PHYSMAP: u32 = 6;
pub const XEN_MEM_MEMORY_MAP: u32 = 9;
pub const XEN_MEM_MEMORY_MAP: u32 = 10;
pub const XEN_MEM_SET_MEMORY_MAP: u32 = 13;
pub const XEN_MEM_CLAIM_PAGES: u32 = 24;
pub const XEN_MEM_ADD_TO_PHYSMAP: u32 = 7;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
@ -388,6 +430,13 @@ pub struct MemoryMap {
pub buffer: c_ulong,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ForeignMemoryMap {
pub domid: u16,
pub map: MemoryMap,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct VcpuGuestContextFpuCtx {
@ -402,8 +451,8 @@ impl Default for VcpuGuestContextFpuCtx {
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
#[cfg(target_arch = "x86_64")]
pub struct CpuUserRegs {
#[allow(non_camel_case_types)]
pub struct x8664CpuUserRegs {
pub r15: u64,
pub r14: u64,
pub r13: u64,
@ -442,7 +491,6 @@ pub struct CpuUserRegs {
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
#[cfg(target_arch = "x86_64")]
pub struct TrapInfo {
pub vector: u8,
pub flags: u8,
@ -452,11 +500,11 @@ pub struct TrapInfo {
#[repr(C)]
#[derive(Copy, Clone, Debug)]
#[cfg(target_arch = "x86_64")]
pub struct VcpuGuestContext {
#[allow(non_camel_case_types)]
pub struct x8664VcpuGuestContext {
pub fpu_ctx: VcpuGuestContextFpuCtx,
pub flags: u64,
pub user_regs: CpuUserRegs,
pub user_regs: x8664CpuUserRegs,
pub trap_ctx: [TrapInfo; 256],
pub ldt_base: u64,
pub ldt_ents: u64,
@ -475,10 +523,9 @@ pub struct VcpuGuestContext {
pub gs_base_user: u64,
}
#[cfg(target_arch = "x86_64")]
impl Default for VcpuGuestContext {
impl Default for x8664VcpuGuestContext {
fn default() -> Self {
VcpuGuestContext {
Self {
fpu_ctx: Default::default(),
flags: 0,
user_regs: Default::default(),
@ -504,8 +551,7 @@ impl Default for VcpuGuestContext {
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
#[cfg(target_arch = "aarch64")]
pub struct CpuUserRegs {
pub struct Arm64CpuUserRegs {
pub x0: u64,
pub x1: u64,
pub x2: u64,
@ -551,10 +597,9 @@ pub struct CpuUserRegs {
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
#[cfg(target_arch = "aarch64")]
pub struct VcpuGuestContext {
pub struct Arm64VcpuGuestContext {
pub flags: u32,
pub user_regs: CpuUserRegs,
pub user_regs: x8664CpuUserRegs,
pub sctlr: u64,
pub ttbcr: u64,
pub ttbr0: u64,
@ -562,7 +607,10 @@ pub struct VcpuGuestContext {
}
pub union VcpuGuestContextAny {
pub value: VcpuGuestContext,
#[cfg(target_arch = "aarch64")]
pub value: Arm64VcpuGuestContext,
#[cfg(target_arch = "x86_64")]
pub value: x8664VcpuGuestContext,
}
#[repr(C)]
@ -582,3 +630,174 @@ pub struct EvtChnAllocUnbound {
pub remote_dom: u16,
pub port: u32,
}
#[repr(C, packed)]
#[derive(Debug, Copy, Clone, Default)]
pub struct E820Entry {
pub addr: u64,
pub size: u64,
pub typ: u32,
}
pub const E820_MAX: u32 = 1024;
pub const E820_RAM: u32 = 1;
pub const E820_RESERVED: u32 = 2;
pub const E820_ACPI: u32 = 3;
pub const E820_NVS: u32 = 4;
pub const E820_UNUSABLE: u32 = 5;
pub const PHYSDEVOP_MAP_PIRQ: u64 = 13;
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
pub struct PhysdevMapPirq {
pub domid: u16,
pub typ: c_int,
pub index: c_int,
pub pirq: c_int,
pub bus: c_int,
pub devfn: c_int,
pub entry_nr: u16,
pub table_base: u64,
}
pub const DOMCTL_DEV_RDM_RELAXED: u32 = 1;
pub const DOMCTL_DEV_PCI: u32 = 0;
pub const DOMCTL_DEV_DT: u32 = 1;
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
pub struct PciAssignDevice {
pub sbdf: u32,
pub padding: u64,
}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
pub struct AssignDevice {
pub device: u32,
pub flags: u32,
pub pci_assign_device: PciAssignDevice,
}
pub const DOMID_IO: u32 = 0x7FF1;
pub const MEMFLAGS_POPULATE_ON_DEMAND: u32 = 1 << 16;
pub struct PodTarget {
pub target_pages: u64,
pub total_pages: u64,
pub pod_cache_pages: u64,
pub pod_entries: u64,
pub domid: u16,
}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
pub struct HvmParam {
pub domid: u16,
pub pad: u8,
pub index: u32,
pub value: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HvmContext {
pub size: u32,
pub buffer: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct PagingMempool {
pub size: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SysctlCputopo {
pub core: u32,
pub socket: u32,
pub node: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SysctlSetCpuFreqGov {
pub scaling_governor: [u8; 16],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union SysctlPmOpValue {
pub set_gov: SysctlSetCpuFreqGov,
pub opt_smt: u32,
pub pad: [u8; 128],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SysctlPmOp {
pub cmd: u32,
pub cpuid: u32,
pub value: SysctlPmOpValue,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SysctlCputopoinfo {
pub num_cpus: u32,
pub handle: c_ulong,
}
#[repr(C)]
pub union SysctlValue {
pub cputopoinfo: SysctlCputopoinfo,
pub pm_op: SysctlPmOp,
pub phys_info: SysctlPhysinfo,
pub pad: [u8; 128],
}
#[repr(C)]
pub struct Sysctl {
pub cmd: u32,
pub interface_version: u32,
pub value: SysctlValue,
}
pub const XEN_SYSCTL_PHYSINFO: u32 = 3;
pub const XEN_SYSCTL_PM_OP: u32 = 12;
pub const XEN_SYSCTL_CPUTOPOINFO: u32 = 16;
pub const XEN_SYSCTL_MIN_INTERFACE_VERSION: u32 = 0x00000015;
pub const XEN_SYSCTL_MAX_INTERFACE_VERSION: u32 = 0x00000020;
pub const XEN_SYSCTL_PM_OP_SET_SCHED_OPT_STMT: u32 = 0x21;
pub const XEN_SYSCTL_PM_OP_ENABLE_TURBO: u32 = 0x26;
pub const XEN_SYSCTL_PM_OP_DISABLE_TURBO: u32 = 0x27;
#[derive(Clone, Copy, Debug)]
pub enum CpuId {
All,
Single(u32),
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct SysctlPhysinfo {
pub threads_per_core: u32,
pub cores_per_socket: u32,
pub nr_cpus: u32,
pub max_cpu_id: u32,
pub nr_nodes: u32,
pub max_node_id: u32,
pub cpu_khz: u32,
pub capabilities: u32,
pub arch_capabilities: u32,
pub pad: u32,
pub total_pages: u64,
pub free_pages: u64,
pub scrub_pages: u64,
pub outstanding_pages: u64,
pub max_mfn: u64,
pub hw_cap: [u32; 8],
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-xenclient"
description = "An implementation of Xen userspace for krata."
description = "An implementation of Xen userspace for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true
@ -10,19 +10,16 @@ resolver = "2"
[dependencies]
async-trait = { workspace = true }
elf = { workspace = true }
flate2 = { workspace = true }
indexmap = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
krata-xencall = { path = "../xencall", version = "^0.0.10" }
krata-xenstore = { path = "../xenstore", version = "^0.0.10" }
memchr = { workspace = true }
nix = { workspace = true }
slice-copy = { workspace = true }
krata-xencall = { path = "../xencall", version = "^0.0.12" }
krata-xenplatform = { path = "../xenplatform", version = "^0.0.12" }
krata-xenstore = { path = "../xenstore", version = "^0.0.12" }
regex = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
uuid = { workspace = true }
xz2 = { workspace = true }
[dev-dependencies]
env_logger = { workspace = true }
@ -34,3 +31,7 @@ name = "xenclient"
[[example]]
name = "xenclient-boot"
path = "examples/boot.rs"
[[example]]
name = "xenclient-pci"
path = "examples/pci.rs"

View File

@ -1,7 +1,15 @@
use std::{env, process};
use tokio::fs;
use uuid::Uuid;
use xenclient::error::Result;
use xenclient::{DomainConfig, XenClient};
use xenplatform::domain::BaseDomainConfig;
#[cfg(target_arch = "x86_64")]
type RuntimePlatform = xenplatform::x86pv::X86PvPlatform;
#[cfg(not(target_arch = "x86_64"))]
type RuntimePlatform = xenplatform::unsupported::UnsupportedPlatform;
#[tokio::main]
async fn main() -> Result<()> {
@ -14,23 +22,28 @@ async fn main() -> Result<()> {
}
let kernel_image_path = args.get(1).expect("argument not specified");
let initrd_path = args.get(2).expect("argument not specified");
let client = XenClient::open(0).await?;
let client = XenClient::new(0, RuntimePlatform::new()).await?;
let config = DomainConfig {
base: BaseDomainConfig {
uuid: Uuid::new_v4(),
max_vcpus: 1,
mem_mb: 512,
enable_iommu: true,
kernel: fs::read(&kernel_image_path).await?,
initrd: fs::read(&initrd_path).await?,
cmdline: "earlyprintk=xen earlycon=xen console=hvc0 init=/init".to_string(),
owner_domid: 0,
},
backend_domid: 0,
name: "xenclient-test".to_string(),
max_vcpus: 1,
mem_mb: 512,
kernel: fs::read(&kernel_image_path).await?,
initrd: fs::read(&initrd_path).await?,
cmdline: "debug elevator=noop".to_string(),
use_console_backend: None,
swap_console_backend: None,
disks: vec![],
channels: vec![],
vifs: vec![],
pcis: vec![],
filesystems: vec![],
extra_keys: vec![],
extra_rw_paths: vec![],
event_channels: vec![],
};
let created = client.create(&config).await?;
println!("created domain {}", created.domid);

View File

@ -0,0 +1,32 @@
use xenclient::pci::*;
use xenclient::error::Result;
#[tokio::main]
async fn main() -> Result<()> {
let backend = XenPciBackend::new();
if !backend.is_loaded().await? {
return Err(xenclient::error::Error::GenericError(
"xen-pciback module not loaded".to_string(),
));
}
println!("assignable devices:");
for device in backend.list_devices().await? {
let is_assigned = backend.is_assigned(&device).await?;
let has_slot = backend.has_slot(&device).await?;
println!("{} slot={} assigned={}", device, has_slot, is_assigned);
let resources = backend.read_resources(&device).await?;
for resource in resources {
println!(
" resource start={:#x} end={:#x} flags={:#x} bar-io={}",
resource.start,
resource.end,
resource.flags,
resource.is_bar_io()
);
}
}
Ok(())
}

View File

@ -1,288 +0,0 @@
use crate::boot::{ArchBootSetup, BootImageInfo, BootSetup, BootState, DomainSegment};
use crate::error::Result;
use crate::sys::XEN_PAGE_SHIFT;
use crate::Error;
use log::trace;
use xencall::sys::VcpuGuestContext;
pub const ARM_PAGE_SHIFT: u64 = 12;
const ARM_PAGE_SIZE: u64 = 1 << ARM_PAGE_SHIFT;
const GUEST_RAM0_BASE: u64 = 0x40000000;
const GUEST_RAM0_SIZE: u64 = 0xc0000000;
const GUEST_RAM1_BASE: u64 = 0x0200000000;
const GUEST_RAM1_SIZE: u64 = 0xfe00000000;
const GUEST_RAM_BANK_BASES: [u64; 2] = [GUEST_RAM0_BASE, GUEST_RAM1_BASE];
const GUEST_RAM_BANK_SIZES: [u64; 2] = [GUEST_RAM0_SIZE, GUEST_RAM1_SIZE];
const LPAE_SHIFT: u64 = 9;
const PFN_4K_SHIFT: u64 = 0;
const PFN_2M_SHIFT: u64 = PFN_4K_SHIFT + LPAE_SHIFT;
const PFN_1G_SHIFT: u64 = PFN_2M_SHIFT + LPAE_SHIFT;
const PFN_512G_SHIFT: u64 = PFN_1G_SHIFT + LPAE_SHIFT;
const PSR_FIQ_MASK: u64 = 1 << 6; /* Fast Interrupt mask */
const PSR_IRQ_MASK: u64 = 1 << 7; /* Interrupt mask */
const PSR_ABT_MASK: u64 = 1 << 8; /* Asynchronous Abort mask */
const PSR_MODE_EL1H: u64 = 0x05;
const PSR_GUEST64_INIT: u64 = PSR_ABT_MASK | PSR_FIQ_MASK | PSR_IRQ_MASK | PSR_MODE_EL1H;
pub struct Arm64BootSetup {}
impl Default for Arm64BootSetup {
fn default() -> Self {
Self::new()
}
}
impl Arm64BootSetup {
pub fn new() -> Arm64BootSetup {
Arm64BootSetup {}
}
async fn populate_one_size(
&self,
setup: &mut BootSetup<'_>,
pfn_shift: u64,
base_pfn: u64,
pfn_count: u64,
extents: &mut [u64],
) -> Result<u64> {
let mask = (1u64 << pfn_shift) - 1;
let next_shift = pfn_shift + LPAE_SHIFT;
let next_mask = (1u64 << next_shift) - 1;
let next_boundary = (base_pfn + (1 << next_shift)) - 1;
let mut end_pfn = base_pfn + pfn_count;
if pfn_shift == PFN_512G_SHIFT {
return Ok(0);
}
if (base_pfn & next_mask) != 0 && end_pfn > next_boundary {
end_pfn = next_boundary;
}
if (mask & base_pfn) != 0 {
return Ok(0);
}
let count = (end_pfn - base_pfn) >> pfn_shift;
if count == 0 {
return Ok(0);
}
for i in 0..count {
extents[i as usize] = base_pfn + (i << pfn_shift);
}
let result_extents = setup
.call
.populate_physmap(
setup.domid,
count,
pfn_shift as u32,
0,
&extents[0usize..count as usize],
)
.await?;
slice_copy::copy(extents, &result_extents);
Ok((result_extents.len() as u64) << pfn_shift)
}
async fn populate_guest_memory(
&mut self,
setup: &mut BootSetup<'_>,
base_pfn: u64,
pfn_count: u64,
) -> Result<()> {
let mut extents = vec![0u64; 1024 * 1024];
for pfn in 0..extents.len() {
let mut allocsz = (1024 * 1024).min(pfn_count - pfn as u64);
allocsz = self
.populate_one_size(
setup,
PFN_512G_SHIFT,
base_pfn + pfn as u64,
allocsz,
&mut extents,
)
.await?;
if allocsz > 0 {
continue;
}
allocsz = self
.populate_one_size(
setup,
PFN_1G_SHIFT,
base_pfn + pfn as u64,
allocsz,
&mut extents,
)
.await?;
if allocsz > 0 {
continue;
}
allocsz = self
.populate_one_size(
setup,
PFN_2M_SHIFT,
base_pfn + pfn as u64,
allocsz,
&mut extents,
)
.await?;
if allocsz > 0 {
continue;
}
allocsz = self
.populate_one_size(
setup,
PFN_4K_SHIFT,
base_pfn + pfn as u64,
allocsz,
&mut extents,
)
.await?;
if allocsz == 0 {
return Err(Error::MemorySetupFailed("allocsz is zero"));
}
}
Ok(())
}
}
#[async_trait::async_trait]
impl ArchBootSetup for Arm64BootSetup {
fn page_size(&mut self) -> u64 {
ARM_PAGE_SIZE
}
fn page_shift(&mut self) -> u64 {
ARM_PAGE_SHIFT
}
fn needs_early_kernel(&mut self) -> bool {
true
}
async fn setup_shared_info(&mut self, _: &mut BootSetup, _: u64) -> Result<()> {
Ok(())
}
async fn setup_start_info(&mut self, _: &mut BootSetup, _: &BootState, _: &str) -> Result<()> {
Ok(())
}
async fn meminit(
&mut self,
setup: &mut BootSetup,
total_pages: u64,
kernel_segment: &Option<DomainSegment>,
initrd_segment: &Option<DomainSegment>,
) -> Result<()> {
let kernel_segment = kernel_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("kernel_segment missing"))?;
setup.call.claim_pages(setup.domid, total_pages).await?;
let mut ramsize = total_pages << XEN_PAGE_SHIFT;
let bankbase = GUEST_RAM_BANK_BASES;
let bankmax = GUEST_RAM_BANK_SIZES;
let kernbase = kernel_segment.vstart;
let kernend = BootSetup::round_up(kernel_segment.size, 21);
let dtb = setup.dtb.as_ref();
let dtb_size = dtb.map(|blob| BootSetup::round_up(blob.len() as u64, XEN_PAGE_SHIFT));
let ramdisk_size = initrd_segment
.as_ref()
.map(|segment| BootSetup::round_up(segment.size, XEN_PAGE_SHIFT));
let modsize = dtb_size.unwrap_or(0) + ramdisk_size.unwrap_or(0);
let ram128mb = bankbase[0] + (128 << 20);
let mut rambank_size: [u64; 2] = [0, 0];
for i in 0..2 {
let size = if ramsize > bankmax[i] {
bankmax[i]
} else {
ramsize
};
ramsize -= size;
rambank_size[i] = size >> XEN_PAGE_SHIFT;
}
for i in 0..2 {
let size = if ramsize > bankmax[i] {
bankmax[i]
} else {
ramsize
};
ramsize -= size;
rambank_size[i] = size >> XEN_PAGE_SHIFT;
}
for i in 0..2 {
self.populate_guest_memory(setup, bankbase[i] >> XEN_PAGE_SHIFT, rambank_size[i])
.await?;
}
let bank0end = bankbase[0] + (rambank_size[0] << XEN_PAGE_SHIFT);
let _modbase = if bank0end >= ram128mb + modsize && kernend < ram128mb {
ram128mb
} else if bank0end - modsize > kernend {
bank0end - modsize
} else if kernbase - bankbase[0] > modsize {
kernbase - modsize
} else {
return Err(Error::MemorySetupFailed("unable to determine modbase"));
};
setup.call.claim_pages(setup.domid, 0).await?;
Ok(())
}
async fn bootlate(&mut self, _: &mut BootSetup, _: &mut BootState) -> Result<()> {
Ok(())
}
async fn vcpu(&mut self, setup: &mut BootSetup, state: &mut BootState) -> Result<()> {
let mut vcpu = VcpuGuestContext::default();
vcpu.user_regs.pc = state.image_info.virt_entry;
vcpu.user_regs.x0 = 0xffffffff;
vcpu.user_regs.x1 = 0;
vcpu.user_regs.x2 = 0;
vcpu.user_regs.x3 = 0;
vcpu.sctlr = 0x00c50078;
vcpu.ttbr0 = 0;
vcpu.ttbr1 = 0;
vcpu.ttbcr = 0;
vcpu.user_regs.cpsr = PSR_GUEST64_INIT;
vcpu.flags = 1 << 0; // VGCF_ONLINE
trace!("vcpu context: {:?}", vcpu);
setup.call.set_vcpu_context(setup.domid, 0, &vcpu).await?;
Ok(())
}
async fn alloc_p2m_segment(
&mut self,
_: &mut BootSetup,
_: &BootImageInfo,
) -> Result<Option<DomainSegment>> {
Ok(None)
}
async fn alloc_page_tables(
&mut self,
_: &mut BootSetup,
_: &BootImageInfo,
) -> Result<Option<DomainSegment>> {
Ok(None)
}
async fn setup_page_tables(&mut self, _: &mut BootSetup, _: &mut BootState) -> Result<()> {
Ok(())
}
}

View File

@ -1,422 +0,0 @@
use crate::error::Result;
use crate::mem::PhysicalPages;
use crate::sys::{GrantEntry, XEN_PAGE_SHIFT};
use crate::Error;
use libc::munmap;
use log::debug;
use nix::errno::Errno;
use slice_copy::copy;
use crate::mem::ARCH_PAGE_SHIFT;
use std::ffi::c_void;
use std::slice;
use xencall::XenCall;
pub trait BootImageLoader {
fn parse(&self) -> Result<BootImageInfo>;
fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<()>;
}
pub const XEN_UNSET_ADDR: u64 = -1i64 as u64;
#[derive(Debug)]
pub struct BootImageInfo {
pub start: u64,
pub virt_base: u64,
pub virt_kstart: u64,
pub virt_kend: u64,
pub virt_hypercall: u64,
pub virt_entry: u64,
pub virt_p2m_base: u64,
pub unmapped_initrd: bool,
}
pub struct BootSetup<'a> {
pub(crate) call: &'a XenCall,
pub phys: PhysicalPages<'a>,
pub(crate) domid: u32,
pub(crate) virt_alloc_end: u64,
pub(crate) pfn_alloc_end: u64,
pub(crate) virt_pgtab_end: u64,
pub(crate) total_pages: u64,
#[cfg(target_arch = "aarch64")]
pub(crate) dtb: Option<Vec<u8>>,
}
#[derive(Debug)]
pub struct DomainSegment {
pub(crate) vstart: u64,
vend: u64,
pub pfn: u64,
pub(crate) addr: u64,
pub(crate) size: u64,
#[cfg(target_arch = "x86_64")]
pub(crate) pages: u64,
}
#[derive(Debug)]
pub struct BootState {
pub kernel_segment: DomainSegment,
pub start_info_segment: DomainSegment,
pub xenstore_segment: DomainSegment,
pub boot_stack_segment: DomainSegment,
pub p2m_segment: Option<DomainSegment>,
pub page_table_segment: Option<DomainSegment>,
pub image_info: BootImageInfo,
pub shared_info_frame: u64,
pub initrd_segment: DomainSegment,
pub store_evtchn: u32,
pub consoles: Vec<(u32, DomainSegment)>,
}
impl BootSetup<'_> {
pub fn new(call: &XenCall, domid: u32) -> BootSetup {
BootSetup {
call,
phys: PhysicalPages::new(call, domid),
domid,
virt_alloc_end: 0,
pfn_alloc_end: 0,
virt_pgtab_end: 0,
total_pages: 0,
#[cfg(target_arch = "aarch64")]
dtb: None,
}
}
async fn initialize_memory(
&mut self,
arch: &mut Box<dyn ArchBootSetup + Send + Sync>,
total_pages: u64,
kernel_segment: &Option<DomainSegment>,
initrd_segment: &Option<DomainSegment>,
) -> Result<()> {
self.call.set_address_size(self.domid, 64).await?;
arch.meminit(self, total_pages, kernel_segment, initrd_segment)
.await?;
Ok(())
}
async fn setup_hypercall_page(&mut self, image_info: &BootImageInfo) -> Result<()> {
if image_info.virt_hypercall == XEN_UNSET_ADDR {
return Ok(());
}
let pfn = (image_info.virt_hypercall - image_info.virt_base) >> ARCH_PAGE_SHIFT;
let mfn = self.phys.p2m[pfn as usize];
self.call.hypercall_init(self.domid, mfn).await?;
Ok(())
}
pub async fn initialize<I: BootImageLoader + Send + Sync>(
&mut self,
arch: &mut Box<dyn ArchBootSetup + Send + Sync>,
image_loader: &I,
initrd: &[u8],
max_vcpus: u32,
mem_mb: u64,
console_count: usize,
) -> Result<BootState> {
debug!("initialize max_vcpus={:?} mem_mb={:?}", max_vcpus, mem_mb);
let page_size = arch.page_size();
let image_info = image_loader.parse()?;
debug!("initialize image_info={:?}", image_info);
let mut kernel_segment: Option<DomainSegment> = None;
let mut initrd_segment: Option<DomainSegment> = None;
if !image_info.unmapped_initrd {
initrd_segment = Some(self.alloc_module(page_size, initrd).await?);
}
if arch.needs_early_kernel() {
kernel_segment = Some(
self.load_kernel_segment(page_size, image_loader, &image_info)
.await?,
);
}
let total_pages = mem_mb << (20 - arch.page_shift());
self.initialize_memory(arch, total_pages, &kernel_segment, &initrd_segment)
.await?;
self.virt_alloc_end = image_info.virt_base;
if kernel_segment.is_none() {
kernel_segment = Some(
self.load_kernel_segment(page_size, image_loader, &image_info)
.await?,
);
}
let mut p2m_segment: Option<DomainSegment> = None;
if image_info.virt_p2m_base >= image_info.virt_base
|| (image_info.virt_p2m_base & ((1 << arch.page_shift()) - 1)) != 0
{
p2m_segment = arch.alloc_p2m_segment(self, &image_info).await?;
}
let start_info_segment = self.alloc_page(page_size)?;
let xenstore_segment = self.alloc_page(page_size)?;
let mut consoles: Vec<(u32, DomainSegment)> = Vec::new();
for _ in 0..console_count {
let evtchn = self.call.evtchn_alloc_unbound(self.domid, 0).await?;
let page = self.alloc_page(page_size)?;
consoles.push((evtchn, page));
}
let page_table_segment = arch.alloc_page_tables(self, &image_info).await?;
let boot_stack_segment = self.alloc_page(page_size)?;
if self.virt_pgtab_end > 0 {
self.alloc_padding_pages(page_size, self.virt_pgtab_end)?;
}
if p2m_segment.is_none() {
if let Some(mut segment) = arch.alloc_p2m_segment(self, &image_info).await? {
segment.vstart = image_info.virt_p2m_base;
p2m_segment = Some(segment);
}
}
if image_info.unmapped_initrd {
initrd_segment = Some(self.alloc_module(page_size, initrd).await?);
}
let initrd_segment = initrd_segment.unwrap();
let store_evtchn = self.call.evtchn_alloc_unbound(self.domid, 0).await?;
let kernel_segment =
kernel_segment.ok_or(Error::MemorySetupFailed("kernel_segment missing"))?;
let state = BootState {
kernel_segment,
start_info_segment,
xenstore_segment,
consoles,
boot_stack_segment,
p2m_segment,
page_table_segment,
image_info,
initrd_segment,
store_evtchn,
shared_info_frame: 0,
};
debug!("initialize state={:?}", state);
Ok(state)
}
pub async fn boot(
&mut self,
arch: &mut Box<dyn ArchBootSetup + Send + Sync>,
state: &mut BootState,
cmdline: &str,
) -> Result<()> {
let domain_info = self.call.get_domain_info(self.domid).await?;
let shared_info_frame = domain_info.shared_info_frame;
state.shared_info_frame = shared_info_frame;
arch.setup_page_tables(self, state).await?;
arch.setup_start_info(self, state, cmdline).await?;
self.setup_hypercall_page(&state.image_info).await?;
arch.bootlate(self, state).await?;
arch.setup_shared_info(self, state.shared_info_frame)
.await?;
arch.vcpu(self, state).await?;
self.phys.unmap_all()?;
self.gnttab_seed(state).await?;
Ok(())
}
async fn gnttab_seed(&mut self, state: &mut BootState) -> Result<()> {
let console_gfn =
self.phys.p2m[state.consoles.first().map(|x| x.1.pfn).unwrap_or(0) as usize];
let xenstore_gfn = self.phys.p2m[state.xenstore_segment.pfn as usize];
let addr = self
.call
.mmap(0, 1 << XEN_PAGE_SHIFT)
.await
.ok_or(Error::MmapFailed)?;
self.call.map_resource(self.domid, 1, 0, 0, 1, addr).await?;
let entries = unsafe { slice::from_raw_parts_mut(addr as *mut GrantEntry, 2) };
entries[0].flags = 1 << 0;
entries[0].domid = 0;
entries[0].frame = console_gfn as u32;
entries[1].flags = 1 << 0;
entries[1].domid = 0;
entries[1].frame = xenstore_gfn as u32;
unsafe {
let result = munmap(addr as *mut c_void, 1 << XEN_PAGE_SHIFT);
if result != 0 {
return Err(Error::UnmapFailed(Errno::from_raw(result)));
}
}
Ok(())
}
async fn load_kernel_segment<I: BootImageLoader + Send + Sync>(
&mut self,
page_size: u64,
image_loader: &I,
image_info: &BootImageInfo,
) -> Result<DomainSegment> {
let kernel_segment = self
.alloc_segment(
page_size,
image_info.virt_kstart,
image_info.virt_kend - image_info.virt_kstart,
)
.await?;
let kernel_segment_ptr = kernel_segment.addr as *mut u8;
let kernel_segment_slice =
unsafe { slice::from_raw_parts_mut(kernel_segment_ptr, kernel_segment.size as usize) };
image_loader.load(image_info, kernel_segment_slice)?;
Ok(kernel_segment)
}
pub(crate) fn round_up(addr: u64, mask: u64) -> u64 {
addr | mask
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn bits_to_mask(bits: u64) -> u64 {
(1 << bits) - 1
}
pub(crate) async fn alloc_segment(
&mut self,
page_size: u64,
start: u64,
size: u64,
) -> Result<DomainSegment> {
debug!("alloc_segment {:#x} {:#x}", start, size);
if start > 0 {
self.alloc_padding_pages(page_size, start)?;
}
let local_page_size: u32 = (1i64 << XEN_PAGE_SHIFT) as u32;
let pages = (size + local_page_size as u64 - 1) / local_page_size as u64;
let start = self.virt_alloc_end;
let mut segment = DomainSegment {
vstart: start,
vend: 0,
pfn: self.pfn_alloc_end,
addr: 0,
size,
#[cfg(target_arch = "x86_64")]
pages,
};
self.chk_alloc_pages(page_size, pages)?;
let ptr = self.phys.pfn_to_ptr(segment.pfn, pages).await?;
segment.addr = ptr;
let slice = unsafe {
slice::from_raw_parts_mut(ptr as *mut u8, (pages * local_page_size as u64) as usize)
};
slice.fill(0);
segment.vend = self.virt_alloc_end;
debug!(
"alloc_segment {:#x} -> {:#x} (pfn {:#x} + {:#x} pages)",
start, segment.vend, segment.pfn, pages
);
Ok(segment)
}
fn alloc_page(&mut self, page_size: u64) -> Result<DomainSegment> {
let start = self.virt_alloc_end;
let pfn = self.pfn_alloc_end;
self.chk_alloc_pages(page_size, 1)?;
debug!("alloc_page {:#x} (pfn {:#x})", start, pfn);
Ok(DomainSegment {
vstart: start,
vend: (start + page_size) - 1,
pfn,
addr: 0,
size: 0,
#[cfg(target_arch = "x86_64")]
pages: 1,
})
}
async fn alloc_module(&mut self, page_size: u64, buffer: &[u8]) -> Result<DomainSegment> {
let segment = self
.alloc_segment(page_size, 0, buffer.len() as u64)
.await?;
let slice = unsafe { slice::from_raw_parts_mut(segment.addr as *mut u8, buffer.len()) };
copy(slice, buffer);
Ok(segment)
}
fn alloc_padding_pages(&mut self, page_size: u64, boundary: u64) -> Result<()> {
if (boundary & (page_size - 1)) != 0 {
return Err(Error::MemorySetupFailed("boundary is incorrect"));
}
if boundary < self.virt_alloc_end {
return Err(Error::MemorySetupFailed("boundary is below allocation end"));
}
let pages = (boundary - self.virt_alloc_end) / page_size;
self.chk_alloc_pages(page_size, pages)?;
Ok(())
}
fn chk_alloc_pages(&mut self, page_size: u64, pages: u64) -> Result<()> {
if pages > self.total_pages
|| self.pfn_alloc_end > self.total_pages
|| pages > self.total_pages - self.pfn_alloc_end
{
return Err(Error::MemorySetupFailed("no more pages left"));
}
self.pfn_alloc_end += pages;
self.virt_alloc_end += pages * page_size;
Ok(())
}
}
#[async_trait::async_trait]
pub trait ArchBootSetup {
fn page_size(&mut self) -> u64;
fn page_shift(&mut self) -> u64;
fn needs_early_kernel(&mut self) -> bool;
async fn alloc_p2m_segment(
&mut self,
setup: &mut BootSetup,
image_info: &BootImageInfo,
) -> Result<Option<DomainSegment>>;
async fn alloc_page_tables(
&mut self,
setup: &mut BootSetup,
image_info: &BootImageInfo,
) -> Result<Option<DomainSegment>>;
async fn setup_page_tables(
&mut self,
setup: &mut BootSetup,
state: &mut BootState,
) -> Result<()>;
async fn setup_start_info(
&mut self,
setup: &mut BootSetup,
state: &BootState,
cmdline: &str,
) -> Result<()>;
async fn setup_shared_info(
&mut self,
setup: &mut BootSetup,
shared_info_frame: u64,
) -> Result<()>;
async fn meminit(
&mut self,
setup: &mut BootSetup,
total_pages: u64,
kernel_segment: &Option<DomainSegment>,
initrd_segment: &Option<DomainSegment>,
) -> Result<()>;
async fn bootlate(&mut self, setup: &mut BootSetup, state: &mut BootState) -> Result<()>;
async fn vcpu(&mut self, setup: &mut BootSetup, state: &mut BootState) -> Result<()>;
}

View File

@ -1,5 +1,7 @@
use std::io;
use crate::pci::PciBdf;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("io issue encountered: {0}")]
@ -18,12 +20,6 @@ pub enum Error {
PathParentNotFound,
#[error("domain does not exist")]
DomainNonExistent,
#[error("elf parse failed: {0}")]
ElfParseFailed(#[from] elf::ParseError),
#[error("mmap failed")]
MmapFailed,
#[error("munmap failed: {0}")]
UnmapFailed(nix::errno::Errno),
#[error("memory setup failed: {0}")]
MemorySetupFailed(&'static str),
#[error("populate physmap failed: wanted={0}, received={1}, input_extents={2}")]
@ -34,6 +30,18 @@ pub enum Error {
ElfInvalidImage,
#[error("provided elf image does not contain xen support")]
ElfXenSupportMissing,
#[error("regex error: {0}")]
RegexError(#[from] regex::Error),
#[error("error: {0}")]
GenericError(String),
#[error("failed to parse int: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("invalid pci bdf string")]
InvalidPciBdfString,
#[error("pci device {0} is not assignable")]
PciDeviceNotAssignable(PciBdf),
#[error("xen platform error: {0}")]
XenPlatform(#[from] xenplatform::error::Error),
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -1,42 +1,28 @@
pub mod boot;
pub mod elfloader;
pub mod error;
pub mod mem;
pub mod sys;
#[cfg(target_arch = "x86_64")]
pub mod x86;
#[cfg(target_arch = "x86_64")]
use crate::x86::X86BootSetup;
#[cfg(target_arch = "aarch64")]
pub mod arm64;
#[cfg(target_arch = "aarch64")]
use crate::arm64::Arm64BootSetup;
use crate::boot::{ArchBootSetup, BootSetup};
use crate::elfloader::ElfImageLoader;
use crate::error::{Error, Result};
use boot::BootState;
use log::{debug, trace, warn};
use log::{debug, trace};
use pci::PciBdf;
use tokio::time::timeout;
use tx::ClientTransaction;
use xenplatform::boot::BootSetupPlatform;
use xenplatform::domain::{BaseDomainConfig, BaseDomainManager, CreatedDomain};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use uuid::Uuid;
use xencall::sys::{CreateDomain, XEN_DOMCTL_CDF_HAP, XEN_DOMCTL_CDF_HVM_GUEST};
use xencall::XenCall;
use xenstore::{
XsPermission, XsdClient, XsdInterface, XS_PERM_NONE, XS_PERM_READ, XS_PERM_READ_WRITE,
};
use xenstore::{XsdClient, XsdInterface};
pub mod pci;
pub mod tx;
#[derive(Clone)]
pub struct XenClient {
pub struct XenClient<P: BootSetupPlatform> {
pub store: XsdClient,
call: XenCall,
pub call: XenCall,
domain_manager: Arc<BaseDomainManager<P>>,
}
#[derive(Clone, Debug)]
@ -78,21 +64,44 @@ pub struct DomainEventChannel {
pub name: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum DomainPciRdmReservePolicy {
Invalid,
#[default]
Strict,
Relaxed,
}
impl DomainPciRdmReservePolicy {
pub fn to_option_str(&self) -> &str {
match self {
DomainPciRdmReservePolicy::Invalid => "-1",
DomainPciRdmReservePolicy::Strict => "0",
DomainPciRdmReservePolicy::Relaxed => "1",
}
}
}
#[derive(Clone, Debug)]
pub struct DomainPciDevice {
pub bdf: PciBdf,
pub permissive: bool,
pub msi_translate: bool,
pub power_management: bool,
pub rdm_reserve_policy: DomainPciRdmReservePolicy,
}
#[derive(Clone, Debug)]
pub struct DomainConfig {
pub base: BaseDomainConfig,
pub backend_domid: u32,
pub name: String,
pub max_vcpus: u32,
pub mem_mb: u64,
pub kernel: Vec<u8>,
pub initrd: Vec<u8>,
pub cmdline: String,
pub disks: Vec<DomainDisk>,
pub use_console_backend: Option<String>,
pub swap_console_backend: Option<String>,
pub channels: Vec<DomainChannel>,
pub vifs: Vec<DomainNetworkInterface>,
pub filesystems: Vec<DomainFilesystem>,
pub event_channels: Vec<DomainEventChannel>,
pub pcis: Vec<DomainPciDevice>,
pub extra_keys: Vec<(String, String)>,
pub extra_rw_paths: Vec<String>,
}
@ -103,618 +112,106 @@ pub struct CreatedChannel {
pub evtchn: u32,
}
#[derive(Debug)]
pub struct CreatedDomain {
pub domid: u32,
pub channels: Vec<CreatedChannel>,
}
impl XenClient {
pub async fn open(current_domid: u32) -> Result<XenClient> {
#[allow(clippy::too_many_arguments)]
impl<P: BootSetupPlatform> XenClient<P> {
pub async fn new(current_domid: u32, platform: P) -> Result<XenClient<P>> {
let store = XsdClient::open().await?;
let call = XenCall::open(current_domid)?;
Ok(XenClient { store, call })
let call: XenCall = XenCall::open(current_domid)?;
let domain_manager = BaseDomainManager::new(call.clone(), platform).await?;
Ok(XenClient {
store,
call,
domain_manager: Arc::new(domain_manager),
})
}
pub async fn create(&self, config: &DomainConfig) -> Result<CreatedDomain> {
let mut domain = CreateDomain {
max_vcpus: config.max_vcpus,
..Default::default()
};
if cfg!(target_arch = "aarch64") {
domain.flags = XEN_DOMCTL_CDF_HVM_GUEST | XEN_DOMCTL_CDF_HAP;
}
let domid = self.call.create_domain(domain).await?;
match self.init(domid, &domain, config).await {
Ok(created) => Ok(created),
let created = self.domain_manager.create(config.base.clone()).await?;
match self.init(created.domid, config, &created).await {
Ok(_) => Ok(created),
Err(err) => {
// ignore since destroying a domain is best
// effort when an error occurs
let _ = self.destroy(domid).await;
let _ = self.domain_manager.destroy(created.domid).await;
Err(err)
}
}
}
async fn init(
&self,
domid: u32,
domain: &CreateDomain,
config: &DomainConfig,
) -> Result<CreatedDomain> {
trace!(
"XenClient init domid={} domain={:?} config={:?}",
domid,
domain,
config
);
let backend_dom_path = self.store.get_domain_path(0).await?;
let dom_path = self.store.get_domain_path(domid).await?;
let uuid_string = Uuid::from_bytes(domain.handle).to_string();
let vm_path = format!("/vm/{}", uuid_string);
pub async fn transaction(&self, domid: u32, backend_domid: u32) -> Result<ClientTransaction> {
ClientTransaction::new(&self.store, domid, backend_domid).await
}
let ro_perm = &[
XsPermission {
id: 0,
perms: XS_PERM_NONE,
},
XsPermission {
id: domid,
perms: XS_PERM_READ,
},
];
let rw_perm = &[XsPermission {
id: domid,
perms: XS_PERM_READ_WRITE,
}];
let no_perm = &[XsPermission {
id: 0,
perms: XS_PERM_NONE,
}];
{
let tx = self.store.transaction().await?;
tx.rm(dom_path.as_str()).await?;
tx.mknod(dom_path.as_str(), ro_perm).await?;
tx.rm(vm_path.as_str()).await?;
tx.mknod(vm_path.as_str(), ro_perm).await?;
tx.mknod(vm_path.as_str(), no_perm).await?;
tx.mknod(format!("{}/device", vm_path).as_str(), no_perm)
.await?;
tx.write_string(format!("{}/vm", dom_path).as_str(), &vm_path)
.await?;
tx.mknod(format!("{}/cpu", dom_path).as_str(), ro_perm)
.await?;
tx.mknod(format!("{}/memory", dom_path).as_str(), ro_perm)
.await?;
tx.mknod(format!("{}/control", dom_path).as_str(), ro_perm)
.await?;
tx.mknod(format!("{}/control/shutdown", dom_path).as_str(), rw_perm)
.await?;
tx.mknod(
format!("{}/control/feature-poweroff", dom_path).as_str(),
rw_perm,
)
async fn init(&self, domid: u32, config: &DomainConfig, created: &CreatedDomain) -> Result<()> {
trace!("xenclient init domid={} domain={:?}", domid, created);
let transaction = self.transaction(domid, config.backend_domid).await?;
transaction
.add_domain_declaration(&config.name, &config.base, created)
.await?;
tx.mknod(
format!("{}/control/feature-reboot", dom_path).as_str(),
rw_perm,
)
.await?;
tx.mknod(
format!("{}/control/feature-suspend", dom_path).as_str(),
rw_perm,
)
.await?;
tx.mknod(format!("{}/control/sysrq", dom_path).as_str(), rw_perm)
.await?;
tx.mknod(format!("{}/data", dom_path).as_str(), rw_perm)
.await?;
tx.mknod(format!("{}/drivers", dom_path).as_str(), rw_perm)
.await?;
tx.mknod(format!("{}/feature", dom_path).as_str(), rw_perm)
.await?;
tx.mknod(format!("{}/attr", dom_path).as_str(), rw_perm)
.await?;
tx.mknod(format!("{}/error", dom_path).as_str(), rw_perm)
.await?;
tx.write_string(
format!("{}/uuid", vm_path).as_str(),
&Uuid::from_bytes(domain.handle).to_string(),
)
.await?;
tx.write_string(format!("{}/name", dom_path).as_str(), &config.name)
.await?;
tx.write_string(format!("{}/name", vm_path).as_str(), &config.name)
.await?;
for (key, value) in &config.extra_keys {
tx.write_string(format!("{}/{}", dom_path, key).as_str(), value)
.await?;
}
for path in &config.extra_rw_paths {
tx.mknod(format!("{}/{}", dom_path, path).as_str(), rw_perm)
.await?;
}
tx.commit().await?;
}
self.call.set_max_vcpus(domid, config.max_vcpus).await?;
self.call.set_max_mem(domid, config.mem_mb * 1024).await?;
let image_loader = ElfImageLoader::load_file_kernel(&config.kernel)?;
let xenstore_evtchn: u32;
let xenstore_mfn: u64;
let p2m: Vec<u64>;
let mut state: BootState;
{
let mut boot = BootSetup::new(&self.call, domid);
#[cfg(target_arch = "x86_64")]
let mut arch = Box::new(X86BootSetup::new()) as Box<dyn ArchBootSetup + Send + Sync>;
#[cfg(target_arch = "aarch64")]
let mut arch = Box::new(Arm64BootSetup::new()) as Box<dyn ArchBootSetup + Send + Sync>;
state = boot
.initialize(
&mut arch,
&image_loader,
&config.initrd,
config.max_vcpus,
config.mem_mb,
1,
)
.await?;
boot.boot(&mut arch, &mut state, &config.cmdline).await?;
xenstore_evtchn = state.store_evtchn;
xenstore_mfn = boot.phys.p2m[state.xenstore_segment.pfn as usize];
p2m = boot.phys.p2m;
}
{
let tx = self.store.transaction().await?;
tx.write_string(format!("{}/image/os_type", vm_path).as_str(), "linux")
.await?;
tx.write_string(
format!("{}/image/cmdline", vm_path).as_str(),
&config.cmdline,
)
.await?;
tx.write_string(
format!("{}/memory/static-max", dom_path).as_str(),
&(config.mem_mb * 1024).to_string(),
)
.await?;
tx.write_string(
format!("{}/memory/target", dom_path).as_str(),
&(config.mem_mb * 1024).to_string(),
)
.await?;
tx.write_string(format!("{}/memory/videoram", dom_path).as_str(), "0")
.await?;
tx.write_string(format!("{}/domid", dom_path).as_str(), &domid.to_string())
.await?;
tx.write_string(
format!("{}/store/port", dom_path).as_str(),
&xenstore_evtchn.to_string(),
)
.await?;
tx.write_string(
format!("{}/store/ring-ref", dom_path).as_str(),
&xenstore_mfn.to_string(),
)
.await?;
for i in 0..config.max_vcpus {
let path = format!("{}/cpu/{}", dom_path, i);
tx.mkdir(&path).await?;
tx.set_perms(&path, ro_perm).await?;
let path = format!("{}/cpu/{}/availability", dom_path, i);
tx.write_string(&path, "online").await?;
tx.set_perms(&path, ro_perm).await?;
}
tx.commit().await?;
}
transaction.commit().await?;
if !self
.store
.introduce_domain(domid, xenstore_mfn, xenstore_evtchn)
.introduce_domain(domid, created.store_mfn, created.store_evtchn)
.await?
{
return Err(Error::IntroduceDomainFailed);
}
self.console_device_add(
&DomainChannel {
typ: config
.use_console_backend
.clone()
.unwrap_or("xenconsoled".to_string())
.to_string(),
initialized: true,
},
&p2m,
&state,
&dom_path,
&backend_dom_path,
config.backend_domid,
domid,
0,
)
.await?;
let transaction = self.transaction(domid, config.backend_domid).await?;
transaction
.add_channel_device(
created,
0,
&DomainChannel {
typ: config
.swap_console_backend
.clone()
.unwrap_or("xenconsoled".to_string())
.to_string(),
initialized: true,
},
)
.await?;
let mut channels: Vec<CreatedChannel> = Vec::new();
for (index, channel) in config.channels.iter().enumerate() {
let (Some(ring_ref), Some(evtchn)) = self
.console_device_add(
channel,
&p2m,
&state,
&dom_path,
&backend_dom_path,
config.backend_domid,
domid,
index + 1,
)
.await?
else {
continue;
};
channels.push(CreatedChannel { ring_ref, evtchn });
transaction
.add_channel_device(created, index + 1, channel)
.await?;
}
for (index, disk) in config.disks.iter().enumerate() {
self.disk_device_add(
&dom_path,
&backend_dom_path,
config.backend_domid,
domid,
index,
disk,
)
.await?;
transaction.add_vbd_device(index, disk).await?;
}
for (index, filesystem) in config.filesystems.iter().enumerate() {
self.fs_9p_device_add(
&dom_path,
&backend_dom_path,
config.backend_domid,
domid,
index,
filesystem,
)
.await?;
transaction.add_9pfs_device(index, filesystem).await?;
}
for (index, vif) in config.vifs.iter().enumerate() {
self.vif_device_add(
&dom_path,
&backend_dom_path,
config.backend_domid,
domid,
index,
vif,
)
.await?;
transaction.add_vif_device(index, vif).await?;
}
for channel in &config.event_channels {
let id = self
.call
.evtchn_alloc_unbound(domid, config.backend_domid)
.await?;
let channel_path = format!("{}/evtchn/{}", dom_path, channel.name);
self.store
.write_string(&format!("{}/name", channel_path), &channel.name)
.await?;
self.store
.write_string(&format!("{}/channel", channel_path), &id.to_string())
for (index, pci) in config.pcis.iter().enumerate() {
transaction
.add_pci_device(&self.call, index, config.pcis.len(), pci)
.await?;
}
for (key, value) in &config.extra_keys {
transaction.write_key(key, value).await?;
}
for key in &config.extra_rw_paths {
transaction.add_rw_path(key).await?;
}
transaction.commit().await?;
self.call.unpause_domain(domid).await?;
Ok(CreatedDomain { domid, channels })
}
async fn disk_device_add(
&self,
dom_path: &str,
backend_dom_path: &str,
backend_domid: u32,
domid: u32,
index: usize,
disk: &DomainDisk,
) -> Result<()> {
let id = (202 << 8) | (index << 4) as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", domid.to_string()),
("online", "1".to_string()),
("removable", "0".to_string()),
("bootable", "1".to_string()),
("state", "1".to_string()),
("dev", disk.vdev.to_string()),
("type", "phy".to_string()),
("mode", if disk.writable { "w" } else { "r" }.to_string()),
("device-type", "disk".to_string()),
("discard-enable", "0".to_string()),
("specification", "xen".to_string()),
("physical-device-path", disk.block.path.to_string()),
(
"physical-device",
format!("{:02x}:{:02x}", disk.block.major, disk.block.minor),
),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", backend_domid.to_string()),
("state", "1".to_string()),
("virtual-device", id.to_string()),
("device-type", "disk".to_string()),
("trusted", "1".to_string()),
("protocol", "x86_64-abi".to_string()),
];
self.device_add(
"vbd",
id,
dom_path,
backend_dom_path,
backend_domid,
domid,
frontend_items,
backend_items,
)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments, clippy::unnecessary_unwrap)]
async fn console_device_add(
&self,
channel: &DomainChannel,
p2m: &[u64],
state: &BootState,
dom_path: &str,
backend_dom_path: &str,
backend_domid: u32,
domid: u32,
index: usize,
) -> Result<(Option<u64>, Option<u32>)> {
let console = state.consoles.get(index);
let port = console.map(|x| x.0);
let ring = console.map(|x| p2m[x.1.pfn as usize]);
let mut backend_entries = vec![
("frontend-id", domid.to_string()),
("online", "1".to_string()),
("protocol", "vt100".to_string()),
];
let mut frontend_entries = vec![
("backend-id", backend_domid.to_string()),
("limit", "1048576".to_string()),
("output", "pty".to_string()),
("tty", "".to_string()),
];
frontend_entries.push(("type", channel.typ.clone()));
backend_entries.push(("type", channel.typ.clone()));
if port.is_some() && ring.is_some() {
if channel.typ != "xenconsoled" {
frontend_entries.push(("state", "1".to_string()));
}
frontend_entries.extend_from_slice(&[
("port", port.unwrap().to_string()),
("ring-ref", ring.unwrap().to_string()),
]);
} else {
frontend_entries.extend_from_slice(&[
("state", "1".to_string()),
("protocol", "vt100".to_string()),
]);
}
if channel.initialized {
backend_entries.push(("state", "4".to_string()));
} else {
backend_entries.push(("state", "1".to_string()));
}
self.device_add(
"console",
index as u64,
dom_path,
backend_dom_path,
backend_domid,
domid,
frontend_entries,
backend_entries,
)
.await?;
Ok((ring, port))
}
async fn fs_9p_device_add(
&self,
dom_path: &str,
backend_dom_path: &str,
backend_domid: u32,
domid: u32,
index: usize,
filesystem: &DomainFilesystem,
) -> Result<()> {
let id = 90 + index as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("path", filesystem.path.to_string()),
("security-model", "none".to_string()),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", backend_domid.to_string()),
("state", "1".to_string()),
("tag", filesystem.tag.to_string()),
];
self.device_add(
"9pfs",
id,
dom_path,
backend_dom_path,
backend_domid,
domid,
frontend_items,
backend_items,
)
.await?;
Ok(())
}
async fn vif_device_add(
&self,
dom_path: &str,
backend_dom_path: &str,
backend_domid: u32,
domid: u32,
index: usize,
vif: &DomainNetworkInterface,
) -> Result<()> {
let id = 20 + index as u64;
let mut backend_items: Vec<(&str, String)> = vec![
("frontend-id", domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("mac", vif.mac.to_string()),
("mtu", vif.mtu.to_string()),
("type", "vif".to_string()),
("handle", id.to_string()),
];
if vif.bridge.is_some() {
backend_items.extend_from_slice(&[("bridge", vif.bridge.clone().unwrap())]);
}
if vif.script.is_some() {
backend_items.extend_from_slice(&[
("script", vif.script.clone().unwrap()),
("hotplug-status", "".to_string()),
]);
} else {
backend_items.extend_from_slice(&[
("script", "".to_string()),
("hotplug-status", "connected".to_string()),
]);
}
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", backend_domid.to_string()),
("state", "1".to_string()),
("mac", vif.mac.to_string()),
("trusted", "1".to_string()),
("mtu", vif.mtu.to_string()),
];
self.device_add(
"vif",
id,
dom_path,
backend_dom_path,
backend_domid,
domid,
frontend_items,
backend_items,
)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn device_add(
&self,
typ: &str,
id: u64,
dom_path: &str,
backend_dom_path: &str,
backend_domid: u32,
domid: u32,
frontend_items: Vec<(&str, String)>,
backend_items: Vec<(&str, String)>,
) -> Result<()> {
let console_zero = typ == "console" && id == 0;
let frontend_path = if console_zero {
format!("{}/console", dom_path)
} else {
format!("{}/device/{}/{}", dom_path, typ, id)
};
let backend_path = format!("{}/backend/{}/{}/{}", backend_dom_path, typ, domid, id);
let mut backend_items: Vec<(&str, String)> = backend_items.clone();
let mut frontend_items: Vec<(&str, String)> = frontend_items.clone();
backend_items.push(("frontend", frontend_path.clone()));
frontend_items.push(("backend", backend_path.clone()));
let frontend_perms = &[
XsPermission {
id: domid,
perms: XS_PERM_NONE,
},
XsPermission {
id: backend_domid,
perms: XS_PERM_READ,
},
];
let backend_perms = &[
XsPermission {
id: backend_domid,
perms: XS_PERM_NONE,
},
XsPermission {
id: domid,
perms: XS_PERM_READ,
},
];
let tx = self.store.transaction().await?;
tx.mknod(&frontend_path, frontend_perms).await?;
for (p, value) in &frontend_items {
let path = format!("{}/{}", frontend_path, *p);
tx.write_string(&path, value).await?;
if !console_zero {
tx.set_perms(&path, frontend_perms).await?;
}
}
tx.mknod(&backend_path, backend_perms).await?;
for (p, value) in &backend_items {
let path = format!("{}/{}", backend_path, *p);
tx.write_string(&path, value).await?;
}
tx.commit().await?;
Ok(())
}
pub async fn destroy(&self, domid: u32) -> Result<()> {
if let Err(err) = self.destroy_store(domid).await {
warn!("failed to destroy store for domain {}: {}", domid, err);
}
self.call.destroy_domain(domid).await?;
let _ = self.destroy_store(domid).await;
self.domain_manager.destroy(domid).await?;
Ok(())
}
@ -809,21 +306,4 @@ impl XenClient {
tx.commit().await?;
Ok(())
}
pub async fn get_console_path(&self, domid: u32) -> Result<String> {
let dom_path = self.store.get_domain_path(domid).await?;
let console_tty_path = format!("{}/console/tty", dom_path);
let mut tty: Option<String> = None;
for _ in 0..5 {
tty = self.store.read_string(&console_tty_path).await?;
if tty.is_some() {
break;
}
tokio::time::sleep(Duration::from_millis(200)).await;
}
let Some(tty) = tty else {
return Err(Error::TtyNotFound);
};
Ok(tty)
}
}

View File

@ -0,0 +1,313 @@
use regex::Regex;
use std::{fmt::Display, path::PathBuf, str::FromStr};
use tokio::fs;
use crate::error::{Error, Result};
const PCIBACK_SYSFS_PATH: &str = "/sys/bus/pci/drivers/pciback";
const PCI_BDF_REGEX: &str = r"^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2}).([0-9a-f]{1})$";
const PCI_BDF_SHORT_REGEX: &str = r"^([0-9a-f]{2}):([0-9a-f]{2}).([0-9a-f]{1})$";
const PCI_BDF_VDEFN_REGEX: &str =
r"^([0-9a-f]{4}):([0-9a-f]{2}):([0-9a-f]{2}).([0-9a-f]{1})@([0-9a-f]{2})$";
const FLAG_PCI_BAR_IO: u64 = 0x1;
#[derive(Clone)]
pub struct XenPciBackend {
path: PathBuf,
}
impl Default for XenPciBackend {
fn default() -> Self {
Self::new()
}
}
impl XenPciBackend {
pub fn new() -> Self {
Self {
path: PathBuf::from(PCIBACK_SYSFS_PATH),
}
}
pub async fn is_loaded(&self) -> Result<bool> {
Ok(fs::try_exists(&self.path).await?)
}
pub async fn list_devices(&self) -> Result<Vec<PciBdf>> {
let mut devices = Vec::new();
let mut dir = fs::read_dir(&self.path).await?;
while let Some(entry) = dir.next_entry().await? {
let file_name_string = entry.file_name().to_string_lossy().to_string();
let Some(bdf) = PciBdf::from_str(&file_name_string).ok() else {
continue;
};
devices.push(bdf);
}
Ok(devices)
}
pub async fn is_assigned(&self, bdf: &PciBdf) -> Result<bool> {
let mut path = self.path.clone();
path.push(bdf.to_string());
Ok(fs::try_exists(path).await?)
}
pub async fn read_irq(&self, bdf: &PciBdf) -> Result<Option<u32>> {
let mut path: PathBuf = self.path.clone();
path.push(bdf.to_string());
path.push("irq");
if !path.exists() {
return Ok(None);
}
let content = fs::read_to_string(&path).await?;
Ok(u32::from_str(content.trim()).ok())
}
pub async fn read_resources(&self, bdf: &PciBdf) -> Result<Vec<PciMemoryResource>> {
let mut resources = Vec::new();
let mut path: PathBuf = self.path.clone();
path.push(bdf.to_string());
path.push("resource");
let content = fs::read_to_string(&path).await?;
for line in content.lines() {
let parts = line.split(' ').collect::<Vec<_>>();
if parts.len() != 3 {
continue;
}
let Some(start) = parts.first() else {
continue;
};
let Some(end) = parts.get(1) else {
continue;
};
let Some(flags) = parts.get(2) else {
continue;
};
if !start.starts_with("0x") || !end.starts_with("0x") || !flags.starts_with("0x") {
continue;
}
let start = &start[2..];
let end = &end[2..];
let flags = &flags[2..];
let Some(start) = u64::from_str_radix(start, 16).ok() else {
continue;
};
let Some(end) = u64::from_str_radix(end, 16).ok() else {
continue;
};
let Some(flags) = u64::from_str_radix(flags, 16).ok() else {
continue;
};
if start > 0 {
resources.push(PciMemoryResource::new(start, end, flags));
}
}
Ok(resources)
}
pub async fn enable_permissive(&self, bdf: &PciBdf) -> Result<()> {
let mut path: PathBuf = self.path.clone();
path.push("permissive");
fs::write(path, bdf.to_string()).await?;
Ok(())
}
pub async fn has_slot(&self, bdf: &PciBdf) -> Result<bool> {
let mut slots_path = self.path.clone();
slots_path.push("slots");
let content = fs::read_to_string(&slots_path).await?;
for line in content.lines() {
if let Ok(slot) = PciBdf::from_str(line) {
if slot == *bdf {
return Ok(true);
}
}
}
Ok(false)
}
pub async fn reset(&self, bdf: &PciBdf) -> Result<()> {
let mut path: PathBuf = self.path.clone();
path.push(bdf.to_string());
path.push("reset");
let _ = fs::write(path, "1\n").await;
Ok(())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct PciBdf {
pub domain: Option<u32>,
pub bus: u16,
pub device: u16,
pub function: u16,
pub vdefn: Option<u16>,
}
impl PciBdf {
pub fn new(
domain: Option<u32>,
bus: u16,
device: u16,
function: u16,
vdefn: Option<u16>,
) -> Self {
Self {
domain,
bus,
device,
function,
vdefn,
}
}
pub fn with_domain(&self, domain: u32) -> PciBdf {
PciBdf {
domain: Some(domain),
bus: self.bus,
device: self.device,
function: self.function,
vdefn: self.vdefn,
}
}
pub fn encode(&self) -> u32 {
let mut value = self.domain.unwrap_or(0) << 16u32;
value |= ((self.bus & 0xff) << 8u32) as u32;
value |= ((self.device & 0x1f) << 3u32) as u32;
value |= (self.function & 0x7) as u32;
value
}
}
impl FromStr for PciBdf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let pci_bdf_regex = Regex::from_str(PCI_BDF_REGEX)?;
let pci_bdf_vdefn_regex = Regex::from_str(PCI_BDF_VDEFN_REGEX)?;
let pci_bdf_short_regex = Regex::from_str(PCI_BDF_SHORT_REGEX)?;
if let Some(pci_bdf_captures) = pci_bdf_regex.captures(s) {
let domain = pci_bdf_captures
.get(1)
.ok_or_else(|| Error::GenericError("capture group 1 did not exist".to_string()))?;
let bus = pci_bdf_captures
.get(2)
.ok_or_else(|| Error::GenericError("capture group 2 did not exist".to_string()))?;
let device = pci_bdf_captures
.get(3)
.ok_or_else(|| Error::GenericError("capture group 3 did not exist".to_string()))?;
let function = pci_bdf_captures
.get(4)
.ok_or_else(|| Error::GenericError("capture group 4 did not exist".to_string()))?;
let domain = u32::from_str_radix(domain.as_str(), 16)?;
let bus = u16::from_str_radix(bus.as_str(), 16)?;
let device = u16::from_str_radix(device.as_str(), 16)?;
let function = u16::from_str_radix(function.as_str(), 16)?;
Ok(PciBdf::new(Some(domain), bus, device, function, None))
} else if let Some(pci_bdf_vdefn_captures) = pci_bdf_vdefn_regex.captures(s) {
let domain = pci_bdf_vdefn_captures
.get(1)
.ok_or_else(|| Error::GenericError("capture group 1 did not exist".to_string()))?;
let bus = pci_bdf_vdefn_captures
.get(2)
.ok_or_else(|| Error::GenericError("capture group 2 did not exist".to_string()))?;
let device = pci_bdf_vdefn_captures
.get(3)
.ok_or_else(|| Error::GenericError("capture group 3 did not exist".to_string()))?;
let function = pci_bdf_vdefn_captures
.get(4)
.ok_or_else(|| Error::GenericError("capture group 4 did not exist".to_string()))?;
let vdefn = pci_bdf_vdefn_captures
.get(5)
.ok_or_else(|| Error::GenericError("capture group 5 did not exist".to_string()))?;
let domain = u32::from_str_radix(domain.as_str(), 16)?;
let bus = u16::from_str_radix(bus.as_str(), 16)?;
let device = u16::from_str_radix(device.as_str(), 16)?;
let function = u16::from_str_radix(function.as_str(), 16)?;
let vdefn = u16::from_str_radix(vdefn.as_str(), 16)?;
Ok(PciBdf::new(
Some(domain),
bus,
device,
function,
Some(vdefn),
))
} else if let Some(pci_bdf_short_captures) = pci_bdf_short_regex.captures(s) {
let bus = pci_bdf_short_captures
.get(1)
.ok_or_else(|| Error::GenericError("capture group 1 did not exist".to_string()))?;
let device = pci_bdf_short_captures
.get(2)
.ok_or_else(|| Error::GenericError("capture group 2 did not exist".to_string()))?;
let function = pci_bdf_short_captures
.get(3)
.ok_or_else(|| Error::GenericError("capture group 3 did not exist".to_string()))?;
let bus = u16::from_str_radix(bus.as_str(), 16)?;
let device = u16::from_str_radix(device.as_str(), 16)?;
let function = u16::from_str_radix(function.as_str(), 16)?;
Ok(PciBdf::new(None, bus, device, function, None))
} else {
Err(Error::InvalidPciBdfString)
}
}
}
impl Display for PciBdf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(domain) = self.domain {
if let Some(vdefn) = self.vdefn {
write!(
f,
"{:04x}:{:02x}:{:02x}.{:01x}@{:02x}",
domain, self.bus, self.device, self.function, vdefn
)
} else {
write!(
f,
"{:04x}:{:02x}:{:02x}.{:01x}",
domain, self.bus, self.device, self.function
)
}
} else {
write!(
f,
"{:02x}:{:02x}.{:01x}",
self.bus, self.device, self.function
)
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PciMemoryResource {
pub start: u64,
pub end: u64,
pub flags: u64,
}
impl PciMemoryResource {
pub fn new(start: u64, end: u64, flags: u64) -> PciMemoryResource {
PciMemoryResource { start, end, flags }
}
pub fn is_bar_io(&self) -> bool {
(self.flags & FLAG_PCI_BAR_IO) != 0
}
pub fn size(&self) -> u64 {
(self.end - self.start) + 1
}
}

View File

@ -0,0 +1,573 @@
use indexmap::IndexMap;
use xencall::{sys::DOMCTL_DEV_RDM_RELAXED, XenCall};
use xenplatform::{
domain::{BaseDomainConfig, CreatedDomain},
sys::XEN_PAGE_SHIFT,
};
use xenstore::{
XsPermission, XsdClient, XsdInterface, XsdTransaction, XS_PERM_NONE, XS_PERM_READ,
XS_PERM_READ_WRITE,
};
use crate::{
error::{Error, Result},
pci::XenPciBackend,
DomainChannel, DomainDisk, DomainFilesystem, DomainNetworkInterface, DomainPciDevice,
DomainPciRdmReservePolicy,
};
pub struct ClientTransaction {
tx: XsdTransaction,
abort: bool,
domid: u32,
dom_path: String,
backend_domid: u32,
backend_dom_path: String,
}
impl ClientTransaction {
pub async fn new(store: &XsdClient, domid: u32, backend_domid: u32) -> Result<Self> {
let backend_dom_path = store.get_domain_path(0).await?;
let dom_path = store.get_domain_path(domid).await?;
Ok(ClientTransaction {
tx: store.transaction().await?,
abort: true,
domid,
dom_path,
backend_domid,
backend_dom_path,
})
}
pub async fn add_domain_declaration(
&self,
name: impl AsRef<str>,
base: &BaseDomainConfig,
created: &CreatedDomain,
) -> Result<()> {
let vm_path = format!("/vm/{}", base.uuid);
let ro_perm = &[
XsPermission {
id: 0,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.domid,
perms: XS_PERM_READ,
},
];
let no_perm = &[XsPermission {
id: 0,
perms: XS_PERM_NONE,
}];
let rw_perm = &[XsPermission {
id: self.domid,
perms: XS_PERM_READ_WRITE,
}];
self.tx.rm(&self.dom_path).await?;
self.tx.mknod(&self.dom_path, ro_perm).await?;
self.tx.rm(&vm_path).await?;
self.tx.mknod(&vm_path, ro_perm).await?;
self.tx.mknod(&vm_path, no_perm).await?;
self.tx
.mknod(format!("{}/device", vm_path).as_str(), no_perm)
.await?;
self.tx
.write_string(format!("{}/vm", self.dom_path).as_str(), &vm_path)
.await?;
self.tx
.mknod(format!("{}/cpu", self.dom_path).as_str(), ro_perm)
.await?;
self.tx
.mknod(format!("{}/memory", self.dom_path).as_str(), ro_perm)
.await?;
self.tx
.mknod(format!("{}/control", self.dom_path).as_str(), ro_perm)
.await?;
self.tx
.mknod(
format!("{}/control/shutdown", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(
format!("{}/control/feature-poweroff", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(
format!("{}/control/feature-reboot", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(
format!("{}/control/feature-suspend", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(format!("{}/control/sysrq", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/data", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/drivers", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/feature", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/attr", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/error", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.write_string(format!("{}/uuid", vm_path).as_str(), &base.uuid.to_string())
.await?;
self.tx
.write_string(format!("{}/name", self.dom_path).as_str(), name.as_ref())
.await?;
self.tx
.write_string(format!("{}/name", vm_path).as_str(), name.as_ref())
.await?;
self.tx
.write_string(format!("{}/image/os_type", vm_path).as_str(), "linux")
.await?;
self.tx
.write_string(format!("{}/image/cmdline", vm_path).as_str(), &base.cmdline)
.await?;
self.tx
.write_string(
format!("{}/memory/static-max", self.dom_path).as_str(),
&(base.mem_mb * 1024).to_string(),
)
.await?;
self.tx
.write_string(
format!("{}/memory/target", self.dom_path).as_str(),
&(base.mem_mb * 1024).to_string(),
)
.await?;
self.tx
.write_string(format!("{}/memory/videoram", self.dom_path).as_str(), "0")
.await?;
self.tx
.write_string(
format!("{}/domid", self.dom_path).as_str(),
&created.domid.to_string(),
)
.await?;
self.tx
.write_string(format!("{}/type", self.dom_path).as_str(), "PV")
.await?;
self.tx
.write_string(
format!("{}/store/port", self.dom_path).as_str(),
&created.store_evtchn.to_string(),
)
.await?;
self.tx
.write_string(
format!("{}/store/ring-ref", self.dom_path).as_str(),
&created.store_mfn.to_string(),
)
.await?;
for i in 0..base.max_vcpus {
let path = format!("{}/cpu/{}", self.dom_path, i);
self.tx.mkdir(&path).await?;
self.tx.set_perms(&path, ro_perm).await?;
let path = format!("{}/cpu/{}/availability", self.dom_path, i);
self.tx.write_string(&path, "online").await?;
self.tx.set_perms(&path, ro_perm).await?;
}
Ok(())
}
pub async fn write_key(&self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
self.tx
.write_string(
&format!("{}/{}", self.dom_path, key.as_ref()),
value.as_ref(),
)
.await?;
Ok(())
}
pub async fn add_rw_path(&self, key: impl AsRef<str>) -> Result<()> {
let rw_perm = &[XsPermission {
id: self.domid,
perms: XS_PERM_READ_WRITE,
}];
self.tx
.mknod(&format!("{}/{}", self.dom_path, key.as_ref()), rw_perm)
.await?;
Ok(())
}
pub async fn add_device(
&self,
typ: impl AsRef<str>,
id: u64,
frontend_items: Vec<(&str, String)>,
backend_items: Vec<(&str, String)>,
) -> Result<()> {
let console_zero = typ.as_ref() == "console" && id == 0;
let frontend_path = if console_zero {
format!("{}/console", self.dom_path)
} else {
format!("{}/device/{}/{}", self.dom_path, typ.as_ref(), id)
};
let backend_path = format!(
"{}/backend/{}/{}/{}",
self.backend_dom_path,
typ.as_ref(),
self.domid,
id
);
let mut backend_items: Vec<(&str, String)> = backend_items.clone();
let mut frontend_items: Vec<(&str, String)> = frontend_items.clone();
backend_items.push(("frontend", frontend_path.clone()));
frontend_items.push(("backend", backend_path.clone()));
let frontend_perms = &[
XsPermission {
id: self.domid,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.backend_domid,
perms: XS_PERM_READ,
},
];
let backend_perms = &[
XsPermission {
id: self.backend_domid,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.domid,
perms: XS_PERM_READ,
},
];
self.tx.mknod(&frontend_path, frontend_perms).await?;
for (p, value) in &frontend_items {
let path = format!("{}/{}", frontend_path, *p);
self.tx.write_string(&path, value).await?;
if !console_zero {
self.tx.set_perms(&path, frontend_perms).await?;
}
}
self.tx.mknod(&backend_path, backend_perms).await?;
for (p, value) in &backend_items {
let path = format!("{}/{}", backend_path, *p);
self.tx.write_string(&path, value).await?;
}
Ok(())
}
pub async fn add_vbd_device(&self, index: usize, disk: &DomainDisk) -> Result<()> {
let id = (202 << 8) | (index << 4) as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("removable", "0".to_string()),
("bootable", "1".to_string()),
("state", "1".to_string()),
("dev", disk.vdev.to_string()),
("type", "phy".to_string()),
("mode", if disk.writable { "w" } else { "r" }.to_string()),
("device-type", "disk".to_string()),
("discard-enable", "0".to_string()),
("specification", "xen".to_string()),
("physical-device-path", disk.block.path.to_string()),
(
"physical-device",
format!("{:02x}:{:02x}", disk.block.major, disk.block.minor),
),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
("virtual-device", id.to_string()),
("device-type", "disk".to_string()),
("trusted", "1".to_string()),
("protocol", "x86_64-abi".to_string()),
];
self.add_device("vbd", id, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_vif_device(&self, index: usize, vif: &DomainNetworkInterface) -> Result<()> {
let id = 20 + index as u64;
let mut backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("mac", vif.mac.to_string()),
("mtu", vif.mtu.to_string()),
("type", "vif".to_string()),
("handle", id.to_string()),
];
if vif.bridge.is_some() {
backend_items.extend_from_slice(&[("bridge", vif.bridge.clone().unwrap())]);
}
if vif.script.is_some() {
backend_items.extend_from_slice(&[
("script", vif.script.clone().unwrap()),
("hotplug-status", "".to_string()),
]);
} else {
backend_items.extend_from_slice(&[
("script", "".to_string()),
("hotplug-status", "connected".to_string()),
]);
}
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
("mac", vif.mac.to_string()),
("trusted", "1".to_string()),
("mtu", vif.mtu.to_string()),
];
self.add_device("vif", id, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_9pfs_device(&self, index: usize, filesystem: &DomainFilesystem) -> Result<()> {
let id = 90 + index as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("path", filesystem.path.to_string()),
("security-model", "none".to_string()),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
("tag", filesystem.tag.to_string()),
];
self.add_device("9pfs", id, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_channel_device(
&self,
domain: &CreatedDomain,
index: usize,
channel: &DomainChannel,
) -> Result<()> {
let port = domain.console_evtchn;
let ring = domain.console_mfn;
let mut backend_items = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("protocol", "vt100".to_string()),
];
let mut frontend_items = vec![
("backend-id", self.backend_domid.to_string()),
("limit", "1048576".to_string()),
("output", "pty".to_string()),
("tty", "".to_string()),
];
frontend_items.push(("type", channel.typ.clone()));
backend_items.push(("type", channel.typ.clone()));
if index == 0 {
if channel.typ != "xenconsoled" {
frontend_items.push(("state", "1".to_string()));
}
frontend_items
.extend_from_slice(&[("port", port.to_string()), ("ring-ref", ring.to_string())]);
} else {
frontend_items.extend_from_slice(&[
("state", "1".to_string()),
("protocol", "vt100".to_string()),
]);
}
if channel.initialized {
backend_items.push(("state", "4".to_string()));
} else {
backend_items.push(("state", "1".to_string()));
}
self.add_device("console", index as u64, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_pci_device(
&self,
call: &XenCall,
index: usize,
device_count: usize,
device: &DomainPciDevice,
) -> Result<()> {
let backend = XenPciBackend::new();
if !backend.is_assigned(&device.bdf).await? {
return Err(Error::PciDeviceNotAssignable(device.bdf));
}
let resources = backend.read_resources(&device.bdf).await?;
for resource in resources {
if resource.is_bar_io() {
call.ioport_permission(
self.domid,
resource.start as u32,
resource.size() as u32,
true,
)
.await?;
} else {
call.iomem_permission(
self.domid,
resource.start >> XEN_PAGE_SHIFT,
(resource.size() + (XEN_PAGE_SHIFT - 1)) >> XEN_PAGE_SHIFT,
true,
)
.await?;
}
}
if let Some(irq) = backend.read_irq(&device.bdf).await? {
let irq = call.map_pirq(self.domid, irq as isize, None).await?;
call.irq_permission(self.domid, irq, true).await?;
}
backend.reset(&device.bdf).await?;
call.assign_device(
self.domid,
device.bdf.encode(),
if device.rdm_reserve_policy == DomainPciRdmReservePolicy::Relaxed {
DOMCTL_DEV_RDM_RELAXED
} else {
0
},
)
.await?;
if device.permissive {
backend.enable_permissive(&device.bdf).await?;
}
let id = 60;
if index == 0 {
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("num_devs", device_count.to_string()),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
];
self.add_device("pci", id, frontend_items, backend_items)
.await?;
}
let backend_path = format!(
"{}/backend/{}/{}/{}",
self.backend_dom_path, "pci", self.domid, id
);
self.tx
.write_string(
format!("{}/key-{}", backend_path, index),
&device.bdf.to_string(),
)
.await?;
self.tx
.write_string(
format!("{}/dev-{}", backend_path, index),
&device.bdf.to_string(),
)
.await?;
if let Some(vdefn) = device.bdf.vdefn {
self.tx
.write_string(
format!("{}/vdefn-{}", backend_path, index),
&format!("{:#x}", vdefn),
)
.await?;
}
let mut options = IndexMap::new();
options.insert("permissive", if device.permissive { "1" } else { "0" });
options.insert("rdm_policy", device.rdm_reserve_policy.to_option_str());
options.insert("msitranslate", if device.msi_translate { "1" } else { "0" });
options.insert(
"power_mgmt",
if device.power_management { "1" } else { "0" },
);
let options = options
.into_iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<_>>()
.join(",");
self.tx
.write_string(format!("{}/opts-{}", backend_path, index), &options)
.await?;
Ok(())
}
pub async fn commit(mut self) -> Result<()> {
self.abort = false;
self.tx.commit().await?;
Ok(())
}
}
impl Drop for ClientTransaction {
fn drop(&mut self) {
if !self.abort {
return;
}
let tx = self.tx.clone();
tokio::task::spawn(async move {
let _ = tx.abort().await;
});
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-xenevtchn"
description = "An implementation of xen evtchn for krata."
description = "An implementation of Xen evtchn for krata."
license.workspace = true
version.workspace = true
homepage.workspace = true

View File

@ -1,6 +1,6 @@
[package]
name = "krata-xengnt"
description = "An implementation of xen grant interfaces for krata."
description = "An implementation of Xen grant interfaces for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true

View File

@ -0,0 +1,34 @@
[package]
name = "krata-xenplatform"
description = "An implementation of Xen platforms for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
async-trait = { workspace = true }
c2rust-bitfields = { workspace = true }
elf = { workspace = true }
flate2 = { workspace = true }
indexmap = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
krata-xencall = { path = "../xencall", version = "^0.0.12" }
memchr = { workspace = true }
nix = { workspace = true }
regex = { workspace = true }
slice-copy = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
uuid = { workspace = true }
xz2 = { workspace = true }
[dev-dependencies]
env_logger = { workspace = true }
tokio = { workspace = true }
[lib]
name = "xenplatform"

View File

@ -0,0 +1,323 @@
use std::slice;
use log::debug;
use slice_copy::copy;
use xencall::{sys::CreateDomain, XenCall};
use crate::{
error::{Error, Result},
mem::PhysicalPages,
sys::XEN_PAGE_SHIFT,
};
pub struct BootSetup<I: BootImageLoader, P: BootSetupPlatform> {
pub call: XenCall,
pub domid: u32,
pub platform: P,
pub image_loader: I,
pub dtb: Option<Vec<u8>>,
}
#[derive(Debug, Default, Clone)]
pub struct DomainSegment {
pub vstart: u64,
pub vend: u64,
pub pfn: u64,
pub addr: u64,
pub size: u64,
pub pages: u64,
}
pub struct BootDomain {
pub domid: u32,
pub call: XenCall,
pub page_size: u64,
pub virt_alloc_end: u64,
pub pfn_alloc_end: u64,
pub virt_pgtab_end: u64,
pub total_pages: u64,
pub target_pages: u64,
pub max_vcpus: u32,
pub image_info: BootImageInfo,
pub phys: PhysicalPages,
pub store_evtchn: u32,
pub store_mfn: u64,
pub initrd_segment: DomainSegment,
pub console_evtchn: u32,
pub console_mfn: u64,
pub cmdline: String,
}
impl BootDomain {
pub async fn alloc_module(&mut self, buffer: &[u8]) -> Result<DomainSegment> {
let segment = self.alloc_segment(0, buffer.len() as u64).await?;
let slice = unsafe { slice::from_raw_parts_mut(segment.addr as *mut u8, buffer.len()) };
copy(slice, buffer);
Ok(segment)
}
pub async fn alloc_segment(&mut self, start: u64, size: u64) -> Result<DomainSegment> {
debug!("alloc_segment {:#x} {:#x}", start, size);
if start > 0 {
self.alloc_padding_pages(start)?;
}
let local_page_size: u32 = (1i64 << XEN_PAGE_SHIFT) as u32;
let pages = (size + local_page_size as u64 - 1) / local_page_size as u64;
let start = self.virt_alloc_end;
let mut segment = DomainSegment {
vstart: start,
vend: 0,
pfn: self.pfn_alloc_end,
addr: 0,
size,
pages,
};
self.chk_alloc_pages(pages)?;
let ptr = self.phys.pfn_to_ptr(segment.pfn, pages).await?;
segment.addr = ptr;
let slice = unsafe {
slice::from_raw_parts_mut(ptr as *mut u8, (pages * local_page_size as u64) as usize)
};
slice.fill(0);
segment.vend = self.virt_alloc_end;
debug!(
"alloc_segment {:#x} -> {:#x} (pfn {:#x} + {:#x} pages)",
start, segment.vend, segment.pfn, pages
);
Ok(segment)
}
pub fn alloc_padding_pages(&mut self, boundary: u64) -> Result<()> {
if (boundary & (self.page_size - 1)) != 0 {
return Err(Error::MemorySetupFailed("boundary is incorrect"));
}
if boundary < self.virt_alloc_end {
return Err(Error::MemorySetupFailed("boundary is below allocation end"));
}
let pages = (boundary - self.virt_alloc_end) / self.page_size;
self.chk_alloc_pages(pages)?;
Ok(())
}
pub fn chk_alloc_pages(&mut self, pages: u64) -> Result<()> {
if pages > self.total_pages
|| self.pfn_alloc_end > self.total_pages
|| pages > self.total_pages - self.pfn_alloc_end
{
return Err(Error::MemorySetupFailed("no more pages left"));
}
self.pfn_alloc_end += pages;
self.virt_alloc_end += pages * self.page_size;
Ok(())
}
pub fn alloc_page(&mut self) -> Result<DomainSegment> {
let start = self.virt_alloc_end;
let pfn = self.pfn_alloc_end;
self.chk_alloc_pages(1)?;
debug!("alloc_page {:#x} (pfn {:#x})", start, pfn);
Ok(DomainSegment {
vstart: start,
vend: (start + self.page_size) - 1,
pfn,
addr: 0,
size: 0,
pages: 1,
})
}
pub fn round_up(addr: u64, mask: u64) -> u64 {
addr | mask
}
pub fn bits_to_mask(bits: u64) -> u64 {
(1 << bits) - 1
}
}
impl<I: BootImageLoader, P: BootSetupPlatform> BootSetup<I, P> {
pub fn new(
call: XenCall,
domid: u32,
platform: P,
image_loader: I,
dtb: Option<Vec<u8>>,
) -> BootSetup<I, P> {
BootSetup {
call,
domid,
platform,
image_loader,
dtb,
}
}
pub async fn initialize(
&mut self,
initrd: &[u8],
mem_mb: u64,
max_vcpus: u32,
cmdline: &str,
) -> Result<BootDomain> {
let total_pages = mem_mb << (20 - self.platform.page_shift());
let image_info = self.image_loader.parse(self.platform.hvm()).await?;
let mut domain = BootDomain {
domid: self.domid,
call: self.call.clone(),
virt_alloc_end: 0,
virt_pgtab_end: 0,
pfn_alloc_end: 0,
total_pages,
target_pages: total_pages,
page_size: self.platform.page_size(),
image_info,
console_evtchn: 0,
console_mfn: 0,
max_vcpus,
phys: PhysicalPages::new(self.call.clone(), self.domid, self.platform.page_shift()),
initrd_segment: DomainSegment::default(),
store_evtchn: 0,
store_mfn: 0,
cmdline: cmdline.to_string(),
};
self.platform.initialize_early(&mut domain).await?;
let mut initrd_segment = if !domain.image_info.unmapped_initrd {
Some(domain.alloc_module(initrd).await?)
} else {
None
};
let mut kernel_segment = if self.platform.needs_early_kernel() {
Some(self.load_kernel_segment(&mut domain).await?)
} else {
None
};
self.platform.initialize_memory(&mut domain).await?;
domain.virt_alloc_end = domain.image_info.virt_base;
if kernel_segment.is_none() {
kernel_segment = Some(self.load_kernel_segment(&mut domain).await?);
}
if domain.image_info.unmapped_initrd {
initrd_segment = Some(domain.alloc_module(initrd).await?);
}
domain.initrd_segment =
initrd_segment.ok_or(Error::MemorySetupFailed("initrd_segment missing"))?;
self.platform.alloc_magic_pages(&mut domain).await?;
domain.store_evtchn = self.call.evtchn_alloc_unbound(self.domid, 0).await?;
let _kernel_segment =
kernel_segment.ok_or(Error::MemorySetupFailed("kernel_segment missing"))?;
Ok(domain)
}
pub async fn boot(&mut self, domain: &mut BootDomain) -> Result<()> {
let domain_info = self.call.get_domain_info(self.domid).await?;
let shared_info_frame = domain_info.shared_info_frame;
self.platform.setup_page_tables(domain).await?;
self.platform
.setup_start_info(domain, shared_info_frame)
.await?;
self.platform.setup_hypercall_page(domain).await?;
self.platform.bootlate(domain).await?;
self.platform
.setup_shared_info(domain, shared_info_frame)
.await?;
self.platform.vcpu(domain).await?;
domain.phys.unmap_all()?;
self.platform.gnttab_seed(domain).await?;
Ok(())
}
async fn load_kernel_segment(&mut self, domain: &mut BootDomain) -> Result<DomainSegment> {
let kernel_segment = domain
.alloc_segment(
domain.image_info.virt_kstart,
domain.image_info.virt_kend - domain.image_info.virt_kstart,
)
.await?;
let kernel_segment_ptr = kernel_segment.addr as *mut u8;
let kernel_segment_slice =
unsafe { slice::from_raw_parts_mut(kernel_segment_ptr, kernel_segment.size as usize) };
self.image_loader
.load(&domain.image_info, kernel_segment_slice)
.await?;
Ok(kernel_segment)
}
}
#[async_trait::async_trait]
pub trait BootSetupPlatform: Clone {
fn create_domain(&self, enable_iommu: bool) -> CreateDomain;
fn page_size(&self) -> u64;
fn page_shift(&self) -> u64;
fn needs_early_kernel(&self) -> bool;
fn hvm(&self) -> bool;
async fn initialize_early(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn initialize_memory(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn alloc_page_tables(&mut self, domain: &mut BootDomain)
-> Result<Option<DomainSegment>>;
async fn alloc_p2m_segment(&mut self, domain: &mut BootDomain)
-> Result<Option<DomainSegment>>;
async fn alloc_magic_pages(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn setup_page_tables(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn setup_shared_info(
&mut self,
domain: &mut BootDomain,
shared_info_frame: u64,
) -> Result<()>;
async fn setup_start_info(
&mut self,
domain: &mut BootDomain,
shared_info_frame: u64,
) -> Result<()>;
async fn bootlate(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn gnttab_seed(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn vcpu(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn setup_hypercall_page(&mut self, domain: &mut BootDomain) -> Result<()>;
}
#[async_trait::async_trait]
pub trait BootImageLoader {
async fn parse(&self, hvm: bool) -> Result<BootImageInfo>;
async fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<()>;
}
#[derive(Debug)]
pub struct BootImageInfo {
pub start: u64,
pub virt_base: u64,
pub virt_kstart: u64,
pub virt_kend: u64,
pub virt_hypercall: u64,
pub virt_entry: u64,
pub virt_p2m_base: u64,
pub unmapped_initrd: bool,
}

View File

@ -0,0 +1,80 @@
use std::sync::Arc;
use crate::{
boot::{BootSetup, BootSetupPlatform},
elfloader::ElfImageLoader,
};
use uuid::Uuid;
use xencall::XenCall;
use crate::error::Result;
pub struct BaseDomainManager<P: BootSetupPlatform> {
call: XenCall,
pub platform: Arc<P>,
}
impl<P: BootSetupPlatform> BaseDomainManager<P> {
pub async fn new(call: XenCall, platform: P) -> Result<BaseDomainManager<P>> {
Ok(BaseDomainManager {
call,
platform: Arc::new(platform),
})
}
pub async fn create(&self, config: BaseDomainConfig) -> Result<CreatedDomain> {
let mut domain = self.platform.create_domain(config.enable_iommu);
domain.handle = config.uuid.into_bytes();
domain.max_vcpus = config.max_vcpus;
let domid = self.call.create_domain(domain).await?;
self.call.set_max_vcpus(domid, config.max_vcpus).await?;
self.call
.set_max_mem(domid, (config.mem_mb * 1024) + 2048)
.await?;
let loader = ElfImageLoader::load_file_kernel(&config.kernel)?;
let platform = (*self.platform).clone();
let mut boot = BootSetup::new(self.call.clone(), domid, platform, loader, None);
let mut domain = boot
.initialize(
&config.initrd,
config.mem_mb,
config.max_vcpus,
&config.cmdline,
)
.await?;
boot.boot(&mut domain).await?;
Ok(CreatedDomain {
domid,
store_evtchn: domain.store_evtchn,
store_mfn: domain.store_mfn,
console_evtchn: domain.console_evtchn,
console_mfn: domain.console_mfn,
})
}
pub async fn destroy(&self, domid: u32) -> Result<()> {
self.call.destroy_domain(domid).await?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct BaseDomainConfig {
pub uuid: Uuid,
pub owner_domid: u32,
pub max_vcpus: u32,
pub mem_mb: u64,
pub kernel: Vec<u8>,
pub initrd: Vec<u8>,
pub cmdline: String,
pub enable_iommu: bool,
}
#[derive(Clone, Debug)]
pub struct CreatedDomain {
pub domid: u32,
pub store_evtchn: u32,
pub store_mfn: u64,
pub console_evtchn: u32,
pub console_mfn: u64,
}

View File

@ -1,8 +1,8 @@
use crate::boot::{BootImageInfo, BootImageLoader, XEN_UNSET_ADDR};
use crate::boot::{BootImageInfo, BootImageLoader};
use crate::error::Result;
use crate::sys::{
XEN_ELFNOTE_ENTRY, XEN_ELFNOTE_HYPERCALL_PAGE, XEN_ELFNOTE_INIT_P2M, XEN_ELFNOTE_MOD_START_PFN,
XEN_ELFNOTE_PADDR_OFFSET, XEN_ELFNOTE_TYPES, XEN_ELFNOTE_VIRT_BASE,
XEN_ELFNOTE_PADDR_OFFSET, XEN_ELFNOTE_PHYS32_ENTRY, XEN_ELFNOTE_TYPES, XEN_ELFNOTE_VIRT_BASE,
};
use crate::Error;
use elf::abi::{PF_R, PF_W, PF_X, PT_LOAD, SHT_NOTE};
@ -16,10 +16,12 @@ use slice_copy::copy;
use std::collections::HashMap;
use std::io::{BufReader, Read};
use std::mem::size_of;
use std::sync::Arc;
use xz2::bufread::XzDecoder;
#[derive(Clone)]
pub struct ElfImageLoader {
data: Vec<u8>,
data: Arc<Vec<u8>>,
}
fn xen_note_value_as_u64(endian: AnyEndian, value: &[u8]) -> Option<u64> {
@ -59,7 +61,9 @@ fn xen_note_value_as_u64(endian: AnyEndian, value: &[u8]) -> Option<u64> {
impl ElfImageLoader {
pub fn new(data: Vec<u8>) -> ElfImageLoader {
ElfImageLoader { data }
ElfImageLoader {
data: Arc::new(data),
}
}
pub fn load_gz(data: &[u8]) -> Result<ElfImageLoader> {
@ -122,14 +126,8 @@ impl ElfImageLoader {
Err(Error::ElfCompressionUnknown)
}
}
struct ElfNoteValue {
value: u64,
}
impl BootImageLoader for ElfImageLoader {
fn parse(&self) -> Result<BootImageInfo> {
fn parse_sync(&self, hvm: bool) -> Result<BootImageInfo> {
let elf = ElfBytes::<AnyEndian>::minimal_parse(self.data.as_slice())?;
let headers = elf.section_headers().ok_or(Error::ElfInvalidImage)?;
let mut linux_notes: HashMap<u64, Vec<u8>> = HashMap::new();
@ -200,6 +198,8 @@ impl BootImageLoader for ElfImageLoader {
.ok_or(Error::ElfInvalidImage)?
.value;
let phys32_entry = xen_notes.get(&XEN_ELFNOTE_PHYS32_ENTRY).map(|x| x.value);
let mut start: u64 = u64::MAX;
let mut end: u64 = 0;
@ -220,15 +220,21 @@ impl BootImageLoader for ElfImageLoader {
}
}
if paddr_offset != XEN_UNSET_ADDR && virt_base == XEN_UNSET_ADDR {
if paddr_offset != u64::MAX && virt_base == u64::MAX {
return Err(Error::ElfInvalidImage);
}
let virt_offset = virt_base - paddr_offset;
let virt_kstart = start + virt_offset;
let virt_kend = end + virt_offset;
let virt_entry = entry;
let mut virt_entry = entry;
if hvm {
if let Some(entry) = phys32_entry {
virt_entry = entry;
} else {
virt_entry = elf.ehdr.e_entry;
}
}
let image_info = BootImageInfo {
start,
virt_base,
@ -241,8 +247,20 @@ impl BootImageLoader for ElfImageLoader {
};
Ok(image_info)
}
}
fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<()> {
struct ElfNoteValue {
value: u64,
}
#[async_trait::async_trait]
impl BootImageLoader for ElfImageLoader {
async fn parse(&self, hvm: bool) -> Result<BootImageInfo> {
let loader = self.clone();
tokio::task::spawn_blocking(move || loader.parse_sync(hvm)).await?
}
async fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<()> {
let elf = ElfBytes::<AnyEndian>::minimal_parse(self.data.as_slice())?;
let segments = elf.segments().ok_or(Error::ElfInvalidImage)?;

View File

@ -0,0 +1,45 @@
use std::io;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("io issue encountered: {0}")]
Io(#[from] io::Error),
#[error("xencall issue encountered: {0}")]
XenCall(#[from] xencall::error::Error),
#[error("domain does not have a tty")]
TtyNotFound,
#[error("introducing the domain failed")]
IntroduceDomainFailed,
#[error("string conversion of a path failed")]
PathStringConversion,
#[error("parent of path not found")]
PathParentNotFound,
#[error("domain does not exist")]
DomainNonExistent,
#[error("elf parse failed: {0}")]
ElfParseFailed(#[from] elf::ParseError),
#[error("mmap failed")]
MmapFailed,
#[error("munmap failed: {0}")]
UnmapFailed(nix::errno::Errno),
#[error("memory setup failed: {0}")]
MemorySetupFailed(&'static str),
#[error("populate physmap failed: wanted={0}, received={1}, input_extents={2}")]
PopulatePhysmapFailed(usize, usize, usize),
#[error("unknown elf compression method")]
ElfCompressionUnknown,
#[error("expected elf image format not found")]
ElfInvalidImage,
#[error("provided elf image does not contain xen support")]
ElfXenSupportMissing,
#[error("regex error: {0}")]
RegexError(#[from] regex::Error),
#[error("error: {0}")]
GenericError(String),
#[error("failed to parse int: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("failed to join async task: {0}")]
AsyncJoinError(#[from] tokio::task::JoinError),
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -0,0 +1,12 @@
pub mod boot;
pub mod elfloader;
pub mod error;
pub mod mem;
pub mod sys;
use crate::error::Error;
pub mod domain;
pub mod unsupported;
#[cfg(target_arch = "x86_64")]
pub mod x86pv;

View File

@ -1,36 +1,34 @@
use crate::error::Result;
use crate::sys::{XEN_PAGE_SHIFT, XEN_PAGE_SIZE};
use crate::sys::XEN_PAGE_SHIFT;
use crate::Error;
use libc::munmap;
use log::debug;
use nix::errno::Errno;
use std::ffi::c_void;
#[cfg(target_arch = "aarch64")]
pub(crate) use crate::arm64::ARM_PAGE_SHIFT as ARCH_PAGE_SHIFT;
#[cfg(target_arch = "x86_64")]
pub(crate) use crate::x86::X86_PAGE_SHIFT as ARCH_PAGE_SHIFT;
use std::slice;
use xencall::sys::MmapEntry;
use xencall::XenCall;
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct PhysicalPage {
pfn: u64,
ptr: u64,
pub ptr: u64,
count: u64,
}
pub struct PhysicalPages<'a> {
pub struct PhysicalPages {
page_shift: u64,
domid: u32,
pub(crate) p2m: Vec<u64>,
call: &'a XenCall,
pub p2m: Vec<u64>,
call: XenCall,
pages: Vec<PhysicalPage>,
}
impl PhysicalPages<'_> {
pub fn new(call: &XenCall, domid: u32) -> PhysicalPages {
impl PhysicalPages {
pub fn new(call: XenCall, domid: u32, page_shift: u64) -> PhysicalPages {
PhysicalPages {
page_shift,
domid,
p2m: Vec::new(),
call,
@ -70,7 +68,7 @@ impl PhysicalPages<'_> {
}
}
return Ok(page.ptr + ((pfn - page.pfn) << ARCH_PAGE_SHIFT));
return Ok(page.ptr + ((pfn - page.pfn) << self.page_shift));
}
if count == 0 {
@ -83,7 +81,11 @@ impl PhysicalPages<'_> {
async fn pfn_alloc(&mut self, pfn: u64, count: u64) -> Result<u64> {
let mut entries = vec![MmapEntry::default(); count as usize];
for (i, entry) in entries.iter_mut().enumerate() {
entry.mfn = self.p2m[pfn as usize + i];
if !self.p2m.is_empty() {
entry.mfn = self.p2m[pfn as usize + i];
} else {
entry.mfn = pfn + i as u64;
}
}
let chunk_size = 1 << XEN_PAGE_SHIFT;
let num_per_entry = chunk_size >> XEN_PAGE_SHIFT;
@ -123,10 +125,19 @@ impl PhysicalPages<'_> {
}
pub async fn map_foreign_pages(&mut self, mfn: u64, size: u64) -> Result<u64> {
let num = ((size + XEN_PAGE_SIZE - 1) >> XEN_PAGE_SHIFT) as usize;
let count = (size >> XEN_PAGE_SHIFT) as usize;
let mut entries = vec![MmapEntry::default(); count];
for (i, entry) in entries.iter_mut().enumerate() {
entry.mfn = mfn + i as u64;
}
let chunk_size = 1 << XEN_PAGE_SHIFT;
let num_per_entry = chunk_size >> XEN_PAGE_SHIFT;
let num = num_per_entry * count;
let mut pfns = vec![u64::MAX; num];
for (i, item) in pfns.iter_mut().enumerate().take(num) {
*item = mfn + i as u64;
for i in 0..count {
for j in 0..num_per_entry {
pfns[i * num_per_entry + j] = entries[i].mfn + j as u64;
}
}
let actual_mmap_len = (num as u64) << XEN_PAGE_SHIFT;
@ -144,9 +155,9 @@ impl PhysicalPages<'_> {
return Err(Error::MmapFailed);
}
let page = PhysicalPage {
pfn: u64::MAX,
pfn: mfn,
ptr: addr,
count: num as u64,
count: count as u64,
};
debug!(
"alloc_mfn {:#x}+{:#x} at {:#x}",
@ -156,12 +167,21 @@ impl PhysicalPages<'_> {
Ok(addr)
}
pub async fn clear_pages(&mut self, pfn: u64, count: u64) -> Result<()> {
let ptr = self.pfn_to_ptr(pfn, count).await?;
let slice = unsafe {
slice::from_raw_parts_mut(ptr as *mut u8, (count * (1 << self.page_shift)) as usize)
};
slice.fill(0);
Ok(())
}
pub fn unmap_all(&mut self) -> Result<()> {
for page in &self.pages {
unsafe {
let err = munmap(
page.ptr as *mut c_void,
(page.count << ARCH_PAGE_SHIFT) as usize,
(page.count << self.page_shift) as usize,
);
if err != 0 {
return Err(Error::UnmapFailed(Errno::from_raw(err)));
@ -182,11 +202,11 @@ impl PhysicalPages<'_> {
unsafe {
let err = munmap(
page.ptr as *mut c_void,
(page.count << ARCH_PAGE_SHIFT) as usize,
(page.count << self.page_shift) as usize,
);
debug!(
"unmapped {:#x} foreign bytes at {:#x}",
(page.count << ARCH_PAGE_SHIFT) as usize,
(page.count << self.page_shift) as usize,
page.ptr
);
if err != 0 {

View File

@ -119,6 +119,8 @@ pub const XEN_PAGE_MASK: u64 = !(XEN_PAGE_SIZE - 1);
pub const SUPERPAGE_BATCH_SIZE: u64 = 512;
pub const SUPERPAGE_2MB_SHIFT: u64 = 9;
pub const SUPERPAGE_2MB_NR_PFNS: u64 = 1u64 << SUPERPAGE_2MB_SHIFT;
pub const SUPERPAGE_1GB_SHIFT: u64 = 18;
pub const SUPERPAGE_1GB_NR_PFNS: u64 = 1u64 << SUPERPAGE_1GB_SHIFT;
pub const VGCF_IN_KERNEL: u64 = 1 << 2;
pub const VGCF_ONLINE: u64 = 1 << 5;
@ -128,3 +130,18 @@ pub struct GrantEntry {
pub domid: u16,
pub frame: u32,
}
pub const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
pub const HVM_PARAM_STORE_PFN: u32 = 1;
pub const HVM_PARAM_STORE_EVTCHN: u32 = 2;
pub const HVM_PARAM_IOREQ_PFN: u32 = 5;
pub const HVM_PARAM_BUFIOREQ_PFN: u32 = 6;
pub const HVM_PARAM_CONSOLE_PFN: u32 = 17;
pub const HVM_PARAM_CONSOLE_EVTCHN: u32 = 18;
pub const HVM_PARAM_PAGING_RING_PFN: u32 = 27;
pub const HVM_PARAM_MONITOR_RING_PFN: u32 = 28;
pub const HVM_PARAM_SHARING_RING_PFN: u32 = 29;
pub const HVM_PARAM_TIMER_MODE: u32 = 10;
pub const HVM_PARAM_ALTP2M: u32 = 35;
pub const HVM_PARAM_IDENT_PT: u32 = 12;

View File

@ -0,0 +1,86 @@
use xencall::sys::CreateDomain;
use crate::{
boot::{BootDomain, BootSetupPlatform, DomainSegment},
error::Result,
};
#[derive(Default, Clone)]
pub struct UnsupportedPlatform;
impl UnsupportedPlatform {
pub fn new() -> Self {
Self {}
}
}
#[async_trait::async_trait]
impl BootSetupPlatform for UnsupportedPlatform {
fn create_domain(&self, _: bool) -> CreateDomain {
panic!("unsupported platform")
}
fn page_size(&self) -> u64 {
panic!("unsupported platform")
}
fn page_shift(&self) -> u64 {
panic!("unsupported platform")
}
fn needs_early_kernel(&self) -> bool {
panic!("unsupported platform")
}
fn hvm(&self) -> bool {
panic!("unsupported platform")
}
async fn initialize_early(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn initialize_memory(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn alloc_p2m_segment(&mut self, _: &mut BootDomain) -> Result<Option<DomainSegment>> {
panic!("unsupported platform")
}
async fn alloc_page_tables(&mut self, _: &mut BootDomain) -> Result<Option<DomainSegment>> {
panic!("unsupported platform")
}
async fn setup_page_tables(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn setup_hypercall_page(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn alloc_magic_pages(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn setup_shared_info(&mut self, _: &mut BootDomain, _: u64) -> Result<()> {
panic!("unsupported platform")
}
async fn setup_start_info(&mut self, _: &mut BootDomain, _: u64) -> Result<()> {
panic!("unsupported platform")
}
async fn bootlate(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn vcpu(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn gnttab_seed(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
}

View File

@ -1,17 +1,26 @@
use crate::boot::{ArchBootSetup, BootImageInfo, BootSetup, BootState, DomainSegment};
use crate::error::Result;
use crate::sys::{
SUPERPAGE_2MB_NR_PFNS, SUPERPAGE_2MB_SHIFT, SUPERPAGE_BATCH_SIZE, VGCF_IN_KERNEL, VGCF_ONLINE,
XEN_PAGE_SHIFT,
use std::{
mem::size_of,
os::raw::{c_char, c_void},
slice,
};
use crate::Error;
use libc::c_char;
use libc::munmap;
use log::{debug, trace};
use nix::errno::Errno;
use slice_copy::copy;
use std::cmp::{max, min};
use std::mem::size_of;
use std::slice;
use xencall::sys::{VcpuGuestContext, MMUEXT_PIN_L4_TABLE};
use xencall::sys::{
x8664VcpuGuestContext, CreateDomain, E820Entry, VcpuGuestContextAny, E820_MAX, E820_RAM,
E820_UNUSABLE, MMUEXT_PIN_L4_TABLE, XEN_DOMCTL_CDF_IOMMU,
};
use crate::{
boot::{BootDomain, BootSetupPlatform, DomainSegment},
error::{Error, Result},
sys::{
GrantEntry, SUPERPAGE_2MB_NR_PFNS, SUPERPAGE_2MB_SHIFT, SUPERPAGE_BATCH_SIZE,
VGCF_IN_KERNEL, VGCF_ONLINE, XEN_PAGE_SHIFT,
},
};
pub const X86_PAGE_SHIFT: u64 = 12;
pub const X86_PAGE_SIZE: u64 = 1 << X86_PAGE_SHIFT;
@ -125,10 +134,6 @@ pub struct SharedInfo {
pub p2m_generation: u64,
}
pub struct X86BootSetup {
table: PageTable,
}
#[derive(Debug)]
struct VmemRange {
start: u64,
@ -137,16 +142,20 @@ struct VmemRange {
_nid: u32,
}
impl Default for X86BootSetup {
fn default() -> Self {
Self::new()
}
#[derive(Default, Clone)]
pub struct X86PvPlatform {
table: PageTable,
p2m_segment: Option<DomainSegment>,
page_table_segment: Option<DomainSegment>,
start_info_segment: Option<DomainSegment>,
boot_stack_segment: Option<DomainSegment>,
xenstore_segment: Option<DomainSegment>,
}
impl X86BootSetup {
pub fn new() -> X86BootSetup {
X86BootSetup {
table: PageTable::default(),
impl X86PvPlatform {
pub fn new() -> Self {
Self {
..Default::default()
}
}
@ -157,22 +166,22 @@ impl X86BootSetup {
const PAGE_DIRTY: u64 = 0x040;
fn get_pg_prot(&mut self, l: usize, pfn: u64) -> u64 {
let prot = [
X86BootSetup::PAGE_PRESENT | X86BootSetup::PAGE_RW | X86BootSetup::PAGE_ACCESSED,
X86BootSetup::PAGE_PRESENT
| X86BootSetup::PAGE_RW
| X86BootSetup::PAGE_ACCESSED
| X86BootSetup::PAGE_DIRTY
| X86BootSetup::PAGE_USER,
X86BootSetup::PAGE_PRESENT
| X86BootSetup::PAGE_RW
| X86BootSetup::PAGE_ACCESSED
| X86BootSetup::PAGE_DIRTY
| X86BootSetup::PAGE_USER,
X86BootSetup::PAGE_PRESENT
| X86BootSetup::PAGE_RW
| X86BootSetup::PAGE_ACCESSED
| X86BootSetup::PAGE_DIRTY
| X86BootSetup::PAGE_USER,
X86PvPlatform::PAGE_PRESENT | X86PvPlatform::PAGE_RW | X86PvPlatform::PAGE_ACCESSED,
X86PvPlatform::PAGE_PRESENT
| X86PvPlatform::PAGE_RW
| X86PvPlatform::PAGE_ACCESSED
| X86PvPlatform::PAGE_DIRTY
| X86PvPlatform::PAGE_USER,
X86PvPlatform::PAGE_PRESENT
| X86PvPlatform::PAGE_RW
| X86PvPlatform::PAGE_ACCESSED
| X86PvPlatform::PAGE_DIRTY
| X86PvPlatform::PAGE_USER,
X86PvPlatform::PAGE_PRESENT
| X86PvPlatform::PAGE_RW
| X86PvPlatform::PAGE_ACCESSED
| X86PvPlatform::PAGE_DIRTY
| X86PvPlatform::PAGE_USER,
];
let prot = prot[l];
@ -185,7 +194,7 @@ impl X86BootSetup {
let pfn_s = map.levels[(X86_PGTABLE_LEVELS - 1) as usize].pfn;
let pfn_e = map.area.pgtables as u64 + pfn_s;
if pfn >= pfn_s && pfn < pfn_e {
return prot & !X86BootSetup::PAGE_RW;
return prot & !X86PvPlatform::PAGE_RW;
}
}
prot
@ -193,7 +202,7 @@ impl X86BootSetup {
fn count_page_tables(
&mut self,
setup: &mut BootSetup,
domain: &mut BootDomain,
from: u64,
to: u64,
pfn: u64,
@ -206,7 +215,7 @@ impl X86BootSetup {
let m = self.table.mappings_count;
let pfn_end = pfn + ((to - from) >> X86_PAGE_SHIFT);
if pfn_end >= setup.phys.p2m_size() {
if pfn_end >= domain.phys.p2m_size() {
return Err(Error::MemorySetupFailed("pfn_end greater than p2m size"));
}
@ -220,7 +229,7 @@ impl X86BootSetup {
map.area.to = to & X86_VIRT_MASK;
for l in (0usize..X86_PGTABLE_LEVELS as usize).rev() {
map.levels[l].pfn = setup.pfn_alloc_end + map.area.pgtables as u64;
map.levels[l].pfn = domain.pfn_alloc_end + map.area.pgtables as u64;
if l as u64 == X86_PGTABLE_LEVELS - 1 {
if self.table.mappings_count == 0 {
map.levels[l].from = 0;
@ -232,7 +241,7 @@ impl X86BootSetup {
}
let bits = X86_PAGE_SHIFT + (l + 1) as u64 * X86_PGTABLE_LEVEL_SHIFT;
let mask = BootSetup::bits_to_mask(bits);
let mask = BootDomain::bits_to_mask(bits);
map.levels[l].from = map.area.from & !mask;
map.levels[l].to = map.area.to | mask;
@ -273,232 +282,199 @@ impl X86BootSetup {
self.table.mappings[m] = map;
Ok(m)
}
fn e820_sanitize(
&self,
mut source: Vec<E820Entry>,
map_limit_kb: u64,
balloon_kb: u64,
) -> Result<Vec<E820Entry>> {
let mut e820 = vec![E820Entry::default(); E820_MAX as usize];
for entry in &mut source {
if entry.addr > 0x100000 {
continue;
}
// entries under 1MB should be removed.
entry.typ = 0;
entry.size = 0;
entry.addr = u64::MAX;
}
let mut lowest = u64::MAX;
let mut highest = 0;
for entry in &source {
if entry.typ == E820_RAM || entry.typ == E820_UNUSABLE || entry.typ == 0 {
continue;
}
lowest = if entry.addr < lowest {
entry.addr
} else {
lowest
};
highest = if entry.addr + entry.size > highest {
entry.addr + entry.size
} else {
highest
}
}
let start_kb = if lowest > 1024 { lowest >> 10 } else { 0 };
let mut idx: usize = 0;
e820[idx].addr = 0;
e820[idx].size = map_limit_kb << 10;
e820[idx].typ = E820_RAM;
let mut delta_kb = 0u64;
if start_kb > 0 && map_limit_kb > start_kb {
delta_kb = map_limit_kb - start_kb;
if delta_kb > 0 {
e820[idx].size -= delta_kb << 10;
}
}
let ram_end = source[0].addr + source[0].size;
idx += 1;
for src in &mut source {
let end = src.addr + src.size;
if src.typ == E820_UNUSABLE || end < ram_end {
src.typ = 0;
continue;
}
if src.typ != E820_RAM {
continue;
}
if src.addr >= (1 << 32) {
continue;
}
if src.addr < ram_end {
let delta = ram_end - src.addr;
src.typ = E820_UNUSABLE;
if src.size < delta {
src.typ = 0;
} else {
src.size -= delta;
src.addr = ram_end;
}
if src.addr + src.size != end {
src.typ = 0;
}
}
if end > ram_end {
src.typ = E820_UNUSABLE;
}
}
if lowest > ram_end {
let mut add_unusable = true;
for src in &mut source {
if !add_unusable {
break;
}
if src.typ != E820_UNUSABLE {
continue;
}
if ram_end != src.addr {
continue;
}
if lowest != src.addr + src.size {
src.size = lowest - src.addr;
}
add_unusable = false;
}
if add_unusable {
e820[1].typ = E820_UNUSABLE;
e820[1].addr = ram_end;
e820[1].size = lowest - ram_end;
}
}
for src in &source {
if src.typ == E820_RAM || src.typ == 0 {
continue;
}
e820[idx].typ = src.typ;
e820[idx].addr = src.addr;
e820[idx].size = src.size;
idx += 1;
}
if balloon_kb > 0 || delta_kb > 0 {
e820[idx].typ = E820_RAM;
e820[idx].addr = if (1u64 << 32u64) > highest {
1u64 << 32u64
} else {
highest
};
e820[idx].size = (delta_kb << 10) + (balloon_kb << 10);
}
Ok(e820)
}
}
#[async_trait::async_trait]
impl ArchBootSetup for X86BootSetup {
fn page_size(&mut self) -> u64 {
impl BootSetupPlatform for X86PvPlatform {
fn create_domain(&self, enable_iommu: bool) -> CreateDomain {
CreateDomain {
flags: if enable_iommu {
XEN_DOMCTL_CDF_IOMMU
} else {
0
},
..Default::default()
}
}
fn page_size(&self) -> u64 {
X86_PAGE_SIZE
}
fn page_shift(&mut self) -> u64 {
fn page_shift(&self) -> u64 {
X86_PAGE_SHIFT
}
fn needs_early_kernel(&mut self) -> bool {
fn needs_early_kernel(&self) -> bool {
false
}
async fn alloc_p2m_segment(
&mut self,
setup: &mut BootSetup,
image_info: &BootImageInfo,
) -> Result<Option<DomainSegment>> {
let mut p2m_alloc_size =
((setup.phys.p2m_size() * 8) + X86_PAGE_SIZE - 1) & !(X86_PAGE_SIZE - 1);
let from = image_info.virt_p2m_base;
let to = from + p2m_alloc_size - 1;
let m = self.count_page_tables(setup, from, to, setup.pfn_alloc_end)?;
fn hvm(&self) -> bool {
false
}
let pgtables: usize;
{
let map = &mut self.table.mappings[m];
map.area.pfn = setup.pfn_alloc_end;
for lvl_idx in 0..4 {
map.levels[lvl_idx].pfn += p2m_alloc_size >> X86_PAGE_SHIFT;
}
pgtables = map.area.pgtables;
}
self.table.mappings_count += 1;
p2m_alloc_size += (pgtables << X86_PAGE_SHIFT) as u64;
let p2m_segment = setup
.alloc_segment(self.page_size(), 0, p2m_alloc_size)
async fn initialize_early(&mut self, _: &mut BootDomain) -> Result<()> {
Ok(())
}
async fn initialize_memory(&mut self, domain: &mut BootDomain) -> Result<()> {
domain.call.set_address_size(domain.domid, 64).await?;
domain
.call
.claim_pages(domain.domid, domain.total_pages)
.await?;
Ok(Some(p2m_segment))
}
async fn alloc_page_tables(
&mut self,
setup: &mut BootSetup,
image_info: &BootImageInfo,
) -> Result<Option<DomainSegment>> {
let mut extra_pages = 1;
extra_pages += (512 * 1024) / X86_PAGE_SIZE;
let mut pages = extra_pages;
let mut try_virt_end: u64;
let mut m: usize;
loop {
try_virt_end = BootSetup::round_up(
setup.virt_alloc_end + pages * X86_PAGE_SIZE,
BootSetup::bits_to_mask(22),
);
m = self.count_page_tables(setup, image_info.virt_base, try_virt_end, 0)?;
pages = self.table.mappings[m].area.pgtables as u64 + extra_pages;
if setup.virt_alloc_end + pages * X86_PAGE_SIZE <= try_virt_end + 1 {
break;
}
}
self.table.mappings[m].area.pfn = 0;
self.table.mappings_count += 1;
setup.virt_pgtab_end = try_virt_end + 1;
let size = self.table.mappings[m].area.pgtables as u64 * X86_PAGE_SIZE;
let segment = setup.alloc_segment(self.page_size(), 0, size).await?;
debug!(
"alloc_page_tables table={:?} segment={:?}",
self.table, segment
);
Ok(Some(segment))
}
async fn setup_page_tables(
&mut self,
setup: &mut BootSetup,
state: &mut BootState,
) -> Result<()> {
let p2m_segment = state
.p2m_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("p2m_segment missing"))?;
let p2m_guest = unsafe {
slice::from_raw_parts_mut(p2m_segment.addr as *mut u64, setup.phys.p2m_size() as usize)
};
copy(p2m_guest, &setup.phys.p2m);
for l in (0usize..X86_PGTABLE_LEVELS as usize).rev() {
for m1 in 0usize..self.table.mappings_count {
let map1 = &self.table.mappings[m1];
let from = map1.levels[l].from;
let to = map1.levels[l].to;
let pg_ptr = setup.phys.pfn_to_ptr(map1.levels[l].pfn, 0).await? as *mut u64;
for m2 in 0usize..self.table.mappings_count {
let map2 = &self.table.mappings[m2];
let lvl = if l > 0 {
&map2.levels[l - 1]
} else {
&map2.area
};
if l > 0 && lvl.pgtables == 0 {
continue;
}
if lvl.from >= to || lvl.to <= from {
continue;
}
let p_s = (max(from, lvl.from) - from)
>> (X86_PAGE_SHIFT + l as u64 * X86_PGTABLE_LEVEL_SHIFT);
let p_e = (min(to, lvl.to) - from)
>> (X86_PAGE_SHIFT + l as u64 * X86_PGTABLE_LEVEL_SHIFT);
let rhs = X86_PAGE_SHIFT as usize + l * X86_PGTABLE_LEVEL_SHIFT as usize;
let mut pfn = ((max(from, lvl.from) - lvl.from) >> rhs) + lvl.pfn;
debug!(
"setup_page_tables lvl={} map_1={} map_2={} pfn={:#x} p_s={:#x} p_e={:#x}",
l, m1, m2, pfn, p_s, p_e
);
let pg = unsafe { slice::from_raw_parts_mut(pg_ptr, (p_e + 1) as usize) };
for p in p_s..p_e + 1 {
let prot = self.get_pg_prot(l, pfn);
let pfn_paddr = setup.phys.p2m[pfn as usize] << X86_PAGE_SHIFT;
let value = pfn_paddr | prot;
pg[p as usize] = value;
pfn += 1;
}
}
}
}
Ok(())
}
async fn setup_start_info(
&mut self,
setup: &mut BootSetup,
state: &BootState,
cmdline: &str,
) -> Result<()> {
let ptr = setup
.phys
.pfn_to_ptr(state.start_info_segment.pfn, 1)
.await?;
let byte_slice =
unsafe { slice::from_raw_parts_mut(ptr as *mut u8, X86_PAGE_SIZE as usize) };
byte_slice.fill(0);
let info = ptr as *mut StartInfo;
let page_table_segment = state
.page_table_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("page_table_segment missing"))?;
let p2m_segment = state
.p2m_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("p2m_segment missing"))?;
unsafe {
for (i, c) in X86_GUEST_MAGIC.chars().enumerate() {
(*info).magic[i] = c as c_char;
}
(*info).magic[X86_GUEST_MAGIC.len()] = 0 as c_char;
(*info).nr_pages = setup.total_pages;
(*info).shared_info = state.shared_info_frame << X86_PAGE_SHIFT;
(*info).pt_base = page_table_segment.vstart;
(*info).nr_pt_frames = self.table.mappings[0].area.pgtables as u64;
(*info).mfn_list = p2m_segment.vstart;
(*info).first_p2m_pfn = p2m_segment.pfn;
(*info).nr_p2m_frames = p2m_segment.pages;
(*info).flags = 0;
(*info).store_evtchn = state.store_evtchn;
(*info).store_mfn = setup.phys.p2m[state.xenstore_segment.pfn as usize];
let console = state.consoles.first().unwrap();
(*info).console.mfn = setup.phys.p2m[console.1.pfn as usize];
(*info).console.evtchn = console.0;
(*info).mod_start = state.initrd_segment.vstart;
(*info).mod_len = state.initrd_segment.size;
for (i, c) in cmdline.chars().enumerate() {
(*info).cmdline[i] = c as c_char;
}
(*info).cmdline[MAX_GUEST_CMDLINE - 1] = 0;
trace!("setup_start_info start_info={:?}", *info);
}
Ok(())
}
async fn setup_shared_info(
&mut self,
setup: &mut BootSetup,
shared_info_frame: u64,
) -> Result<()> {
let info = setup
.phys
.map_foreign_pages(shared_info_frame, X86_PAGE_SIZE)
.await? as *mut SharedInfo;
unsafe {
let size = size_of::<SharedInfo>();
let info_as_buff = slice::from_raw_parts_mut(info as *mut u8, size);
info_as_buff.fill(0);
for i in 0..32 {
(*info).vcpu_info[i].evtchn_upcall_mask = 1;
}
trace!("setup_shared_info shared_info={:?}", *info);
}
Ok(())
}
async fn meminit(
&mut self,
setup: &mut BootSetup,
total_pages: u64,
_: &Option<DomainSegment>,
_: &Option<DomainSegment>,
) -> Result<()> {
setup.call.claim_pages(setup.domid, total_pages).await?;
let mut vmemranges: Vec<VmemRange> = Vec::new();
let stub = VmemRange {
start: 0,
end: total_pages << XEN_PAGE_SHIFT,
end: domain.total_pages << XEN_PAGE_SHIFT,
_flags: 0,
_nid: 0,
};
@ -510,12 +486,10 @@ impl ArchBootSetup for X86BootSetup {
p2m_size = p2m_size.max(range.end >> XEN_PAGE_SHIFT);
}
if total != total_pages {
if total != domain.total_pages {
return Err(Error::MemorySetupFailed("total pages mismatch"));
}
setup.total_pages = total;
let mut p2m = vec![u64::MAX; p2m_size as usize];
for range in &vmemranges {
let mut extents_init = vec![0u64; SUPERPAGE_BATCH_SIZE as usize];
@ -544,10 +518,10 @@ impl ArchBootSetup for X86BootSetup {
}
let extents_init_slice = extents_init.as_slice();
let extents = setup
let extents = domain
.call
.populate_physmap(
setup.domid,
domain.domid,
count,
SUPERPAGE_2MB_SHIFT as u32,
0,
@ -575,9 +549,9 @@ impl ArchBootSetup for X86BootSetup {
let p2m_idx = (pfn_base + j) as usize;
let p2m_end_idx = p2m_idx + allocsz as usize;
let input_extent_starts = &p2m[p2m_idx..p2m_end_idx];
let result = setup
let result = domain
.call
.populate_physmap(setup.domid, allocsz, 0, 0, input_extent_starts)
.populate_physmap(domain.domid, allocsz, 0, 0, input_extent_starts)
.await?;
if result.len() != allocsz as usize {
@ -597,44 +571,297 @@ impl ArchBootSetup for X86BootSetup {
}
}
setup.phys.load_p2m(p2m);
setup.call.claim_pages(setup.domid, 0).await?;
domain.phys.load_p2m(p2m);
domain.call.claim_pages(domain.domid, 0).await?;
Ok(())
}
async fn bootlate(&mut self, setup: &mut BootSetup, state: &mut BootState) -> Result<()> {
let p2m_segment = state
async fn alloc_p2m_segment(
&mut self,
domain: &mut BootDomain,
) -> Result<Option<DomainSegment>> {
let mut p2m_alloc_size =
((domain.phys.p2m_size() * 8) + X86_PAGE_SIZE - 1) & !(X86_PAGE_SIZE - 1);
let from = domain.image_info.virt_p2m_base;
let to = from + p2m_alloc_size - 1;
let m = self.count_page_tables(domain, from, to, domain.pfn_alloc_end)?;
let pgtables: usize;
{
let map = &mut self.table.mappings[m];
map.area.pfn = domain.pfn_alloc_end;
for lvl_idx in 0..4 {
map.levels[lvl_idx].pfn += p2m_alloc_size >> X86_PAGE_SHIFT;
}
pgtables = map.area.pgtables;
}
self.table.mappings_count += 1;
p2m_alloc_size += (pgtables << X86_PAGE_SHIFT) as u64;
let p2m_segment = domain.alloc_segment(0, p2m_alloc_size).await?;
Ok(Some(p2m_segment))
}
async fn alloc_page_tables(
&mut self,
domain: &mut BootDomain,
) -> Result<Option<DomainSegment>> {
let mut extra_pages = 1;
extra_pages += (512 * 1024) / X86_PAGE_SIZE;
let mut pages = extra_pages;
let mut try_virt_end: u64;
let mut m: usize;
loop {
try_virt_end = BootDomain::round_up(
domain.virt_alloc_end + pages * X86_PAGE_SIZE,
BootDomain::bits_to_mask(22),
);
m = self.count_page_tables(domain, domain.image_info.virt_base, try_virt_end, 0)?;
pages = self.table.mappings[m].area.pgtables as u64 + extra_pages;
if domain.virt_alloc_end + pages * X86_PAGE_SIZE <= try_virt_end + 1 {
break;
}
}
self.table.mappings[m].area.pfn = 0;
self.table.mappings_count += 1;
domain.virt_pgtab_end = try_virt_end + 1;
let size = self.table.mappings[m].area.pgtables as u64 * X86_PAGE_SIZE;
let segment = domain.alloc_segment(0, size).await?;
debug!(
"alloc_page_tables table={:?} segment={:?}",
self.table, segment
);
Ok(Some(segment))
}
async fn setup_page_tables(&mut self, domain: &mut BootDomain) -> Result<()> {
let p2m_segment = self
.p2m_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("p2m_segment missing"))?;
let page_table_segment = state
let p2m_guest = unsafe {
slice::from_raw_parts_mut(
p2m_segment.addr as *mut u64,
domain.phys.p2m_size() as usize,
)
};
copy(p2m_guest, &domain.phys.p2m);
for l in (0usize..X86_PGTABLE_LEVELS as usize).rev() {
for m1 in 0usize..self.table.mappings_count {
let map1 = &self.table.mappings[m1];
let from = map1.levels[l].from;
let to = map1.levels[l].to;
let pg_ptr = domain.phys.pfn_to_ptr(map1.levels[l].pfn, 0).await? as *mut u64;
for m2 in 0usize..self.table.mappings_count {
let map2 = &self.table.mappings[m2];
let lvl = if l > 0 {
&map2.levels[l - 1]
} else {
&map2.area
};
if l > 0 && lvl.pgtables == 0 {
continue;
}
if lvl.from >= to || lvl.to <= from {
continue;
}
let p_s = (std::cmp::max(from, lvl.from) - from)
>> (X86_PAGE_SHIFT + l as u64 * X86_PGTABLE_LEVEL_SHIFT);
let p_e = (std::cmp::min(to, lvl.to) - from)
>> (X86_PAGE_SHIFT + l as u64 * X86_PGTABLE_LEVEL_SHIFT);
let rhs = X86_PAGE_SHIFT as usize + l * X86_PGTABLE_LEVEL_SHIFT as usize;
let mut pfn = ((std::cmp::max(from, lvl.from) - lvl.from) >> rhs) + lvl.pfn;
debug!(
"setup_page_tables lvl={} map_1={} map_2={} pfn={:#x} p_s={:#x} p_e={:#x}",
l, m1, m2, pfn, p_s, p_e
);
let pg = unsafe { slice::from_raw_parts_mut(pg_ptr, (p_e + 1) as usize) };
for p in p_s..p_e + 1 {
let prot = self.get_pg_prot(l, pfn);
let pfn_paddr = domain.phys.p2m[pfn as usize] << X86_PAGE_SHIFT;
let value = pfn_paddr | prot;
pg[p as usize] = value;
pfn += 1;
}
}
}
}
Ok(())
}
async fn setup_hypercall_page(&mut self, domain: &mut BootDomain) -> Result<()> {
if domain.image_info.virt_hypercall == u64::MAX {
return Ok(());
}
let pfn =
(domain.image_info.virt_hypercall - domain.image_info.virt_base) >> self.page_shift();
let mfn = domain.phys.p2m[pfn as usize];
domain.call.hypercall_init(domain.domid, mfn).await?;
Ok(())
}
async fn alloc_magic_pages(&mut self, domain: &mut BootDomain) -> Result<()> {
if domain.image_info.virt_p2m_base >= domain.image_info.virt_base
|| (domain.image_info.virt_p2m_base & ((1 << self.page_shift()) - 1)) != 0
{
self.p2m_segment = self.alloc_p2m_segment(domain).await?;
}
self.start_info_segment = Some(domain.alloc_page()?);
self.xenstore_segment = Some(domain.alloc_page()?);
domain.store_mfn = domain.phys.p2m[self.xenstore_segment.as_ref().unwrap().pfn as usize];
let evtchn = domain.call.evtchn_alloc_unbound(domain.domid, 0).await?;
let page = domain.alloc_page()?;
domain.console_evtchn = evtchn;
domain.console_mfn = domain.phys.p2m[page.pfn as usize];
self.page_table_segment = self.alloc_page_tables(domain).await?;
self.boot_stack_segment = Some(domain.alloc_page()?);
if domain.virt_pgtab_end > 0 {
domain.alloc_padding_pages(domain.virt_pgtab_end)?;
}
if self.p2m_segment.is_none() {
if let Some(mut p2m_segment) = self.alloc_p2m_segment(domain).await? {
p2m_segment.vstart = domain.image_info.virt_p2m_base;
self.p2m_segment = Some(p2m_segment);
}
}
Ok(())
}
async fn setup_shared_info(
&mut self,
domain: &mut BootDomain,
shared_info_frame: u64,
) -> Result<()> {
let info = domain
.phys
.map_foreign_pages(shared_info_frame, X86_PAGE_SIZE)
.await? as *mut SharedInfo;
unsafe {
let size = size_of::<SharedInfo>();
let info_as_buff = slice::from_raw_parts_mut(info as *mut u8, size);
info_as_buff.fill(0);
for i in 0..32 {
(*info).vcpu_info[i].evtchn_upcall_mask = 1;
}
trace!("setup_shared_info shared_info={:?}", *info);
}
Ok(())
}
async fn setup_start_info(
&mut self,
domain: &mut BootDomain,
shared_info_frame: u64,
) -> Result<()> {
let start_info_segment = self
.start_info_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("start_info_segment missing"))?;
let ptr = domain.phys.pfn_to_ptr(start_info_segment.pfn, 1).await?;
let byte_slice =
unsafe { slice::from_raw_parts_mut(ptr as *mut u8, X86_PAGE_SIZE as usize) };
byte_slice.fill(0);
let info = ptr as *mut StartInfo;
let page_table_segment = self
.page_table_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("page_table_segment missing"))?;
let p2m_segment = self
.p2m_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("p2m_segment missing"))?;
let xenstore_segment = self
.xenstore_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("xenstore_segment missing"))?;
unsafe {
for (i, c) in X86_GUEST_MAGIC.chars().enumerate() {
(*info).magic[i] = c as c_char;
}
(*info).magic[X86_GUEST_MAGIC.len()] = 0 as c_char;
(*info).nr_pages = domain.total_pages;
(*info).shared_info = shared_info_frame << X86_PAGE_SHIFT;
(*info).pt_base = page_table_segment.vstart;
(*info).nr_pt_frames = self.table.mappings[0].area.pgtables as u64;
(*info).mfn_list = p2m_segment.vstart;
(*info).first_p2m_pfn = p2m_segment.pfn;
(*info).nr_p2m_frames = p2m_segment.pages;
(*info).flags = 0;
(*info).store_evtchn = domain.store_evtchn;
(*info).store_mfn = domain.phys.p2m[xenstore_segment.pfn as usize];
(*info).console.mfn = domain.console_mfn;
(*info).console.evtchn = domain.console_evtchn;
(*info).mod_start = domain.initrd_segment.vstart;
(*info).mod_len = domain.initrd_segment.size;
for (i, c) in domain.cmdline.chars().enumerate() {
(*info).cmdline[i] = c as c_char;
}
(*info).cmdline[MAX_GUEST_CMDLINE - 1] = 0;
trace!("setup_start_info start_info={:?}", *info);
}
Ok(())
}
async fn bootlate(&mut self, domain: &mut BootDomain) -> Result<()> {
let p2m_segment = self
.p2m_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("p2m_segment missing"))?;
let page_table_segment = self
.page_table_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("page_table_segment missing"))?;
let pg_pfn = page_table_segment.pfn;
let pg_mfn = setup.phys.p2m[pg_pfn as usize];
setup.phys.unmap(pg_pfn)?;
setup.phys.unmap(p2m_segment.pfn)?;
setup
let pg_mfn = domain.phys.p2m[pg_pfn as usize];
domain.phys.unmap(pg_pfn)?;
domain.phys.unmap(p2m_segment.pfn)?;
let map = domain.call.get_memory_map(E820_MAX).await?;
let mem_mb = domain.total_pages >> (20 - self.page_shift());
let mem_kb = mem_mb * 1024;
let e820 = self.e820_sanitize(map, mem_kb, 0)?;
domain.call.set_memory_map(domain.domid, e820).await?;
domain
.call
.mmuext(setup.domid, MMUEXT_PIN_L4_TABLE, pg_mfn, 0)
.mmuext(domain.domid, MMUEXT_PIN_L4_TABLE, pg_mfn, 0)
.await?;
Ok(())
}
async fn vcpu(&mut self, setup: &mut BootSetup, state: &mut BootState) -> Result<()> {
let page_table_segment = state
async fn vcpu(&mut self, domain: &mut BootDomain) -> Result<()> {
let page_table_segment = self
.page_table_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("page_table_segment missing"))?;
let boot_stack_segment = self
.boot_stack_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("boot_stack_segment missing"))?;
let start_info_segment = self
.start_info_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("start_info_segment missing"))?;
let pg_pfn = page_table_segment.pfn;
let pg_mfn = setup.phys.p2m[pg_pfn as usize];
let mut vcpu = VcpuGuestContext::default();
vcpu.user_regs.rip = state.image_info.virt_entry;
let pg_mfn = domain.phys.p2m[pg_pfn as usize];
let mut vcpu = x8664VcpuGuestContext::default();
vcpu.user_regs.rip = domain.image_info.virt_entry;
vcpu.user_regs.rsp =
state.image_info.virt_base + (state.boot_stack_segment.pfn + 1) * self.page_size();
domain.image_info.virt_base + (boot_stack_segment.pfn + 1) * self.page_size();
vcpu.user_regs.rsi =
state.image_info.virt_base + (state.start_info_segment.pfn) * self.page_size();
domain.image_info.virt_base + (start_info_segment.pfn) * self.page_size();
vcpu.user_regs.rflags = 1 << 9;
vcpu.debugreg[6] = 0xffff0ff0;
vcpu.debugreg[7] = 0x00000400;
@ -651,7 +878,43 @@ impl ArchBootSetup for X86BootSetup {
vcpu.kernel_ss = vcpu.user_regs.ss as u64;
vcpu.kernel_sp = vcpu.user_regs.rsp;
trace!("vcpu context: {:?}", vcpu);
setup.call.set_vcpu_context(setup.domid, 0, &vcpu).await?;
domain
.call
.set_vcpu_context(domain.domid, 0, VcpuGuestContextAny { value: vcpu })
.await?;
Ok(())
}
async fn gnttab_seed(&mut self, domain: &mut BootDomain) -> Result<()> {
let xenstore_segment = self
.xenstore_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("xenstore_segment missing"))?;
let console_gfn = domain.console_mfn as usize;
let xenstore_gfn = domain.phys.p2m[xenstore_segment.pfn as usize];
let addr = domain
.call
.mmap(0, 1 << XEN_PAGE_SHIFT)
.await
.ok_or(Error::MmapFailed)?;
domain
.call
.map_resource(domain.domid, 1, 0, 0, 1, addr)
.await?;
let entries = unsafe { slice::from_raw_parts_mut(addr as *mut GrantEntry, 2) };
entries[0].flags = 1 << 0;
entries[0].domid = 0;
entries[0].frame = console_gfn as u32;
entries[1].flags = 1 << 0;
entries[1].domid = 0;
entries[1].frame = xenstore_gfn as u32;
unsafe {
let result = munmap(addr as *mut c_void, 1 << XEN_PAGE_SHIFT);
if result != 0 {
return Err(Error::UnmapFailed(Errno::from_raw(result)));
}
}
Ok(())
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-xenstore"
description = "A client that interacts with xenstore for krata."
description = "A client that interacts with xenstore for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true

View File

@ -108,7 +108,6 @@ impl XsdClient {
}
async fn write<P: AsRef<str>>(&self, tx: u32, path: P, data: Vec<u8>) -> Result<bool> {
trace!("write tx={tx} path={} data={:?}", path.as_ref(), data);
let mut buffer = Vec::new();
let path = CString::new(path.as_ref())?;
buffer.extend_from_slice(path.as_bytes_with_nul());
@ -120,6 +119,11 @@ impl XsdClient {
response.parse_bool()
}
async fn write_string<P: AsRef<str>>(&self, tx: u32, path: P, data: &str) -> Result<bool> {
trace!("write tx={tx} path={} data=\"{}\"", path.as_ref(), data);
self.write(tx, path, data.as_bytes().to_vec()).await
}
async fn mkdir<P: AsRef<str>>(&self, tx: u32, path: P) -> Result<bool> {
trace!("mkdir tx={tx} path={}", path.as_ref());
self.socket
@ -247,7 +251,7 @@ impl XsdInterface for XsdClient {
}
async fn write_string<P: AsRef<str>>(&self, path: P, data: &str) -> Result<bool> {
self.write(0, path, data.as_bytes().to_vec()).await
self.write_string(0, path, data).await
}
async fn mkdir<P: AsRef<str>>(&self, path: P) -> Result<bool> {
@ -287,9 +291,7 @@ impl XsdInterface for XsdTransaction {
}
async fn write_string<P: AsRef<str>>(&self, path: P, data: &str) -> Result<bool> {
self.client
.write(self.tx, path, data.as_bytes().to_vec())
.await
self.client.write_string(self.tx, path, data).await
}
async fn mkdir<P: AsRef<str>>(&self, path: P) -> Result<bool> {

View File

@ -28,5 +28,5 @@ build_and_run() {
fi
RUST_TARGET="$(./hack/build/target.sh)"
./hack/build/cargo.sh build ${CARGO_BUILD_FLAGS} --bin "${EXE_TARGET}"
exec sudo sh -c "RUST_LOG='${RUST_LOG}' 'target/${RUST_TARGET}/debug/${EXE_TARGET}' $*"
exec sudo -E sh -c "RUST_LOG='${RUST_LOG}' 'target/${RUST_TARGET}/debug/${EXE_TARGET}' $*"
}

2
hack/dist/apk.sh vendored
View File

@ -21,7 +21,7 @@ fpm -s tar -t apk \
--architecture "${TARGET_ARCH}" \
--depends "squashfs-tools" \
--depends "erofs-utils" \
--description "Krata Hypervisor" \
--description "Krata Isolation Engine" \
--url "https://krata.dev" \
--maintainer "Edera Team <contact@edera.dev>" \
"${OUTPUT_DIR}/system-openrc-${TARGET_ARCH}.tgz"

11
hack/dist/bundle.sh vendored
View File

@ -4,11 +4,6 @@ set -e
# shellcheck source-path=SCRIPTDIR source=common.sh
. "$(dirname "${0}")/common.sh"
if [ -z "${KRATA_KERNEL_BUILD_JOBS}" ]
then
KRATA_KERNEL_BUILD_JOBS="2"
fi
TARGET_ARCH="$("${KRATA_DIR}/hack/build/arch.sh")"
BUNDLE_TAR="${OUTPUT_DIR}/bundle-systemd-${TARGET_ARCH}.tgz"
rm -f "${BUNDLE_TAR}"
@ -24,15 +19,13 @@ do
cp "${KRATA_DIR}/target/${RUST_TARGET}/release/${X}" "${BUNDLE_DIR}/${X}"
done
./hack/initrd/build.sh
if [ "${KRATA_KERNEL_BUILD_SKIP}" != "1" ]
then
./hack/kernel/build.sh "-j${KRATA_KERNEL_BUILD_JOBS}"
fi
./hack/kernel/fetch.sh
cd "${BUNDLE_DIR}"
cp "${KRATA_DIR}/target/initrd/initrd-${TARGET_ARCH}" initrd
cp "${KRATA_DIR}/target/kernel/kernel-${TARGET_ARCH}" kernel
cp "${KRATA_DIR}/target/kernel/addons-${TARGET_ARCH}.squashfs" addons.squashfs
cp "${KRATA_DIR}/resources/systemd/kratad.service" kratad.service
cp "${KRATA_DIR}/resources/systemd/kratanet.service" kratanet.service
cp "${KRATA_DIR}/resources/bundle/install.sh" install.sh

2
hack/dist/deb.sh vendored
View File

@ -22,7 +22,7 @@ fpm -s tar -t deb \
--depends "xen-system-${TARGET_ARCH_DEBIAN}" \
--depends "squashfs-tools" \
--depends "erofs-utils" \
--description "Krata Hypervisor" \
--description "Krata Isolation Engine" \
--url "https://krata.dev" \
--maintainer "Edera Team <contact@edera.dev>" \
-x "usr/lib/**" \

5
hack/dist/systar.sh vendored
View File

@ -22,9 +22,9 @@ tar xf "${OUTPUT_DIR}/bundle-systemd-${TARGET_ARCH}.tgz"
mkdir sys
cd sys
mkdir -p usr/bin usr/libexec
mkdir -p usr/bin usr/sbin
mv ../krata/kratactl usr/bin
mv ../krata/kratanet ../krata/kratad usr/libexec/
mv ../krata/kratanet ../krata/kratad usr/sbin/
if [ "${SYSTAR_VARIANT}" = "openrc" ]
then
@ -40,6 +40,7 @@ fi
mkdir -p usr/share/krata/guest
mv ../krata/kernel ../krata/initrd usr/share/krata/guest
mv ../krata/addons.squashfs usr/share/krata/guest/addons.squashfs
tar czf "${SYSTAR}" --owner 0 --group 0 .

View File

@ -1,76 +0,0 @@
#!/bin/sh
set -e
REAL_SCRIPT="$(realpath "${0}")"
cd "$(dirname "${REAL_SCRIPT}")/../.."
KRATA_DIR="${PWD}"
KERNEL_DIR="${KRATA_DIR}/kernel"
cd "${KRATA_DIR}"
TARGET_ARCH_STANDARD="$(KRATA_ARCH_KERNEL_NAME=0 ./hack/build/arch.sh)"
TARGET_ARCH_KERNEL="$(KRATA_ARCH_KERNEL_NAME=1 ./hack/build/arch.sh)"
C_TARGET="$(KRATA_TARGET_C_MODE=1 KRATA_TARGET_IGNORE_LIBC=1 ./hack/build/target.sh)"
IS_CROSS_COMPILE="$(./hack/build/cross-compile.sh)"
if [ "${IS_CROSS_COMPILE}" = "1" ]
then
CROSS_COMPILE_MAKE="CROSS_COMPILE=${C_TARGET}-"
else
CROSS_COMPILE_MAKE="CROSS_COMPILE="
fi
# shellcheck source-path=SCRIPTDIR source=../../kernel/config.sh
. "${KERNEL_DIR}/config.sh"
KERNEL_SRC="${KERNEL_DIR}/linux-${KERNEL_VERSION}-${TARGET_ARCH_STANDARD}"
if [ -z "${KRATA_KERNEL_BUILD_JOBS}" ]
then
KRATA_KERNEL_BUILD_JOBS="$(nproc)"
fi
if [ ! -f "${KERNEL_SRC}/Makefile" ]
then
rm -rf "${KERNEL_SRC}"
mkdir -p "${KERNEL_SRC}"
curl --progress-bar -L -o "${KERNEL_SRC}.txz" "${KERNEL_SRC_URL}"
tar xf "${KERNEL_SRC}.txz" --strip-components 1 -C "${KERNEL_SRC}"
rm "${KERNEL_SRC}.txz"
fi
OUTPUT_DIR="${KRATA_DIR}/target/kernel"
mkdir -p "${OUTPUT_DIR}"
KERNEL_CONFIG_FILE="${KERNEL_DIR}/krata-${TARGET_ARCH_STANDARD}.config"
if [ ! -f "${KERNEL_CONFIG_FILE}" ]
then
echo "ERROR: kernel config file not found for ${TARGET_ARCH_STANDARD}" > /dev/stderr
exit 1
fi
cp "${KERNEL_CONFIG_FILE}" "${KERNEL_SRC}/.config"
make -C "${KERNEL_SRC}" ARCH="${TARGET_ARCH_KERNEL}" "${CROSS_COMPILE_MAKE}" olddefconfig
IMAGE_TARGET="bzImage"
if [ "${TARGET_ARCH_STANDARD}" = "x86_64" ]
then
IMAGE_TARGET="bzImage"
elif [ "${TARGET_ARCH_STANDARD}" = "aarch64" ]
then
IMAGE_TARGET="Image.gz"
fi
make -C "${KERNEL_SRC}" ARCH="${TARGET_ARCH_KERNEL}" -j"${KRATA_KERNEL_BUILD_JOBS}" "${CROSS_COMPILE_MAKE}" "${IMAGE_TARGET}"
if [ "${TARGET_ARCH_STANDARD}" = "x86_64" ]
then
cp "${KERNEL_SRC}/arch/x86/boot/bzImage" "${OUTPUT_DIR}/kernel-${TARGET_ARCH_STANDARD}"
elif [ "${TARGET_ARCH_STANDARD}" = "aarch64" ]
then
cp "${KERNEL_SRC}/arch/arm64/boot/Image.gz" "${OUTPUT_DIR}/kernel-${TARGET_ARCH_STANDARD}"
else
echo "ERROR: unable to determine what file is the vmlinuz for ${TARGET_ARCH_STANDARD}" > /dev/stderr
exit 1
fi

19
hack/kernel/fetch.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
set -e
REAL_SCRIPT="$(realpath "${0}")"
cd "$(dirname "${REAL_SCRIPT}")/../.."
KRATA_DIR="${PWD}"
cd "${KRATA_DIR}"
HOST_RUST_TARGET="$(TARGET_ARCH="" TARGET_LIBC="" ./hack/build/target.sh)"
TARGET_ARCH="$(./hack/build/arch.sh)"
if [ "${1}" != "-u" ] && [ -f "target/kernel/kernel-${TARGET_ARCH}" ]
then
exit 0
fi
export TARGET_ARCH
TARGET_ARCH="" TARGET_LIBC="" RUST_TARGET="${HOST_RUST_TARGET}" ./hack/build/cargo.sh build -q --bin build-fetch-kernel
exec "target/${HOST_RUST_TARGET}/debug/build-fetch-kernel" "ghcr.io/edera-dev/kernels:latest"

View File

@ -99,7 +99,10 @@ sudo mount --make-private "${ROOT_DIR}/sys"
sudo cp "${PWD}/os/internal/stage2.sh" "${ROOT_DIR}/stage2.sh"
echo "${ROOT_UUID}" | sudo tee "${ROOT_DIR}/root-uuid" > /dev/null
sudo mv "${ROOT_DIR}/etc/resolv.conf" "${ROOT_DIR}/etc/resolv.conf.orig"
sudo cp "/etc/resolv.conf" "${ROOT_DIR}/etc/resolv.conf"
sudo chroot "${ROOT_DIR}" /bin/sh -c "/stage2.sh ${TARGET_ARCH} ${TARGET_ARCH_ALT}"
sudo mv "${ROOT_DIR}/etc/resolv.conf.orig" "${ROOT_DIR}/etc/resolv.conf"
sudo rm -f "${ROOT_DIR}/stage2.sh"
sudo rm -f "${ROOT_DIR}/root-uuid"

View File

@ -0,0 +1,10 @@
FROM rust:1.79-alpine AS build
RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/*
ENV TARGET_LIBC=musl TARGET_VENDOR=unknown
WORKDIR /usr/src/app
COPY . .
RUN ./hack/initrd/build.sh && cp target/initrd/initrd-* target/initrd/initrd
FROM scratch AS final
COPY --from=build /usr/src/app/target/initrd/initrd /krata/initrd

View File

@ -0,0 +1,12 @@
FROM rust:1.79-alpine AS build
RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/*
ENV TARGET_LIBC=musl TARGET_VENDOR=unknown
WORKDIR /usr/src/app
COPY . .
RUN ./hack/build/cargo.sh build --release --bin kratactl
RUN mv ./target/$(./hack/build/target.sh)/release/kratactl /usr/sbin
FROM scratch
ENTRYPOINT ["/usr/sbin/kratactl"]
COPY --from=build /usr/sbin/kratactl /usr/sbin/kratactl

13
images/Dockerfile.kratad Normal file
View File

@ -0,0 +1,13 @@
FROM rust:1.79-alpine AS build
RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/*
ENV TARGET_LIBC=musl TARGET_VENDOR=unknown
WORKDIR /usr/src/app
COPY . .
RUN ./hack/build/cargo.sh build --release --bin kratad
RUN mv ./target/$(./hack/build/target.sh)/release/kratad /usr/sbin
FROM scratch
ENTRYPOINT ["/usr/sbin/kratad"]
COPY --from=build /usr/sbin/kratad /usr/sbin/kratad
COPY ./resources/systemd/kratad.service /usr/lib/systemd/system/kratad.service

View File

@ -0,0 +1,13 @@
FROM rust:1.79-alpine AS build
RUN apk update && apk add protoc protobuf-dev build-base && rm -rf /var/cache/apk/*
ENV TARGET_LIBC=musl TARGET_VENDOR=unknown
WORKDIR /usr/src/app
COPY . .
RUN ./hack/build/cargo.sh build --release --bin kratanet
RUN mv ./target/$(./hack/build/target.sh)/release/kratanet /usr/sbin
FROM scratch
ENTRYPOINT ["/usr/sbin/kratanet"]
COPY --from=build /usr/sbin/kratanet /usr/sbin/kratanet
COPY ./resources/systemd/kratanet.service /usr/lib/systemd/system/kratanet.service

1
kernel/.gitignore vendored
View File

@ -1 +0,0 @@
/linux-*

View File

@ -1,3 +0,0 @@
#!/bin/sh
KERNEL_VERSION="6.7.2"
KERNEL_SRC_URL="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KERNEL_VERSION}.tar.xz"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More