Compare commits

...

261 Commits

Author SHA1 Message Date
c49ac181fb added a technical overview and added a link in the readme 2025-07-02 14:21:22 -07:00
53052b24a0 [StepSecurity] Apply security best practices (#473)
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
2025-06-06 18:36:16 +00:00
ea3ac96926 build(deps): bump the actions-updates group across 1 directory with 3 updates (#467)
Bumps the actions-updates group with 3 updates in the / directory: [step-security/harden-runner](https://github.com/step-security/harden-runner), [actions/create-github-app-token](https://github.com/actions/create-github-app-token) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `step-security/harden-runner` from 2.10.2 to 2.11.0
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](0080882f6c...4d991eb9b9)

Updates `actions/create-github-app-token` from 1.11.0 to 1.11.6
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](5d869da34e...21cfef2b49)

Updates `MarcoIeni/release-plz-action` from 0.5.86 to 0.5.99
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](db75300cf2...476794ede1)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 14:26:50 +00:00
cc1ff65869 build(deps): bump the dep-updates group across 1 directory with 4 updates (#441)
Bumps the dep-updates group with 4 updates in the / directory: [async-trait](https://github.com/dtolnay/async-trait), [env_logger](https://github.com/rust-cli/env_logger), [libc](https://github.com/rust-lang/libc) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `async-trait` from 0.1.83 to 0.1.85
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.85)

Updates `env_logger` from 0.11.5 to 0.11.6
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.5...v0.11.6)

Updates `libc` from 0.2.168 to 0.2.169
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.169/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.168...0.2.169)

Updates `thiserror` from 2.0.7 to 2.0.9
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.7...2.0.9)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 06:38:16 +00:00
b7dd2b8d89 chore: release v0.0.24 (#388)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-12-14 23:53:21 +00:00
bf8a8379db fix(xenplatform): use cfg attributes for returning supported platforms 2024-12-14 18:46:45 -05:00
e0bbeb5d64 fix(xenclient): examples should use supported platform 2024-12-14 18:33:53 -05:00
4b0f3782bd fix(xenclient): boot example should use unsupported platform on aarch64 2024-12-14 18:22:37 -05:00
3adf9b5e88 feat(xen): update xenclient and xenplatform to the latest structure (#433) 2024-12-14 18:16:10 -05:00
f9d4508149 build(deps): bump the actions-updates group across 1 directory with 3 updates (#427)
Bumps the actions-updates group with 3 updates in the / directory: [step-security/harden-runner](https://github.com/step-security/harden-runner), [actions/checkout](https://github.com/actions/checkout) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `step-security/harden-runner` from 2.10.1 to 2.10.2
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](91182cccc0...0080882f6c)

Updates `actions/checkout` from 4.2.1 to 4.2.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](eef61447b9...11bd71901b)

Updates `MarcoIeni/release-plz-action` from 0.5.76 to 0.5.86
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](ede2f28fa4...db75300cf2)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Zenla <alex@edera.dev>
2024-12-14 18:10:32 -05:00
54dc9cbdaa chore(deps): upgrade dependencies and clean code (#432) 2024-12-14 18:09:56 -05:00
ea9624955c feat(evtchn): harden evtchn handling and improve api (#431) 2024-12-14 18:03:33 -05:00
960578efc4 feat(xencall): improve asynchronous support (#430) 2024-12-14 18:00:11 -05:00
d7affe6c8c feat(xenstore): multi-watch and maybe-commit support (#429) 2024-12-14 17:57:15 -05:00
a04a812f28 build(deps): bump hashbrown from 0.15.0 to 0.15.2 in the cargo group (#426)
Bumps the cargo group with 1 update: [hashbrown](https://github.com/rust-lang/hashbrown).


Updates `hashbrown` from 0.15.0 to 0.15.2
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/commits)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 01:07:56 +00:00
9f6383b4c5 build(deps): bump the dep-updates group across 1 directory with 4 updates (#415)
Bumps the dep-updates group with 4 updates in the / directory: [libc](https://github.com/rust-lang/libc), [regex](https://github.com/rust-lang/regex), [thiserror](https://github.com/dtolnay/thiserror) and [tokio](https://github.com/tokio-rs/tokio).


Updates `libc` from 0.2.159 to 0.2.162
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.162/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.159...0.2.162)

Updates `regex` from 1.11.0 to 1.11.1
- [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.11.0...1.11.1)

Updates `thiserror` from 1.0.64 to 1.0.65
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...1.0.65)

Updates `tokio` from 1.40.0 to 1.41.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.40.0...tokio-1.41.1)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 11:31:15 +00:00
7f5a8c7a6e fix(xenplatform): e820 sanitize should now produce valid mappings 2024-10-17 13:27:23 -04:00
b1e7a05440 build(deps): bump the actions-updates group across 1 directory with 2 updates (#404)
Bumps the actions-updates group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `actions/checkout` from 4.2.0 to 4.2.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](d632683dd7...eef61447b9)

Updates `MarcoIeni/release-plz-action` from 0.5.72 to 0.5.76
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](dbf3db74cd...ede2f28fa4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 16:10:56 +00:00
998df55711 build(deps): bump uuid from 1.10.0 to 1.11.0 in the dep-updates group (#405)
Bumps the dep-updates group with 1 update: [uuid](https://github.com/uuid-rs/uuid).


Updates `uuid` from 1.10.0 to 1.11.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.10.0...1.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 16:10:18 +00:00
3138c3e3e5 build(deps): bump MarcoIeni/release-plz-action (#399)
Bumps the actions-updates group with 1 update: [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `MarcoIeni/release-plz-action` from 0.5.71 to 0.5.72
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](91356927c5...dbf3db74cd)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 21:00:36 +00:00
d12fee305c build(deps): bump the actions-updates group across 1 directory with 2 updates (#397)
Bumps the actions-updates group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `actions/checkout` from 4.1.7 to 4.2.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](692973e3d9...d632683dd7)

Updates `MarcoIeni/release-plz-action` from 0.5.65 to 0.5.71
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](e28810957e...91356927c5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:53:34 +00:00
66aef77b05 build(deps): bump the dep-updates group across 1 directory with 6 updates (#398)
Bumps the dep-updates group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.82` | `0.1.83` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.0.33` | `1.0.34` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.5.0` | `2.6.0` |
| [libc](https://github.com/rust-lang/libc) | `0.2.158` | `0.2.159` |
| [regex](https://github.com/rust-lang/regex) | `1.10.6` | `1.11.0` |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.63` | `1.0.64` |



Updates `async-trait` from 0.1.82 to 0.1.83
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.82...0.1.83)

Updates `flate2` from 1.0.33 to 1.0.34
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Changelog](https://github.com/rust-lang/flate2-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.33...1.0.34)

Updates `indexmap` from 2.5.0 to 2.6.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.5.0...2.6.0)

Updates `libc` from 0.2.158 to 0.2.159
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.159/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.158...0.2.159)

Updates `regex` from 1.10.6 to 1.11.0
- [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.6...1.11.0)

Updates `thiserror` from 1.0.63 to 1.0.64
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:53:19 +00:00
367b4e5124 chore: release (#387)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-09-17 04:19:00 +00:00
b9559525c6 chore: release (#386)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-09-17 03:37:45 +00:00
cc63412a7f relicense to GPL v2.0 2024-09-16 02:32:59 -04:00
aa473b1c8b build(deps): bump the dep-updates group across 1 directory with 3 updates (#385)
Bumps the dep-updates group with 3 updates in the / directory: [async-trait](https://github.com/dtolnay/async-trait), [indexmap](https://github.com/indexmap-rs/indexmap) and [tokio](https://github.com/tokio-rs/tokio).


Updates `async-trait` from 0.1.81 to 0.1.82
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.81...0.1.82)

Updates `indexmap` from 2.4.0 to 2.5.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.4.0...2.5.0)

Updates `tokio` from 1.39.3 to 1.40.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.3...tokio-1.40.0)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 06:27:26 +00:00
7e34766bdc build(deps): bump the actions-updates group with 2 updates (#384)
Bumps the actions-updates group with 2 updates: [step-security/harden-runner](https://github.com/step-security/harden-runner) and [actions/create-github-app-token](https://github.com/actions/create-github-app-token).


Updates `step-security/harden-runner` from 2.9.1 to 2.10.1
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](5c7944e73c...91182cccc0)

Updates `actions/create-github-app-token` from 1.10.3 to 1.11.0
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](31c86eb3b3...5d869da34e)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 06:27:16 +00:00
83fd7607dd preparations for xen control-plane 2024-09-16 02:13:35 -04:00
ab0d66c611 fix release-plz workflow 2024-09-16 02:05:46 -04:00
67652a5e1b krata xen control-plane 2024-09-16 02:03:43 -04:00
a320efad6b chore: release (#368)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-09-03 21:13:00 +00:00
a61e2fb30a build(deps): bump quinn-proto from 0.11.6 to 0.11.8 in the cargo group (#373)
Bumps the cargo group with 1 update: [quinn-proto](https://github.com/quinn-rs/quinn).


Updates `quinn-proto` from 0.11.6 to 0.11.8
- [Release notes](https://github.com/quinn-rs/quinn/releases)
- [Commits](https://github.com/quinn-rs/quinn/compare/quinn-proto-0.11.6...quinn-proto-0.11.8)

---
updated-dependencies:
- dependency-name: quinn-proto
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 20:55:54 +00:00
3cb0e214e9 chore: release (#362)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-29 01:50:12 +00:00
0e0c5264eb build(deps): bump the dep-updates group with 2 updates (#367)
Bumps the dep-updates group with 2 updates: [tonic-build](https://github.com/hyperium/tonic) and [tonic](https://github.com/hyperium/tonic).


Updates `tonic-build` from 0.12.1 to 0.12.2
- [Release notes](https://github.com/hyperium/tonic/releases)
- [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/tonic/compare/v0.12.1...v0.12.2)

Updates `tonic` from 0.12.1 to 0.12.2
- [Release notes](https://github.com/hyperium/tonic/releases)
- [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/tonic/compare/v0.12.1...v0.12.2)

---
updated-dependencies:
- dependency-name: tonic-build
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tonic
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-27 19:23:26 +00:00
19f35ef20a feature(krata): implement network reservation list (#366) 2024-08-26 19:05:57 +00:00
79e27256e6 build(deps): bump the dep-updates group with 6 updates (#365)
Bumps the dep-updates group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.0.32` | `1.0.33` |
| [ratatui](https://github.com/ratatui/ratatui) | `0.28.0` | `0.28.1` |
| [redb](https://github.com/cberner/redb) | `2.1.1` | `2.1.2` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.125` | `1.0.127` |
| [sysinfo](https://github.com/GuillaumeGomez/sysinfo) | `0.31.2` | `0.31.3` |
| [serde](https://github.com/serde-rs/serde) | `1.0.208` | `1.0.209` |


Updates `flate2` from 1.0.32 to 1.0.33
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Changelog](https://github.com/rust-lang/flate2-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.32...1.0.33)

Updates `ratatui` from 0.28.0 to 0.28.1
- [Release notes](https://github.com/ratatui/ratatui/releases)
- [Changelog](https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ratatui/ratatui/compare/v0.28.0...v0.28.1)

Updates `redb` from 2.1.1 to 2.1.2
- [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.1...v2.1.2)

Updates `serde_json` from 1.0.125 to 1.0.127
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/1.0.125...1.0.127)

Updates `sysinfo` from 0.31.2 to 0.31.3
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.31.2...v0.31.3)

Updates `serde` from 1.0.208 to 1.0.209
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.208...v1.0.209)

---
updated-dependencies:
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: ratatui
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: redb
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: sysinfo
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Zenla <alex@edera.dev>
2024-08-26 08:27:30 +00:00
b6c726e7aa feature(kernel): switch to linux-kernel-oci images (#364) 2024-08-26 06:18:56 +00:00
0d2b7a3ae3 feature(zone-exec): implement terminal resize support (#363) 2024-08-26 04:43:07 +00:00
f1e3d59b6a chore: release (#354)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-26 01:38:30 +00:00
0106b85de9 fix(zone-exec): catch panic errors and show all errors immediately (#359) 2024-08-25 07:16:20 +00:00
96ccbd50bb fix(zone-exec): ensure that the underlying process is killed when rpc is closed (#361) 2024-08-25 07:07:37 +00:00
41aa1aa707 fix(rpc): rename HostStatus to GetHostStatus (#360) 2024-08-25 06:24:46 +00:00
ec74bc8d2b fix(console): don't replay history when attaching to the console (#358) 2024-08-25 03:49:33 +00:00
694de5d1fd chore(control): split out all of the rpc calls into their own files (#357) 2024-08-25 03:03:20 +00:00
f2db826ba6 feature(config): write default config to config.toml on startup (#356) 2024-08-25 00:48:38 +00:00
7f5609a846 feature(ctl): add --format option to host status and improve cpu topology format (#355) 2024-08-23 19:26:23 +00:00
adb7b29354 chore: release (#341)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-22 23:43:03 +00:00
bd448ee8d9 fix(network): allocate host ip from allocation pool (#353) 2024-08-22 22:52:38 +00:00
1647a07226 fix(daemon): turn off trace logging (#352) 2024-08-21 22:04:15 +00:00
151b43eeec feature(zone): kernel command line control on launch (#351) 2024-08-21 20:51:09 +00:00
1123a1a50a build(deps): bump the dep-updates group across 1 directory with 3 updates (#350)
Bumps the dep-updates group with 3 updates in the / directory: [flate2](https://github.com/rust-lang/flate2-rs), [libc](https://github.com/rust-lang/libc) and [reqwest](https://github.com/seanmonstar/reqwest).


Updates `flate2` from 1.0.31 to 1.0.32
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Changelog](https://github.com/rust-lang/flate2-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.31...1.0.32)

Updates `libc` from 0.2.157 to 0.2.158
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.158/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.157...0.2.158)

Updates `reqwest` from 0.12.5 to 0.12.7
- [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.5...v0.12.7)

---
updated-dependencies:
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-21 06:16:57 +00:00
6a6b5b6e0b feature(xen-preflight): test for hypervisor presence explicitly and error if missing (#347)
Fixes #309
2024-08-20 00:22:28 +00:00
274136825a build(deps): bump MarcoIeni/release-plz-action in the dep-updates group (#345)
Bumps the dep-updates group with 1 update: [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `MarcoIeni/release-plz-action` from 0.5.64 to 0.5.65
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](92ae919a6b...e28810957e)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-20 00:01:47 +00:00
2ab2cda937 Add support for reading hypervisor console (#344)
* feature(xencall): add hypervisor SYSCTL_readconsole definitions

* feature(hypervisor-dmesg): xencall: add read_console_ring_raw hypercall wrapper

* feature(hypervisor-dmesg): protobuf: add ReadHypervisorConsoleRing RPC

* feature(hypervisor-dmesg): runtime: add read_hypervisor_console wrapper

* feature(hypervisor-dmesg): daemon: add ReadHypervisorConsoleRing rpc implementation

* feature(hypervisor-dmesg): ctl: add host hypervisor-messages command to get hypervisor messages

* feature(hypervisor-dmesg): cli: rename hypervisor-messages command to hv-console

* feature(hypervisor-dmesg): proto: change ReadHypervisorConsoleRing to ReadHypervisorConsole

* feature(hypervisor-dmesg): fix up kratactl protobuf calls
2024-08-19 23:49:02 +00:00
2519d76479 build(deps): bump the dep-updates group with 3 updates (#346)
Bumps the dep-updates group with 3 updates: [arrayvec](https://github.com/bluss/arrayvec), [libc](https://github.com/rust-lang/libc) and [tokio](https://github.com/tokio-rs/tokio).


Updates `arrayvec` from 0.7.4 to 0.7.6
- [Release notes](https://github.com/bluss/arrayvec/releases)
- [Changelog](https://github.com/bluss/arrayvec/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bluss/arrayvec/compare/0.7.4...0.7.6)

Updates `libc` from 0.2.156 to 0.2.157
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.157/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.156...0.2.157)

Updates `tokio` from 1.39.2 to 1.39.3
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.2...tokio-1.39.3)

---
updated-dependencies:
- dependency-name: arrayvec
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 23:46:46 +00:00
dbeb8bf43b build(deps): bump the dep-updates group with 3 updates (#343)
Bumps the dep-updates group with 3 updates: [libc](https://github.com/rust-lang/libc), [clap](https://github.com/clap-rs/clap) and [serde](https://github.com/serde-rs/serde).


Updates `libc` from 0.2.155 to 0.2.156
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.156/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.155...0.2.156)

Updates `clap` from 4.5.15 to 4.5.16
- [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.15...clap_complete-v4.5.16)

Updates `serde` from 1.0.207 to 1.0.208
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.207...v1.0.208)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-16 06:39:43 +00:00
6093627bdd cleanup(ctl): move logic for branching ctl run steps into ControlCommands (#342) 2024-08-16 02:32:30 +00:00
1d75dfb88a chore: release (#334)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-15 19:06:56 +00:00
18bf370f74 feature(krata): first pass on cpu hotplug support (#340)
* fix(runtime): adjust memory resources inside a transaction

* feature(krata): first pass on cpu hotplug support
2024-08-15 08:06:04 +00:00
506d2ccf46 build(deps): bump serde_json in the dep-updates group (#339)
Bumps the dep-updates group with 1 update: [serde_json](https://github.com/serde-rs/json).


Updates `serde_json` from 1.0.124 to 1.0.125
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.124...1.0.125)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-15 06:20:52 +00:00
6096dee2fe chore(document-custom-kernels): Add initial documentation on custom kernels (#337) 2024-08-15 00:10:56 +00:00
bf3b73bf24 feature(exec): implement tty support (fixes #335) (#336) 2024-08-14 19:45:59 +00:00
87530edf70 feature(krata): dynamic resource allocation (closes #298) (#333) 2024-08-14 08:14:49 +00:00
1dca770091 chore: release (#306)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-14 06:37:52 +00:00
01a94ad23e feature(krata): prepare for workload rework (#276)
* chore(code): simple code cleanup

* chore(code): additional code cleanup

* feature(krata): rework api and make ip assignment persistent to database

* rework and cleanup

* fix daemon config references
2024-08-14 06:17:47 +00:00
2a107a370f build(deps): bump the dep-updates group with 2 updates (#332)
Bumps the dep-updates group with 2 updates: [ctrlc](https://github.com/Detegr/rust-ctrlc) and [indexmap](https://github.com/indexmap-rs/indexmap).


Updates `ctrlc` from 3.4.4 to 3.4.5
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.4...3.4.5)

Updates `indexmap` from 2.3.0 to 2.4.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.3.0...2.4.0)

---
updated-dependencies:
- dependency-name: ctrlc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 05:39:00 +00:00
313d3f72a5 build(deps): bump docker/build-push-action in the dep-updates group (#331)
Bumps the dep-updates group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `docker/build-push-action` from 6.6.1 to 6.7.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](16ebe778df...5cd11c3a4c)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 05:23:20 +00:00
5ec3d9d5c1 xencall: use correct op for setting cpufreq governor (#327) 2024-08-14 03:11:08 +00:00
1cf03a460e fix(idm): reimplement packet processing algorithm (#330)
* chore(xen): rewrite event channel code

* fix(idm): repair idm bugs on the file backend
2024-08-13 23:18:27 +00:00
ffc9dcc0ea build(deps): bump serde from 1.0.206 to 1.0.207 in the dep-updates group (#324)
Bumps the dep-updates group with 1 update: [serde](https://github.com/serde-rs/serde).


Updates `serde` from 1.0.206 to 1.0.207
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.206...v1.0.207)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-13 16:58:37 +00:00
0358c9c775 fix(power-trap-eacces): gracefully handle hypercall errors in power management (#325)
* daemon: reenable built-in power management policy

* xenruntime: gracefully handle power management errors
2024-08-13 08:22:05 +00:00
dcffaf110e build(deps): bump the dep-updates group with 2 updates (#316)
Bumps the dep-updates group with 2 updates: [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) and [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `sigstore/cosign-installer` from 3.5.0 to 3.6.0
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](59acb6260d...4959ce089c)

Updates `docker/build-push-action` from 6.5.0 to 6.6.1
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](5176d81f87...16ebe778df)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 22:10:01 +00:00
b81ae5d01a build(deps): bump rust in /images in the dep-updates group (#321)
Bumps the dep-updates group in /images with 1 update: rust.


Updates `rust` from `596c7fa` to `1f5aff5`

---
updated-dependencies:
- dependency-name: rust
  dependency-type: direct:production
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 22:08:50 +00:00
1756bc6647 build(deps): bump the dep-updates group across 1 directory with 3 updates (#323)
Bumps the dep-updates group with 3 updates in the / directory: [serde_json](https://github.com/serde-rs/json), [clap](https://github.com/clap-rs/clap) and [serde](https://github.com/serde-rs/serde).


Updates `serde_json` from 1.0.122 to 1.0.124
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.122...v1.0.124)

Updates `clap` from 4.5.13 to 4.5.15
- [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.13...v4.5.15)

Updates `serde` from 1.0.205 to 1.0.206
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.205...v1.0.206)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 22:08:32 +00:00
6bf3741ec9 chore(o11y): add more debug logs to daemon & runtime (#318)
This change adds debug log lines to make it easier to tell where issues
are occuring during startup.
2024-08-08 19:11:51 +00:00
b7d41ee9f4 build(deps): bump serde from 1.0.204 to 1.0.205 in the dep-updates group (#315)
Bumps the dep-updates group with 1 update: [serde](https://github.com/serde-rs/serde).


Updates `serde` from 1.0.204 to 1.0.205
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.204...v1.0.205)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-08 05:52:22 +00:00
53059e8cca fix(xenbus): avoid fd close race by forgetting copy of handle (#314)
This change addresses a race condition where the read-copy of a raw FD
is closed while the write-copy is still in use. Now the read-copy is not
closed, by utilizing mem::forget.

Co-authored-by: Alex Zenla <alex@edera.dev>
2024-08-07 23:55:04 +00:00
11bb99b1e4 build(deps): bump actions/upload-artifact in the dep-updates group (#312)
Bumps the dep-updates group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/upload-artifact` from 4.3.5 to 4.3.6
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](89ef406dd8...834a144ee9)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 19:25:49 +00:00
eaa84089ce build(deps): bump hyper-util in the dep-updates group (#311)
Bumps the dep-updates group with 1 update: [hyper-util](https://github.com/hyperium/hyper-util).


Updates `hyper-util` from 0.1.6 to 0.1.7
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.6...v0.1.7)

---
updated-dependencies:
- dependency-name: hyper-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 05:17:50 +00:00
680244fc5e build(deps): bump step-security/harden-runner in the dep-updates group (#308)
Bumps the dep-updates group with 1 update: [step-security/harden-runner](https://github.com/step-security/harden-runner).


Updates `step-security/harden-runner` from 2.9.0 to 2.9.1
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](0d381219dd...5c7944e73c)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 16:56:49 +00:00
d469da4d9b chore: release (#303)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-06 01:57:25 +00:00
99091df3cf fix(zone): waitpid should be limited when no child processes exist (fixes #304) (#305) 2024-08-05 18:48:30 -07:00
08b30c2eaa chore: release (#279)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-08-06 00:48:44 +00:00
224fdbe227 fix(idm): process all idm messages in the same frame and use childwait exit notification for exec (fixes #290) (#302) 2024-08-06 00:29:09 +00:00
62569f6c59 build(deps): bump the dep-updates group across 1 directory with 4 updates (#300)
Bumps the dep-updates group with 4 updates in the / directory: [bytes](https://github.com/tokio-rs/bytes), [flate2](https://github.com/rust-lang/flate2-rs), [regex](https://github.com/rust-lang/regex) and [serde_json](https://github.com/serde-rs/json).


Updates `bytes` from 1.7.0 to 1.7.1
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.0...v1.7.1)

Updates `flate2` from 1.0.30 to 1.0.31
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/commits)

Updates `regex` from 1.10.5 to 1.10.6
- [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.5...1.10.6)

Updates `serde_json` from 1.0.121 to 1.0.122
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.121...v1.0.122)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 00:14:17 +00:00
0b991f454e build(deps): bump the dep-updates group with 2 updates (#301)
Bumps the dep-updates group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `actions/upload-artifact` from 4.3.4 to 4.3.5
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](0b2256b8c0...89ef406dd8)

Updates `MarcoIeni/release-plz-action` from 0.5.62 to 0.5.64
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](86afd21a7b...92ae919a6b)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 00:14:12 +00:00
75aba8a1e3 build(deps): bump the dep-updates group with 4 updates (#296)
Bumps the dep-updates group with 4 updates: [bytes](https://github.com/tokio-rs/bytes), [indexmap](https://github.com/indexmap-rs/indexmap), [toml](https://github.com/toml-rs/toml) and [clap](https://github.com/clap-rs/clap).


Updates `bytes` from 1.6.1 to 1.7.0
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.6.1...v1.7.0)

Updates `indexmap` from 2.2.6 to 2.3.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.2.6...2.3.0)

Updates `toml` from 0.8.17 to 0.8.19
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.17...toml-v0.8.19)

Updates `clap` from 4.5.11 to 4.5.13
- [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.11...v4.5.13)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-01 06:29:02 +00:00
8216ab3602 feature(oci): use local index as resolution cache when appropriate, fixes #289 (#294) 2024-07-31 23:05:15 +00:00
902fffe207 build(deps): bump docker/setup-buildx-action in the dep-updates group (#291)
Bumps the dep-updates group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action).


Updates `docker/setup-buildx-action` from 3.5.0 to 3.6.1
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](aa33708b10...988b5a0280)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-31 21:16:37 +00:00
45cfc6bb27 build(deps): bump toml from 0.8.16 to 0.8.17 in the dep-updates group (#292)
Bumps the dep-updates group with 1 update: [toml](https://github.com/toml-rs/toml).


Updates `toml` from 0.8.16 to 0.8.17
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.16...toml-v0.8.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-31 21:16:24 +00:00
146bda0810 build(deps): bump rust in /images in the dep-updates group (#285)
Bumps the dep-updates group in /images with 1 update: rust.


Updates `rust` from 1.79-alpine to 1.80-alpine

---
updated-dependencies:
- dependency-name: rust
  dependency-type: direct:production
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-29 05:34:37 +00:00
45e7d7515b build(deps): bump the dep-updates group across 1 directory with 6 updates (#288)
Bumps the dep-updates group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [env_logger](https://github.com/rust-cli/env_logger) | `0.11.4` | `0.11.5` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.120` | `1.0.121` |
| [termtree](https://github.com/rust-cli/termtree) | `0.5.0` | `0.5.1` |
| [toml](https://github.com/toml-rs/toml) | `0.8.15` | `0.8.16` |
| [clap](https://github.com/clap-rs/clap) | `4.5.10` | `4.5.11` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.39.1` | `1.39.2` |



Updates `env_logger` from 0.11.4 to 0.11.5
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.4...v0.11.5)

Updates `serde_json` from 1.0.120 to 1.0.121
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.120...v1.0.121)

Updates `termtree` from 0.5.0 to 0.5.1
- [Changelog](https://github.com/rust-cli/termtree/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/termtree/compare/v0.5.0...v0.5.1)

Updates `toml` from 0.8.15 to 0.8.16
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.15...toml-v0.8.16)

Updates `clap` from 4.5.10 to 4.5.11
- [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.10...clap_complete-v4.5.11)

Updates `tokio` from 1.39.1 to 1.39.2
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.39.1...tokio-1.39.2)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: termtree
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-29 05:34:16 +00:00
f161b5afd6 build(deps): bump rust in /images in the dep-updates group (#281)
Bumps the dep-updates group in /images with 1 update: rust.


Updates `rust` from `a454f49` to `71c9d7a`

---
updated-dependencies:
- dependency-name: rust
  dependency-type: direct:production
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-25 09:33:07 +00:00
7fe3e2c7cb build(deps): bump the dep-updates group with 3 updates (#282)
Bumps the dep-updates group with 3 updates: [env_logger](https://github.com/rust-cli/env_logger), [clap](https://github.com/clap-rs/clap) and [tokio](https://github.com/tokio-rs/tokio).


Updates `env_logger` from 0.11.3 to 0.11.4
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.3...v0.11.4)

Updates `clap` from 4.5.9 to 4.5.10
- [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.9...v4.5.10)

Updates `tokio` from 1.38.1 to 1.39.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.1...tokio-1.39.1)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-25 09:32:54 +00:00
3a5be71db4 build(deps): bump the dep-updates group with 3 updates (#280)
Bumps the dep-updates group with 3 updates: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/login-action](https://github.com/docker/login-action) and [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `docker/setup-buildx-action` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](4fd812986e...aa33708b10)

Updates `docker/login-action` from 3.2.0 to 3.3.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](0d4c9c5ea7...9780b0c442)

Updates `docker/build-push-action` from 6.4.1 to 6.5.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](1ca370b3a9...5176d81f87)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-25 09:32:41 +00:00
d1b910f5c4 fix(workflows): upgrade rustup on darwin as best-effort fix for homebrew regression (#284) 2024-07-25 02:15:03 -07:00
8806a79161 zone: init: mount /proc with hidepid=1 (#277)
Mounting procfs with hidepid=1 denies access to procfs directories
for processes not accessible by the current user credentials.

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-07-22 06:11:36 +00:00
c8795fa08d build(deps): bump the dep-updates group with 2 updates (#278)
Bumps the dep-updates group with 2 updates: [async-compression](https://github.com/Nullus157/async-compression) and [oci-spec](https://github.com/containers/oci-spec-rs).


Updates `async-compression` from 0.4.11 to 0.4.12
- [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.11...v0.4.12)

Updates `oci-spec` from 0.6.7 to 0.6.8
- [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.7...v0.6.8)

---
updated-dependencies:
- dependency-name: async-compression
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: oci-spec
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-22 06:07:29 +00:00
d792eb5439 fix(workflows): tag latest version during release-assets workflow (#275) 2024-07-20 00:55:09 +00:00
398e555bd3 chore: release (#249)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-07-19 06:34:46 +00:00
75901233b1 feature(kratactl): rework cli to use subcommands (#268) 2024-07-19 06:13:29 +00:00
04665ce690 build(deps): bump step-security/harden-runner in the dep-updates group (#269)
Bumps the dep-updates group with 1 update: [step-security/harden-runner](https://github.com/step-security/harden-runner).


Updates `step-security/harden-runner` from 2.8.1 to 2.9.0
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](17d0e2bd7d...0d381219dd)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-19 05:38:46 +00:00
481a5884d9 fix(workflows): use full platform name in all names (#267) 2024-07-19 04:46:21 +00:00
5ee1035896 feature(krata): rename guest to zone (#266) 2024-07-19 03:47:18 +00:00
9bd8d1bb1d chore(workflows): make builds faster by only installing necessary tools (#265) 2024-07-19 02:26:26 +00:00
3bada811b2 build(deps): bump docker/build-push-action in the dep-updates group (#262)
Bumps the dep-updates group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `docker/build-push-action` from 6.4.0 to 6.4.1
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](a254f8ca60...1ca370b3a9)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-18 22:22:35 +00:00
e08d25ebde fix(root): remove empty file (#264) 2024-07-18 22:06:00 +00:00
2c884a6882 fix(workflows): give id-token write permission to nightly and release-assets oci (#263) 2024-07-18 21:47:35 +00:00
d756fa82f4 build(deps): bump the dep-updates group across 1 directory with 5 updates (#261)
Bumps the dep-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.62` | `1.0.63` |
| [toml](https://github.com/toml-rs/toml) | `0.8.14` | `0.8.15` |
| [tonic-build](https://github.com/hyperium/tonic) | `0.12.0` | `0.12.1` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.38.0` | `1.38.1` |
| [tonic](https://github.com/hyperium/tonic) | `0.12.0` | `0.12.1` |



Updates `thiserror` from 1.0.62 to 1.0.63
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.62...1.0.63)

Updates `toml` from 0.8.14 to 0.8.15
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.14...toml-v0.8.15)

Updates `tonic-build` from 0.12.0 to 0.12.1
- [Release notes](https://github.com/hyperium/tonic/releases)
- [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/tonic/compare/v0.12.0...v0.12.1)

Updates `tokio` from 1.38.0 to 1.38.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.0...tokio-1.38.1)

Updates `tonic` from 0.12.0 to 0.12.1
- [Release notes](https://github.com/hyperium/tonic/releases)
- [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/tonic/compare/v0.12.0...v0.12.1)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tonic-build
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tonic
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-18 04:06:07 +00:00
6e051f52b9 chore(workflows): rework and simplify github actions workflows (#260) 2024-07-18 03:48:54 +00:00
b2fba6400e chore(dependabot): look for dockerfiles in images subdirectory (#259) 2024-07-17 02:44:18 +00:00
b26469be28 chore(workflows): use rustup directly to not depend on external actions (#258) 2024-07-17 02:39:16 +00:00
28d63d7d70 chore(cleanup): remove legacy OS technology demo (#256) 2024-07-17 02:02:47 +00:00
6b91f0be94 chore(dependabot): rename version groups to be more concise (#255) 2024-07-17 01:54:21 +00:00
9e91ffe065 chore(security): pin docker images and improve actions permissions (#253) 2024-07-16 22:25:29 +00:00
b57d95c610 chore(deps): upgrade dependencies, fix hyper io traits issue (#252) 2024-07-16 21:15:07 +00:00
de6bfe38fe build(deps): bump docker/build-push-action (#251)
Bumps the production-version-updates group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action).


Updates `docker/build-push-action` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](1a162644f9...a254f8ca60)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 12:17:01 +00:00
f6dffd6e17 build(deps): bump bytes in the production-version-updates group (#250)
Bumps the production-version-updates group with 1 update: [bytes](https://github.com/tokio-rs/bytes).


Updates `bytes` from 1.6.0 to 1.6.1
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-version-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 15:23:30 +00:00
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
95fbc62486 chore: release (#87)
Signed-off-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-04-23 09:41:56 +00:00
284ed8f17b feat: implement guest exec (#107) 2024-04-22 20:13:43 +00:00
82576df7b7 feat: implement kernel / initrd oci image support (#103)
* feat: implement kernel / initrd oci image support

* fix: implement image urls more faithfully
2024-04-22 19:48:45 +00:00
1b90eedbcd build(deps): bump thiserror from 1.0.58 to 1.0.59 (#104)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.58 to 1.0.59.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.59)

---
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-04-22 05:36:20 +00:00
aa941c6e87 build(deps): bump redb from 2.0.0 to 2.1.0 (#106)
Bumps [redb](https://github.com/cberner/redb) from 2.0.0 to 2.1.0.
- [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.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: redb
  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-04-22 05:36:13 +00:00
d0bf3c4c77 build(deps): bump reqwest from 0.12.3 to 0.12.4 (#105)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.3 to 0.12.4.
- [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.3...v0.12.4)

---
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-04-22 05:36:06 +00:00
38e892e249 feat: idm v2 (#102)
* feat: rebuild idm to separate transport from content

* feat: fast guest lookup table and host identification
2024-04-22 04:00:32 +00:00
1a90372037 build(deps): bump sysinfo from 0.30.10 to 0.30.11 (#99)
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.30.10 to 0.30.11.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.30.10...v0.30.11)

---
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-04-19 20:35:57 +00:00
4754cdd128 build(deps): bump rustls from 0.22.3 to 0.22.4 (#101)
Bumps [rustls](https://github.com/rustls/rustls) from 0.22.3 to 0.22.4.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.22.3...v/0.22.4)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 20:35:44 +00:00
f843abcabf docs(dev): update order of setup instructions (#98)
This change corrects the steps order to have the krata daemon started
before starting the krata networking service

Co-authored-by: Khionu Sybiern <khionu@edera.dev>
2024-04-18 18:00:48 +00:00
e8d89d4d5b build(deps): bump serde from 1.0.197 to 1.0.198 (#97)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.197 to 1.0.198.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.197...v1.0.198)

---
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-17 05:17:00 +00:00
4e9738b959 fix: oci cache store should fallback to copy when rename won't work (#96) 2024-04-16 17:05:24 +00:00
8135307283 feat: oci concurrency improvements (#95)
* feat: implement improved and detailed oci progress indication

* feat: implement on-disk indexes of images

* oci: utilize rw-lock for increased cache performance
2024-04-16 16:29:54 +00:00
e450ebd2a2 feat: oci tar format, bit-perfect disk storage for config and manifest, concurrent image pulls (#88)
* oci: retain bit-perfect copies of manifest and config on disk

* feat: oci tar format support

* feat: concurrent image pulls
2024-04-16 08:53:44 +00:00
79f7742caa build(deps): bump serde_json from 1.0.115 to 1.0.116 (#90)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.115 to 1.0.116.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.115...v1.0.116)

---
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-04-16 07:17:11 +00:00
c3c18271b4 build(deps): bump ratatui from 0.26.1 to 0.26.2 (#89)
Bumps [ratatui](https://github.com/ratatui-org/ratatui) from 0.26.1 to 0.26.2.
- [Release notes](https://github.com/ratatui-org/ratatui/releases)
- [Changelog](https://github.com/ratatui-org/ratatui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ratatui-org/ratatui/compare/v0.26.1...v0.26.2)

---
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-04-16 07:16:53 +00:00
218f848170 chore: release (#41)
Signed-off-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-04-15 19:15:00 +00:00
9d8c516a29 build(deps): bump sysinfo from 0.30.9 to 0.30.10 (#86)
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.30.9 to 0.30.10.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/v0.30.10/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.30.9...v0.30.10)

---
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-04-15 17:24:41 +00:00
89055ef77c feat: oci compliance work (#85)
* chore: rework oci crate to be more composable

* feat: image pull is now internally explicit

* feat: utilize vfs for assembling oci images

* feat: rework oci to preserve permissions via a vfs
2024-04-15 17:24:14 +00:00
24c71e9725 feat: oci packer can now use mksquashfs if available (#70)
* feat: oci packer can now use mksquashfs if available

* fix: use nproc in kernel build script for default jobs, and fix DEV.md guide

* feat: working erofs backend
2024-04-15 00:19:38 +00:00
0a6a112133 feat: basic kratactl top command (#72)
* feat: basic kratactl top command

* fix: use magic bytes 0xff 0xff in idm to improve reliability
2024-04-14 22:32:34 +00:00
1627cbcdd7 feat: idm snooping (#71)
Implement IDM snooping, a new feature that lets you snoop on messages between guests and the host. The feature exposes the IDM packets send and receives
to the API, allowing kratactl to now listen for messages and feed them to a user for debugging purposes.
2024-04-14 11:54:21 +00:00
f8247f13e4 build: use LTO for release builds and strip guestinit (#68)
* initrd: strip guestinit binary before adding it to initramfs

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

* build: use LTO for release profile artifacts

this allows us to save ~25-30% on binary sizes, at least in guestinit

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

* revert strip command usage, breaks arm

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

* build: use strip=symbols

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

---------

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-04-13 09:20:24 +00:00
6d07112e3d feat: implement oci image progress (#64)
* feat: oci progress events

* feat: oci progress bars on launch
2024-04-12 18:09:26 +00:00
6cef03bffa debug: common: run programs in a way that is compatible with alpine doas-sudo-shim (#53)
doas sudo shim (as used by Alpine) does not support passing through environment variables
in the same way that sudo does, therefore use `sh -c` instead.

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-04-12 09:20:34 +00:00
73fd95dbe2 guest: init: default to xterm if TERM is not set (#52)
Most terminal emulators support the xterm control codes more faithfully than the
vt100 ones.

Fixes #51.

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-04-12 08:52:18 +00:00
f41a1e2168 build: target: use alpine rust triplets when building on alpine (#49)
Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
2024-04-12 08:40:44 +00:00
346cf4a7fa build(deps): bump async-trait from 0.1.79 to 0.1.80 (#48)
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.79 to 0.1.80.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.79...0.1.80)

---
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-04-12 07:35:12 +00:00
5e16f3149f feat: guest metrics support (#46)
* feat: initial support for idm send in daemon

* feat: implement IdmClient backend support

* feat: daemon idm now uses IdmClient

* fix: implement channel destruction propagation

* feat: implement request response idm system

* feat: implement metrics support

* proto: move metrics into GuestMetrics for reusability

* fix: log level of guest agent was trace

* feat: metrics tree with process information
2024-04-12 07:34:46 +00:00
ec9060d872 build(deps): bump anyhow from 1.0.81 to 1.0.82 (#42)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.81 to 1.0.82.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.81...1.0.82)

---
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-04-10 06:19:10 +00:00
6050e99aa7 chore: release (#39)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-04-09 11:47:58 +00:00
7cfdb27d23 chore(workflows): fix release asset assembly for alpine/debian packages (#40) 2024-04-09 04:45:36 -07:00
194 changed files with 5981 additions and 27511 deletions

View File

@ -1,2 +0,0 @@
/target
/kernel/linux-*

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,21 @@ updates:
directory: "/"
schedule:
interval: "daily"
groups:
actions-updates:
dependency-type: "production"
applies-to: "version-updates"
actions-dev-updates:
dependency-type: "development"
applies-to: "version-updates"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
groups:
dep-updates:
dependency-type: "production"
applies-to: "version-updates"
dev-updates:
dependency-type: "development"
applies-to: "version-updates"

View File

@ -6,24 +6,126 @@ on:
merge_group:
branches:
- main
permissions:
contents: read
jobs:
fmt:
name: fmt
rustfmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: harden runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/build/cargo.sh fmt --all -- --check
- name: install stable rust toolchain with rustfmt
run: |
rustup update --no-self-update stable
rustup default stable
rustup component add rustfmt
- name: install linux dependencies
run: ./hack/ci/install-linux-deps.sh
- name: cargo fmt
run: ./hack/build/cargo.sh fmt --all -- --check
shellcheck:
name: shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: harden runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- run: ./hack/code/shellcheck.sh
- name: shellcheck
run: ./hack/code/shellcheck.sh
full-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: full build linux-${{ matrix.arch }}
steps:
- name: harden runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain
run: |
rustup update --no-self-update stable
rustup default stable
- name: install linux dependencies
run: ./hack/ci/install-linux-deps.sh
- name: cargo build
run: ./hack/build/cargo.sh build
full-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: full test linux-${{ matrix.arch }}
steps:
- name: harden runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain
run: |
rustup update --no-self-update stable
rustup default stable
- name: install linux dependencies
run: ./hack/ci/install-linux-deps.sh
- name: cargo test
run: ./hack/build/cargo.sh test
full-clippy:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: full clippy linux-${{ matrix.arch }}
steps:
- name: harden runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain with clippy
run: |
rustup update --no-self-update stable
rustup default stable
rustup component add clippy
- name: install linux dependencies
run: ./hack/ci/install-linux-deps.sh
- name: cargo clippy
run: ./hack/build/cargo.sh clippy

View File

@ -1,44 +0,0 @@
name: client
on:
pull_request:
branches:
- main
merge_group:
branches:
- main
jobs:
build:
strategy:
fail-fast: false
matrix:
platform:
- { os: linux, arch: x86_64, on: ubuntu-latest, deps: linux }
- { os: linux, arch: aarch64, on: ubuntu-latest, deps: linux }
- { os: darwin, arch: x86_64, on: macos-14, deps: darwin }
- { os: darwin, arch: aarch64, on: macos-14, deps: darwin }
- { os: freebsd, arch: x86_64, on: ubuntu-latest, deps: linux }
- { os: windows, arch: x86_64, on: windows-latest, deps: windows }
env:
TARGET_OS: "${{ matrix.platform.os }}"
TARGET_ARCH: "${{ matrix.platform.arch }}"
runs-on: "${{ matrix.platform.on }}"
name: client build ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
defaults:
run:
shell: bash
steps:
- run: git config --global core.autocrlf false && git config --global core.eol lf
if: ${{ matrix.platform.os == 'windows' }}
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@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
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

@ -1,102 +0,0 @@
name: nightly
on:
workflow_dispatch:
schedule:
- cron: "0 10 * * *"
jobs:
server:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: nightly server ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@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
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
with:
name: krata-debian-${{ matrix.arch }}
path: "target/dist/*.deb"
compression-level: 0
- run: ./hack/dist/apk.sh
env:
KRATA_KERNEL_BUILD_SKIP: "1"
- uses: actions/upload-artifact@v4
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
with:
name: krata-os-${{ matrix.arch }}
path: "target/os/krata-${{ matrix.arch }}.qcow2"
compression-level: 0
client:
strategy:
fail-fast: false
matrix:
platform:
- { os: linux, arch: x86_64, on: ubuntu-latest, deps: linux }
- { os: linux, arch: aarch64, on: ubuntu-latest, deps: linux }
- { os: darwin, arch: x86_64, on: macos-14, deps: darwin }
- { os: darwin, arch: aarch64, on: macos-14, deps: darwin }
- { os: freebsd, arch: x86_64, on: ubuntu-latest, deps: linux }
- { os: windows, arch: x86_64, on: windows-latest, deps: windows }
env:
TARGET_OS: "${{ matrix.platform.os }}"
TARGET_ARCH: "${{ matrix.platform.arch }}"
runs-on: "${{ matrix.platform.on }}"
name: nightly client ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
defaults:
run:
shell: bash
steps:
- run: git config --global core.autocrlf false && git config --global core.eol lf
if: ${{ matrix.platform.os == 'windows' }}
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@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
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
with:
name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }}
path: "target/*/release/kratactl"
if: ${{ matrix.platform.os != 'windows' }}
- uses: actions/upload-artifact@v4
with:
name: kratactl-${{ matrix.platform.os }}-${{ matrix.platform.arch }}
path: "target/*/release/kratactl.exe"
if: ${{ matrix.platform.os == 'windows' }}

View File

@ -1,40 +0,0 @@
name: os
on:
pull_request:
branches:
- main
paths:
- "os/**"
- "hack/os/**"
- "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: os build ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@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
with:
name: krata-os-${{ matrix.arch }}
path: "target/os/krata-${{ matrix.arch }}.qcow2"
compression-level: 0

View File

@ -1,94 +0,0 @@
name: release-binaries
permissions:
contents: write
on:
release:
types:
- published
env:
CARGO_INCREMENTAL: 0
CARGO_NET_GIT_FETCH_WITH_CLI: true
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUSTUP_MAX_RETRIES: 10
jobs:
server:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: release-binaries server ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@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:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
client:
strategy:
fail-fast: false
matrix:
platform:
- { os: linux, arch: x86_64, on: ubuntu-latest, deps: linux }
- { os: linux, arch: aarch64, on: ubuntu-latest, deps: linux }
- { os: darwin, arch: x86_64, on: macos-14, deps: darwin }
- { os: darwin, arch: aarch64, on: macos-14, deps: darwin }
- { os: freebsd, arch: x86_64, on: ubuntu-latest, deps: linux }
- { os: windows, arch: x86_64, on: windows-latest, deps: windows }
env:
TARGET_OS: "${{ matrix.platform.os }}"
TARGET_ARCH: "${{ matrix.platform.arch }}"
runs-on: "${{ matrix.platform.on }}"
name: release-binaries client ${{ matrix.platform.os }}-${{ matrix.platform.arch }}
defaults:
run:
shell: bash
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@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
if: ${{ matrix.platform.os == 'darwin' }}
- run: ./hack/ci/install-${{ matrix.platform.deps }}-deps.sh
- run: ./hack/build/cargo.sh build --release --bin kratactl
- run: "./hack/ci/assemble-release-assets.sh kratactl ${{ github.event.release.tag_name }} ${{ matrix.platform.os }}-${{ matrix.platform.arch }} target/*/release/kratactl"
if: ${{ matrix.platform.os != 'windows' }}
- run: "./hack/ci/assemble-release-assets.sh kratactl ${{ github.event.release.tag_name }} ${{ matrix.platform.os }}-${{ matrix.platform.arch }} target/*/release/kratactl.exe"
if: ${{ matrix.platform.os == 'windows' }}
- run: "./hack/ci/upload-release-assets.sh ${{ github.event.release.tag_name }}"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@ -1,7 +1,4 @@
name: release-plz
permissions:
pull-requests: write
contents: write
on:
push:
branches:
@ -9,25 +6,41 @@ on:
concurrency:
group: "${{ github.workflow }}"
cancel-in-progress: true
permissions:
contents: read
jobs:
release-plz:
name: release-plz
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
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 }}"
- name: harden runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: generate cultivator token
uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1.11.6
id: generate-token
with:
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
- name: checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
fetch-depth: 0
token: "${{ steps.generate-token.outputs.token }}"
- name: install stable rust toolchain
run: |
rustup update --no-self-update stable
rustup default stable
- name: install linux dependencies
run: ./hack/ci/install-linux-deps.sh
- name: release-plz
uses: MarcoIeni/release-plz-action@476794ede164c5137bfc3a1dc6ed3675275690f9 # v0.5.99
env:
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
CARGO_REGISTRY_TOKEN: "${{ secrets.KRATA_RELEASE_CARGO_TOKEN }}"

View File

@ -1,82 +0,0 @@
name: server
on:
pull_request:
branches:
- main
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: server 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/build/cargo.sh build
test:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: server test ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/build/cargo.sh test
clippy:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: server clippy ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/build/cargo.sh clippy
initrd:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- x86_64
- aarch64
env:
TARGET_ARCH: "${{ matrix.arch }}"
name: server initrd ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
targets: "${{ matrix.arch }}-unknown-linux-gnu,${{ matrix.arch }}-unknown-linux-musl"
- run: ./hack/ci/install-linux-deps.sh
- run: ./hack/initrd/build.sh

2
.gitignore vendored
View File

@ -1 +1,3 @@
/target
/.idea
/.vscode

3
.gitmodules vendored
View File

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

View File

@ -1,4 +1,5 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@ -6,40 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.0.7](https://github.com/edera-dev/krata/compare/v0.0.6...v0.0.7) - 2024-04-09
### Other
- update Cargo.toml dependencies
- update Cargo.lock dependencies
## [0.0.6](https://github.com/edera-dev/krata/compare/v0.0.5...v0.0.6) - 2024-04-09
### Fixed
- increase channel acquisition timeout to support lower performance hosts ([#36](https://github.com/edera-dev/krata/pull/36))
### Other
- update Cargo.toml dependencies
- update Cargo.lock dependencies
## [0.0.5](https://github.com/edera-dev/krata/compare/v0.0.4...v0.0.5) - 2024-04-09
## [0.0.24](https://github.com/edera-dev/krata/compare/v0.0.23...v0.0.24) - 2024-12-14
### Added
- *(ctl)* add help and about to commands and arguments ([#25](https://github.com/edera-dev/krata/pull/25))
- *(xen)* update xenclient and xenplatform to the latest structure (#433)
- *(xencall)* improve asynchronous support (#430)
- *(evtchn)* harden evtchn handling and improve api (#431)
- *(xenstore)* multi-watch and maybe-commit support (#429)
### Fixed
- *(xenclient)* examples should use supported platform
- *(xenclient)* boot example should use unsupported platform on aarch64
- *(xenplatform)* e820 sanitize should now produce valid mappings
- *(xenplatform)* use cfg attributes for returning supported platforms
### Other
- *(deps)* upgrade dependencies and clean code (#432)
- update Cargo.toml dependencies
- update Cargo.lock dependencies
## [0.0.4](https://github.com/edera-dev/krata/releases/tag/v${version}) - 2024-04-03
## [0.0.23](https://github.com/edera-dev/krata/compare/v0.0.22...v0.0.23) - 2024-09-17
### Other
- implement automatic releases
- reimplement console to utilize channels, and provide logs support
- set hostname from launch config
- implement event stream retries
- work on parallel reconciliation
- implement parallel guest reconciliation
- log when a guest start failures occurs
- remove device restriction
- setup loopback interface
- place running tasks in cgroup
- update Cargo.toml dependencies
## [0.0.22](https://github.com/edera-dev/krata/compare/v0.0.21...v0.0.22) - 2024-09-16
### Other
- update Cargo.toml dependencies
- preparations for xen control-plane

33
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,33 @@
# 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
[good-first-issues]: https://github.com/edera-dev/krata/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22

2967
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,111 +1,45 @@
[workspace]
members = [
"crates/krata",
"crates/oci",
"crates/guest",
"crates/runtime",
"crates/daemon",
"crates/network",
"crates/ctl",
"crates/xen/xencall",
"crates/xen/xenclient",
"crates/xen/xenevtchn",
"crates/xen/xengnt",
"crates/xen/xenplatform",
"crates/xen/xenstore",
]
resolver = "2"
[workspace.package]
version = "0.0.7"
version = "0.0.24"
homepage = "https://krata.dev"
license = "Apache-2.0"
license = "GPL-2.0-or-later"
repository = "https://github.com/edera-dev/krata"
[workspace.dependencies]
anyhow = "1.0"
arrayvec = "0.7.4"
async-compression = "0.4.8"
async-stream = "0.3.5"
async-trait = "0.1.77"
backhand = "0.15.0"
async-trait = "0.1.85"
bit-vec = "0.8.0"
byteorder = "1"
bytes = "1.5.0"
cgroups-rs = "0.3.4"
circular-buffer = "0.1.7"
comfy-table = "7.1.1"
crossterm = "0.27.0"
ctrlc = "3.4.4"
elf = "0.7.4"
env_logger = "0.11.0"
etherparse = "0.14.3"
env_logger = "0.11.6"
flate2 = "1.0"
futures = "0.3.30"
ipnetwork = "0.20.0"
indexmap = "2.6.0"
libc = "0.2"
log = "0.4.20"
loopdev-3 = "0.5.1"
krata-advmac = "1.1.0"
krata-tokio-tar = "0.4.0"
log = "0.4.22"
memchr = "2"
nix = "0.28.0"
oci-spec = "0.6.4"
once_cell = "1.19.0"
path-absolutize = "3.1.1"
path-clean = "1.0.1"
prost = "0.12.4"
prost-build = "0.12.4"
prost-reflect-build = "0.13.0"
rand = "0.8.5"
redb = "2.0.0"
rtnetlink = "0.14.1"
serde_json = "1.0.113"
serde_yaml = "0.9"
sha256 = "1.5.0"
signal-hook = "0.3.17"
nix = "0.29.0"
regex = "1.11.1"
slice-copy = "0.3.0"
smoltcp = "0.11.0"
thiserror = "1.0"
tokio-tun = "0.11.4"
tonic-build = "0.11.0"
tower = "0.4.13"
udp-stream = "0.0.11"
url = "2.5.0"
walkdir = "2"
thiserror = "2.0.9"
xz2 = "0.1"
[workspace.dependencies.clap]
version = "4.4.18"
features = ["derive"]
[workspace.dependencies.prost-reflect]
version = "0.13.1"
features = ["derive"]
[workspace.dependencies.reqwest]
version = "0.12.3"
default-features = false
features = ["rustls-tls"]
[workspace.dependencies.serde]
version = "1.0.196"
features = ["derive"]
[workspace.dependencies.sys-mount]
version = "3.0.0"
default-features = false
[workspace.dependencies.tokio]
version = "1.35.1"
version = "1.41.1"
features = ["full"]
[workspace.dependencies.tokio-stream]
version = "0.1"
features = ["io-util", "net"]
[workspace.dependencies.tonic]
version = "0.11.0"
features = ["tls"]
[workspace.dependencies.uuid]
version = "1.6.1"
version = "1.11.0"
features = ["v4"]
[profile.release]
lto = "fat"
strip = "symbols"

View File

@ -1,34 +0,0 @@
[target.aarch64-unknown-linux-gnu]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]
[target.aarch64-unknown-linux-musl]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]
[target.x86_64-unknown-linux-gnu]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]
[target.x86_64-unknown-linux-musl]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]
[target.riscv64gc-unknown-linux-gnu]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]
[target.x86_64-unknown-freebsd]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]
[target.x86_64-unknown-netbsd]
pre-build = [
"apt-get update && apt-get --assume-yes install protobuf-compiler"
]

79
DEV.md
View File

@ -1,79 +0,0 @@
# Development Guide
## Structure
krata is composed of four major executables:
| Executable | Runs On | User Interaction | Dev Runner | Code Path |
| ---------- | ------- | ---------------- | ------------------------ | ----------------- |
| kratad | host | backend daemon | ./hack/debug/kratad.sh | crates/daemon |
| kratanet | host | backend daemon | ./hack/debug/kratanet.sh | crates/network |
| kratactl | host | CLI tool | ./hack/debug/kratactl.sh | crates/ctl |
| krataguest | guest | none, guest init | N/A | crates/guest |
You will find the code to each executable available in the bin/ and src/ directories inside
it's corresponding code path from the above table.
## Environment
| 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 |
| 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 |
## Setup Guide
1. Install the specified Debian version on a x86_64 host _capable_ of KVM (NOTE: KVM is not used, Xen is a type-1 hypervisor).
2. Install required packages: `apt install git xen-system-amd64 flex bison libelf-dev libssl-dev bc`
3. Install [rustup](https://rustup.rs) for managing a Rust environment.
4. Configure `/etc/default/grub.d/xen.cfg` to give krata guests some room:
```sh
# Configure dom0_mem to be 4GB, but leave the rest of the RAM for krata guests.
GRUB_CMDLINE_XEN_DEFAULT="dom0_mem=4G,max:4G"
```
After changing the grub config, update grub: `update-grub`
Then reboot to boot the system as a Xen dom0.
You can validate that Xen is setup by running `xl info` and ensuring it returns useful information about the Xen hypervisor.
5. Clone the krata source code:
```sh
$ git clone https://github.com/edera-dev/krata.git krata
$ cd krata
```
6. Build a guest kernel image:
```sh
$ ./hack/kernel/build.sh
```
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/kratanet.sh` and keep it running in the foreground.
9. Launch `./hack/debug/kratad.sh` and keep it running in the foreground.
10. Run kratactl to launch a guest:
```sh
$ ./hack/debug/kratactl.sh launch --attach alpine:latest
```
To detach from the guest console, use `Ctrl + ]` on your keyboard.
To list the running guests, run:
```sh
$ ./hack/debug/kratactl.sh list
```
To destroy a running guest, copy it's UUID from either the launch command or the guest list and run:
```sh
$ ./hack/debug/kratactl.sh destroy GUEST_UUID
```

14
FAQ.md
View File

@ -1,19 +1,9 @@
# Frequently Asked Questions
## 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 file and packaging basic startup data in a bundle which 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.
## 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.
pvcalls is fascinating, 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.

476
LICENSE
View File

@ -1,201 +1,339 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
1. Definitions.
Preamble
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
The precise terms and conditions for copying, distribution and
modification follow.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
END OF TERMS AND CONDITIONS
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
APPENDIX: How to apply the Apache License to your work.
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
Copyright 2024 Edera Inc.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
http://www.apache.org/licenses/LICENSE-2.0
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -1,28 +1,27 @@
# krata
The Edera Hypervisor
krata is an implementation of a Xen control-plane in Rust.
![license](https://img.shields.io/github/license/edera-dev/krata)
![discord](https://img.shields.io/discord/1207447453083766814?label=discord)
[![check](https://github.com/edera-dev/krata/actions/workflows/check.yml/badge.svg)](https://github.com/edera-dev/krata/actions/workflows/check.yml)
[![nightly](https://github.com/edera-dev/krata/actions/workflows/nightly.yml/badge.svg)](https://github.com/edera-dev/krata/actions/workflows/nightly.yml)
---
- [Frequently Asked Questions](FAQ.md)
- [Development Guide](DEV.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Security Policy](SECURITY.md)
- [Edera Technical Overview](technical-overview.md)
## 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 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 is a component of [Edera Protect](https://edera.dev/protect-kubernetes), for secure-by-design infrastructure.
It provides the base layer upon which Edera Protect zones are built on: a securely booted virtualization guest on the Xen hypervisor.
## 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 |

View File

@ -1,34 +0,0 @@
[package]
name = "krata-ctl"
description = "Command-line tool to control the krata hypervisor"
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
anyhow = { workspace = true }
async-stream = { workspace = true }
clap = { workspace = true }
comfy-table = { workspace = true }
crossterm = { workspace = true }
ctrlc = { workspace = true, features = ["termination"] }
env_logger = { workspace = true }
krata = { path = "../krata", version = "^0.0.7" }
log = { workspace = true }
prost-reflect = { workspace = true, features = ["serde"] }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tonic = { workspace = true }
tower = { workspace = true }
[lib]
name = "kratactl"
[[bin]]
name = "kratactl"
path = "bin/control.rs"

View File

@ -1,11 +0,0 @@
use anyhow::Result;
use clap::Parser;
use env_logger::Env;
use kratactl::cli::ControlCommand;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
ControlCommand::parse().run().await
}

View File

@ -1,41 +0,0 @@
use anyhow::Result;
use clap::Parser;
use krata::{events::EventStream, v1::control::control_service_client::ControlServiceClient};
use tokio::select;
use tonic::transport::Channel;
use crate::console::StdioConsoleStream;
use super::resolve_guest;
#[derive(Parser)]
#[command(about = "Attach to the guest console")]
pub struct AttachCommand {
#[arg(help = "Guest to attach to, either the name or the uuid")]
guest: String,
}
impl AttachCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let input = StdioConsoleStream::stdin_stream(guest_id.clone()).await;
let output = client.console_data(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
let code = select! {
x = stdout_handle => {
x??;
None
},
x = exit_hook_task => x?
};
StdioConsoleStream::restore_terminal_mode();
std::process::exit(code.unwrap_or(0));
}
}

View File

@ -1,85 +0,0 @@
use anyhow::Result;
use clap::Parser;
use krata::{
events::EventStream,
v1::{
common::GuestStatus,
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
DestroyGuestRequest,
},
},
};
use log::error;
use tonic::{transport::Channel, Request};
use crate::cli::resolve_guest;
#[derive(Parser)]
#[command(about = "Destroy a guest")]
pub struct DestroyCommand {
#[arg(
short = 'W',
long,
help = "Wait for the destruction of the guest to complete"
)]
wait: bool,
#[arg(help = "Guest to destroy, either the name or the uuid")]
guest: String,
}
impl DestroyCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let _ = client
.destroy_guest(Request::new(DestroyGuestRequest {
guest_id: guest_id.clone(),
}))
.await?
.into_inner();
if self.wait {
wait_guest_destroyed(&guest_id, events).await?;
}
Ok(())
}
}
async fn wait_guest_destroyed(id: &str, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
match event {
Event::GuestChanged(changed) => {
let Some(guest) = changed.guest else {
continue;
};
if guest.id != id {
continue;
}
let Some(state) = guest.state else {
continue;
};
if let Some(ref error) = state.error_info {
if state.status() == GuestStatus::Failed {
error!("destroy failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
}
}
if state.status() == GuestStatus::Destroyed {
std::process::exit(0);
}
}
}
}
Ok(())
}

View File

@ -1,174 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use clap::Parser;
use krata::{
events::EventStream,
v1::{
common::{
guest_image_spec::Image, GuestImageSpec, GuestOciImageSpec, GuestSpec, GuestStatus,
GuestTaskSpec, GuestTaskSpecEnvVar,
},
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
CreateGuestRequest,
},
},
};
use log::error;
use tokio::select;
use tonic::{transport::Channel, Request};
use crate::console::StdioConsoleStream;
#[derive(Parser)]
#[command(about = "Launch a new guest")]
pub struct LauchCommand {
#[arg(short, long, help = "Name of the guest")]
name: Option<String>,
#[arg(
short,
long,
default_value_t = 1,
help = "vCPUs available to the guest"
)]
cpus: u32,
#[arg(
short,
long,
default_value_t = 512,
help = "Memory available to the guest, in megabytes"
)]
mem: u64,
#[arg[short, long, help = "Environment variables set in the guest"]]
env: Option<Vec<String>>,
#[arg(
short,
long,
help = "Attach to the guest after guest starts, implies --wait"
)]
attach: bool,
#[arg(
short = 'W',
long,
help = "Wait for the guest to start, implied by --attach"
)]
wait: bool,
#[arg(help = "Container image for guest to use")]
oci: String,
#[arg(
allow_hyphen_values = true,
trailing_var_arg = true,
help = "Command to run inside the guest"
)]
command: Vec<String>,
}
impl LauchCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let request = CreateGuestRequest {
spec: Some(GuestSpec {
name: self.name.unwrap_or_default(),
image: Some(GuestImageSpec {
image: Some(Image::Oci(GuestOciImageSpec { image: self.oci })),
}),
vcpus: self.cpus,
mem: self.mem,
task: Some(GuestTaskSpec {
environment: env_map(&self.env.unwrap_or_default())
.iter()
.map(|(key, value)| GuestTaskSpecEnvVar {
key: key.clone(),
value: value.clone(),
})
.collect(),
command: self.command,
}),
annotations: vec![],
}),
};
let response = client
.create_guest(Request::new(request))
.await?
.into_inner();
let id = response.guest_id;
if self.wait || self.attach {
wait_guest_started(&id, events.clone()).await?;
}
let code = if self.attach {
let input = StdioConsoleStream::stdin_stream(id.clone()).await;
let output = client.console_data(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
let exit_hook_task = StdioConsoleStream::guest_exit_hook(id.clone(), events).await?;
select! {
x = stdout_handle => {
x??;
None
},
x = exit_hook_task => x?
}
} else {
println!("{}", id);
None
};
StdioConsoleStream::restore_terminal_mode();
std::process::exit(code.unwrap_or(0));
}
}
async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
match event {
Event::GuestChanged(changed) => {
let Some(guest) = changed.guest else {
continue;
};
if guest.id != id {
continue;
}
let Some(state) = guest.state else {
continue;
};
if let Some(ref error) = state.error_info {
if state.status() == GuestStatus::Failed {
error!("launch failed: {}", error.message);
std::process::exit(1);
} else {
error!("guest error: {}", error.message);
}
}
if state.status() == GuestStatus::Destroyed {
error!("guest destroyed");
std::process::exit(1);
}
if state.status() == GuestStatus::Started {
break;
}
}
}
}
Ok(())
}
fn env_map(env: &[String]) -> HashMap<String, String> {
let mut map = HashMap::<String, String>::new();
for item in env {
if let Some((key, value)) = item.split_once('=') {
map.insert(key.to_string(), value.to_string());
}
}
map
}

View File

@ -1,174 +0,0 @@
use anyhow::{anyhow, Result};
use clap::{Parser, ValueEnum};
use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
use krata::{
events::EventStream,
v1::{
common::{Guest, GuestStatus},
control::{
control_service_client::ControlServiceClient, ListGuestsRequest, ResolveGuestRequest,
},
},
};
use serde_json::Value;
use tonic::{transport::Channel, Request};
use crate::format::{guest_simple_line, guest_status_text, kv2line, proto2dynamic, proto2kv};
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
enum ListFormat {
Table,
Json,
JsonPretty,
Jsonl,
Yaml,
KeyValue,
Simple,
}
#[derive(Parser)]
#[command(about = "List the guests on the hypervisor")]
pub struct ListCommand {
#[arg(short, long, default_value = "table", help = "Output format")]
format: ListFormat,
#[arg(help = "Limit to a single guest, either the name or the uuid")]
guest: Option<String>,
}
impl ListCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
_events: EventStream,
) -> Result<()> {
let mut guests = if let Some(ref guest) = self.guest {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
name: guest.clone(),
}))
.await?
.into_inner();
if let Some(guest) = reply.guest {
vec![guest]
} else {
return Err(anyhow!("unable to resolve guest '{}'", guest));
}
} else {
client
.list_guests(Request::new(ListGuestsRequest {}))
.await?
.into_inner()
.guests
};
guests.sort_by(|a, b| {
a.spec
.as_ref()
.map(|x| x.name.as_str())
.unwrap_or("")
.cmp(b.spec.as_ref().map(|x| x.name.as_str()).unwrap_or(""))
});
match self.format {
ListFormat::Table => {
self.print_guest_table(guests)?;
}
ListFormat::Simple => {
for guest in guests {
println!("{}", guest_simple_line(&guest));
}
}
ListFormat::Json | ListFormat::JsonPretty | ListFormat::Yaml => {
let mut values = Vec::new();
for guest in guests {
let message = proto2dynamic(guest)?;
values.push(serde_json::to_value(message)?);
}
let value = Value::Array(values);
let encoded = if self.format == ListFormat::JsonPretty {
serde_json::to_string_pretty(&value)?
} else if self.format == ListFormat::Yaml {
serde_yaml::to_string(&value)?
} else {
serde_json::to_string(&value)?
};
println!("{}", encoded.trim());
}
ListFormat::Jsonl => {
for guest in guests {
let message = proto2dynamic(guest)?;
println!("{}", serde_json::to_string(&message)?);
}
}
ListFormat::KeyValue => {
self.print_key_value(guests)?;
}
}
Ok(())
}
fn print_guest_table(&self, guests: Vec<Guest>) -> 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", "uuid", "status", "ipv4", "ipv6"]);
for guest in guests {
let ipv4 = guest
.state
.as_ref()
.and_then(|x| x.network.as_ref())
.map(|x| x.guest_ipv4.as_str())
.unwrap_or("n/a");
let ipv6 = guest
.state
.as_ref()
.and_then(|x| x.network.as_ref())
.map(|x| x.guest_ipv6.as_str())
.unwrap_or("n/a");
let Some(spec) = guest.spec else {
continue;
};
let status = guest.state.as_ref().cloned().unwrap_or_default().status();
let status_text = guest_status_text(status);
let status_color = match status {
GuestStatus::Destroyed | GuestStatus::Failed => Color::Red,
GuestStatus::Destroying | GuestStatus::Exited | GuestStatus::Starting => {
Color::Yellow
}
GuestStatus::Started => Color::Green,
_ => Color::Reset,
};
table.add_row(vec![
Cell::new(spec.name),
Cell::new(guest.id),
Cell::new(status_text).fg(status_color),
Cell::new(ipv4.to_string()),
Cell::new(ipv6.to_string()),
]);
}
if table.is_empty() {
if self.guest.is_none() {
println!("no guests have been launched");
}
} else {
println!("{}", table);
}
Ok(())
}
fn print_key_value(&self, guests: Vec<Guest>) -> Result<()> {
for guest in guests {
let kvs = proto2kv(guest)?;
println!("{}", kv2line(kvs),);
}
Ok(())
}
}

View File

@ -1,58 +0,0 @@
use anyhow::Result;
use async_stream::stream;
use clap::Parser;
use krata::{
events::EventStream,
v1::control::{control_service_client::ControlServiceClient, ConsoleDataRequest},
};
use tokio::select;
use tokio_stream::{pending, StreamExt};
use tonic::transport::Channel;
use crate::console::StdioConsoleStream;
use super::resolve_guest;
#[derive(Parser)]
#[command(about = "View the logs of a guest")]
pub struct LogsCommand {
#[arg(short, long, help = "Follow output from the guest")]
follow: bool,
#[arg(help = "Guest to show logs for, either the name or the uuid")]
guest: String,
}
impl LogsCommand {
pub async fn run(
self,
mut client: ControlServiceClient<Channel>,
events: EventStream,
) -> Result<()> {
let guest_id: String = resolve_guest(&mut client, &self.guest).await?;
let guest_id_stream = guest_id.clone();
let follow = self.follow;
let input = stream! {
yield ConsoleDataRequest { guest_id: guest_id_stream, data: Vec::new() };
if follow {
let mut pending = pending::<ConsoleDataRequest>();
while let Some(x) = pending.next().await {
yield x;
}
}
};
let output = client.console_data(input).await?.into_inner();
let stdout_handle =
tokio::task::spawn(async move { StdioConsoleStream::stdout(output).await });
let exit_hook_task = StdioConsoleStream::guest_exit_hook(guest_id.clone(), events).await?;
let code = select! {
x = stdout_handle => {
x??;
None
},
x = exit_hook_task => x?
};
StdioConsoleStream::restore_terminal_mode();
std::process::exit(code.unwrap_or(0));
}
}

View File

@ -1,106 +0,0 @@
pub mod attach;
pub mod destroy;
pub mod launch;
pub mod list;
pub mod logs;
pub mod resolve;
pub mod watch;
use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand};
use krata::{
client::ControlClientProvider,
events::EventStream,
v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest},
};
use tonic::{transport::Channel, Request};
use self::{
attach::AttachCommand, destroy::DestroyCommand, launch::LauchCommand, list::ListCommand,
logs::LogsCommand, resolve::ResolveCommand, watch::WatchCommand,
};
#[derive(Parser)]
#[command(
version,
about = "Control the krata hypervisor, a secure platform for running containers"
)]
pub struct ControlCommand {
#[arg(
short,
long,
help = "The connection URL to the krata hypervisor",
default_value = "unix:///var/lib/krata/daemon.socket"
)]
connection: String,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
Launch(LauchCommand),
Destroy(DestroyCommand),
List(ListCommand),
Attach(AttachCommand),
Logs(LogsCommand),
Watch(WatchCommand),
Resolve(ResolveCommand),
}
impl ControlCommand {
pub async fn run(self) -> Result<()> {
let client = ControlClientProvider::dial(self.connection.parse()?).await?;
let events = EventStream::open(client.clone()).await?;
match self.command {
Commands::Launch(launch) => {
launch.run(client, events).await?;
}
Commands::Destroy(destroy) => {
destroy.run(client, events).await?;
}
Commands::Attach(attach) => {
attach.run(client, events).await?;
}
Commands::Logs(logs) => {
logs.run(client, events).await?;
}
Commands::List(list) => {
list.run(client, events).await?;
}
Commands::Watch(watch) => {
watch.run(events).await?;
}
Commands::Resolve(resolve) => {
resolve.run(client).await?;
}
}
Ok(())
}
}
pub async fn resolve_guest(
client: &mut ControlServiceClient<Channel>,
name: &str,
) -> Result<String> {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
name: name.to_string(),
}))
.await?
.into_inner();
if let Some(guest) = reply.guest {
Ok(guest.id)
} else {
Err(anyhow!("unable to resolve guest '{}'", name))
}
}

View File

@ -1,29 +0,0 @@
use anyhow::Result;
use clap::Parser;
use krata::v1::control::{control_service_client::ControlServiceClient, ResolveGuestRequest};
use tonic::{transport::Channel, Request};
#[derive(Parser)]
#[command(about = "Resolve a guest name to a uuid")]
pub struct ResolveCommand {
#[arg(help = "Guest name")]
guest: String,
}
impl ResolveCommand {
pub async fn run(self, mut client: ControlServiceClient<Channel>) -> Result<()> {
let reply = client
.resolve_guest(Request::new(ResolveGuestRequest {
name: self.guest.clone(),
}))
.await?
.into_inner();
if let Some(guest) = reply.guest {
println!("{}", guest.id);
} else {
std::process::exit(1);
}
Ok(())
}
}

View File

@ -1,70 +0,0 @@
use anyhow::Result;
use clap::{Parser, ValueEnum};
use krata::{
events::EventStream,
v1::{common::Guest, control::watch_events_reply::Event},
};
use prost_reflect::ReflectMessage;
use serde_json::Value;
use crate::format::{guest_simple_line, kv2line, proto2dynamic, proto2kv};
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
enum WatchFormat {
Simple,
Json,
KeyValue,
}
#[derive(Parser)]
#[command(about = "Watch for guest changes")]
pub struct WatchCommand {
#[arg(short, long, default_value = "simple", help = "Output format")]
format: WatchFormat,
}
impl WatchCommand {
pub async fn run(self, events: EventStream) -> Result<()> {
let mut stream = events.subscribe();
loop {
let event = stream.recv().await?;
match event {
Event::GuestChanged(changed) => {
let guest = changed.guest.clone();
self.print_event("guest.changed", changed, guest)?;
}
}
}
}
fn print_event(
&self,
typ: &str,
event: impl ReflectMessage,
guest: Option<Guest>,
) -> Result<()> {
match self.format {
WatchFormat::Simple => {
if let Some(guest) = guest {
println!("{}", guest_simple_line(&guest));
}
}
WatchFormat::Json => {
let message = proto2dynamic(event)?;
let mut value = serde_json::to_value(&message)?;
if let Value::Object(ref mut map) = value {
map.insert("event.type".to_string(), Value::String(typ.to_string()));
}
println!("{}", serde_json::to_string(&value)?);
}
WatchFormat::KeyValue => {
let mut map = proto2kv(event)?;
map.insert("event.type".to_string(), typ.to_string());
println!("{}", kv2line(map),);
}
}
Ok(())
}
}

View File

@ -1,115 +0,0 @@
use anyhow::Result;
use async_stream::stream;
use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled},
tty::IsTty,
};
use krata::{
events::EventStream,
v1::{
common::GuestStatus,
control::{watch_events_reply::Event, ConsoleDataReply, ConsoleDataRequest},
},
};
use log::debug;
use tokio::{
io::{stdin, stdout, AsyncReadExt, AsyncWriteExt},
task::JoinHandle,
};
use tokio_stream::{Stream, StreamExt};
use tonic::Streaming;
pub struct StdioConsoleStream;
impl StdioConsoleStream {
pub async fn stdin_stream(guest: String) -> impl Stream<Item = ConsoleDataRequest> {
let mut stdin = stdin();
stream! {
yield ConsoleDataRequest { guest_id: guest, data: vec![] };
let mut buffer = vec![0u8; 60];
loop {
let size = match stdin.read(&mut buffer).await {
Ok(size) => size,
Err(error) => {
debug!("failed to read stdin: {}", error);
break;
}
};
let data = buffer[0..size].to_vec();
if size == 1 && buffer[0] == 0x1d {
break;
}
yield ConsoleDataRequest { guest_id: String::default(), data };
}
}
}
pub async fn stdout(mut stream: Streaming<ConsoleDataReply>) -> Result<()> {
if stdin().is_tty() {
enable_raw_mode()?;
StdioConsoleStream::register_terminal_restore_hook()?;
}
let mut stdout = stdout();
while let Some(reply) = stream.next().await {
let reply = reply?;
if reply.data.is_empty() {
continue;
}
stdout.write_all(&reply.data).await?;
stdout.flush().await?;
}
Ok(())
}
pub async fn guest_exit_hook(
id: String,
events: EventStream,
) -> Result<JoinHandle<Option<i32>>> {
Ok(tokio::task::spawn(async move {
let mut stream = events.subscribe();
while let Ok(event) = stream.recv().await {
match event {
Event::GuestChanged(changed) => {
let Some(guest) = changed.guest else {
continue;
};
let Some(state) = guest.state else {
continue;
};
if guest.id != id {
continue;
}
if let Some(exit_info) = state.exit_info {
return Some(exit_info.code);
}
let status = state.status();
if status == GuestStatus::Destroying || status == GuestStatus::Destroyed {
return Some(10);
}
}
}
}
None
}))
}
fn register_terminal_restore_hook() -> Result<()> {
if stdin().is_tty() {
ctrlc::set_handler(move || {
StdioConsoleStream::restore_terminal_mode();
})?;
}
Ok(())
}
pub fn restore_terminal_mode() {
if is_raw_mode_enabled().unwrap_or(false) {
let _ = disable_raw_mode();
}
}
}

View File

@ -1,87 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use krata::v1::common::{Guest, GuestStatus};
use prost_reflect::{DynamicMessage, ReflectMessage, Value};
pub fn proto2dynamic(proto: impl ReflectMessage) -> Result<DynamicMessage> {
Ok(DynamicMessage::decode(
proto.descriptor(),
proto.encode_to_vec().as_slice(),
)?)
}
pub fn proto2kv(proto: impl ReflectMessage) -> Result<HashMap<String, String>> {
let message = proto2dynamic(proto)?;
let mut map = HashMap::new();
fn crawl(prefix: &str, map: &mut HashMap<String, String>, message: &DynamicMessage) {
for (field, value) in message.fields() {
let path = if prefix.is_empty() {
field.name().to_string()
} else {
format!("{}.{}", prefix, field.name())
};
match value {
Value::Message(child) => {
crawl(&path, map, child);
}
Value::EnumNumber(number) => {
if let Some(e) = field.kind().as_enum() {
if let Some(value) = e.get_value(*number) {
map.insert(path, value.name().to_string());
}
}
}
Value::String(value) => {
map.insert(path, value.clone());
}
_ => {
map.insert(path, value.to_string());
}
}
}
}
crawl("", &mut map, &message);
Ok(map)
}
pub fn kv2line(map: HashMap<String, String>) -> String {
map.iter()
.map(|(k, v)| format!("{}=\"{}\"", k, v.replace('"', "\\\"")))
.collect::<Vec<_>>()
.join(" ")
}
pub fn guest_status_text(status: GuestStatus) -> String {
match status {
GuestStatus::Starting => "starting",
GuestStatus::Started => "started",
GuestStatus::Destroying => "destroying",
GuestStatus::Destroyed => "destroyed",
GuestStatus::Exited => "exited",
GuestStatus::Failed => "failed",
_ => "unknown",
}
.to_string()
}
pub fn guest_simple_line(guest: &Guest) -> String {
let state = guest_status_text(
guest
.state
.as_ref()
.map(|x| x.status())
.unwrap_or(GuestStatus::Unknown),
);
let name = guest.spec.as_ref().map(|x| x.name.as_str()).unwrap_or("");
let network = guest.state.as_ref().and_then(|x| x.network.as_ref());
let ipv4 = network.map(|x| x.guest_ipv4.as_str()).unwrap_or("");
let ipv6 = network.map(|x| x.guest_ipv6.as_str()).unwrap_or("");
format!("{}\t{}\t{}\t{}\t{}", guest.id, state, name, ipv4, ipv6)
}

View File

@ -1,3 +0,0 @@
pub mod cli;
pub mod console;
pub mod format;

View File

@ -1,36 +0,0 @@
[package]
name = "krata-daemon"
description = "Daemon for the krata hypervisor."
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
anyhow = { workspace = true }
async-stream = { workspace = true }
async-trait = { workspace = true }
bytes = { workspace = true }
circular-buffer = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
futures = { workspace = true }
krata = { path = "../krata", version = "^0.0.7" }
krata-runtime = { path = "../runtime", version = "^0.0.7" }
log = { workspace = true }
prost = { workspace = true }
redb = { workspace = true }
signal-hook = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tonic = { workspace = true, features = ["tls"] }
uuid = { workspace = true }
[lib]
name = "kratad"
[[bin]]
name = "kratad"
path = "bin/daemon.rs"

View File

@ -1,40 +0,0 @@
use anyhow::Result;
use clap::Parser;
use env_logger::Env;
use krata::dial::ControlDialAddress;
use kratad::Daemon;
use kratart::Runtime;
use log::LevelFilter;
use std::{
str::FromStr,
sync::{atomic::AtomicBool, Arc},
};
#[derive(Parser)]
struct DaemonCommand {
#[arg(short, long, default_value = "unix:///var/lib/krata/daemon.socket")]
listen: String,
#[arg(short, long, default_value = "/var/lib/krata")]
store: String,
}
#[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();
mask_sighup()?;
let args = DaemonCommand::parse();
let addr = ControlDialAddress::from_str(&args.listen)?;
let runtime = Runtime::new(args.store.clone()).await?;
let mut daemon = Daemon::new(args.store.clone(), runtime).await?;
daemon.listen(addr).await?;
Ok(())
}
fn mask_sighup() -> Result<()> {
let flag = Arc::new(AtomicBool::new(false));
signal_hook::flag::register(signal_hook::consts::SIGHUP, flag)?;
Ok(())
}

View File

@ -1,147 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use circular_buffer::CircularBuffer;
use kratart::channel::ChannelService;
use log::error;
use tokio::{
sync::{
mpsc::{error::TrySendError, Receiver, Sender},
Mutex,
},
task::JoinHandle,
};
const CONSOLE_BUFFER_SIZE: usize = 1024 * 1024;
type RawConsoleBuffer = CircularBuffer<CONSOLE_BUFFER_SIZE, u8>;
type ConsoleBuffer = Box<RawConsoleBuffer>;
type ListenerMap = Arc<Mutex<HashMap<u32, Vec<Sender<Vec<u8>>>>>>;
type BufferMap = Arc<Mutex<HashMap<u32, ConsoleBuffer>>>;
#[derive(Clone)]
pub struct DaemonConsoleHandle {
listeners: ListenerMap,
buffers: BufferMap,
sender: Sender<(u32, Vec<u8>)>,
task: Arc<JoinHandle<()>>,
}
#[derive(Clone)]
pub struct DaemonConsoleAttachHandle {
pub initial: Vec<u8>,
listeners: ListenerMap,
sender: Sender<(u32, Vec<u8>)>,
domid: u32,
}
impl DaemonConsoleAttachHandle {
pub async fn unsubscribe(&self) -> Result<()> {
let mut guard = self.listeners.lock().await;
let _ = guard.remove(&self.domid);
Ok(())
}
pub async fn send(&self, data: Vec<u8>) -> Result<()> {
Ok(self.sender.send((self.domid, data)).await?)
}
}
impl DaemonConsoleHandle {
pub async fn attach(
&self,
domid: u32,
sender: Sender<Vec<u8>>,
) -> Result<DaemonConsoleAttachHandle> {
let buffers = self.buffers.lock().await;
let buffer = buffers.get(&domid).map(|x| x.to_vec()).unwrap_or_default();
drop(buffers);
let mut listeners = self.listeners.lock().await;
let senders = listeners.entry(domid).or_default();
senders.push(sender);
Ok(DaemonConsoleAttachHandle {
initial: buffer,
sender: self.sender.clone(),
listeners: self.listeners.clone(),
domid,
})
}
}
impl Drop for DaemonConsoleHandle {
fn drop(&mut self) {
if Arc::strong_count(&self.task) <= 1 {
self.task.abort();
}
}
}
pub struct DaemonConsole {
listeners: ListenerMap,
buffers: BufferMap,
receiver: Receiver<(u32, Vec<u8>)>,
sender: Sender<(u32, Vec<u8>)>,
task: JoinHandle<()>,
}
impl DaemonConsole {
pub async fn new() -> Result<DaemonConsole> {
let (service, sender, receiver) =
ChannelService::new("krata-console".to_string(), Some(0)).await?;
let task = service.launch().await?;
let listeners = Arc::new(Mutex::new(HashMap::new()));
let buffers = Arc::new(Mutex::new(HashMap::new()));
Ok(DaemonConsole {
listeners,
buffers,
receiver,
sender,
task,
})
}
pub async fn launch(mut self) -> Result<DaemonConsoleHandle> {
let listeners = self.listeners.clone();
let buffers = self.buffers.clone();
let sender = self.sender.clone();
let task = tokio::task::spawn(async move {
if let Err(error) = self.process().await {
error!("failed to process console: {}", error);
}
});
Ok(DaemonConsoleHandle {
listeners,
buffers,
sender,
task: Arc::new(task),
})
}
async fn process(&mut self) -> Result<()> {
loop {
let Some((domid, data)) = self.receiver.recv().await else {
break;
};
let mut buffers = self.buffers.lock().await;
let buffer = buffers
.entry(domid)
.or_insert_with_key(|_| RawConsoleBuffer::boxed());
buffer.extend_from_slice(&data);
drop(buffers);
let mut listeners = self.listeners.lock().await;
if let Some(senders) = listeners.get_mut(&domid) {
senders.retain(|sender| {
!matches!(sender.try_send(data.to_vec()), Err(TrySendError::Closed(_)))
});
}
}
Ok(())
}
}
impl Drop for DaemonConsole {
fn drop(&mut self) {
self.task.abort();
}
}

View File

@ -1,285 +0,0 @@
use std::{pin::Pin, str::FromStr};
use async_stream::try_stream;
use futures::Stream;
use krata::v1::{
common::{Guest, GuestState, GuestStatus},
control::{
control_service_server::ControlService, ConsoleDataReply, ConsoleDataRequest,
CreateGuestReply, CreateGuestRequest, DestroyGuestReply, DestroyGuestRequest,
ListGuestsReply, ListGuestsRequest, ResolveGuestReply, ResolveGuestRequest,
WatchEventsReply, WatchEventsRequest,
},
};
use tokio::{
select,
sync::mpsc::{channel, Sender},
};
use tokio_stream::StreamExt;
use tonic::{Request, Response, Status, Streaming};
use uuid::Uuid;
use crate::{console::DaemonConsoleHandle, db::GuestStore, event::DaemonEventContext};
pub struct ApiError {
message: String,
}
impl From<anyhow::Error> for ApiError {
fn from(value: anyhow::Error) -> Self {
ApiError {
message: value.to_string(),
}
}
}
impl From<ApiError> for Status {
fn from(value: ApiError) -> Self {
Status::unknown(value.message)
}
}
#[derive(Clone)]
pub struct RuntimeControlService {
events: DaemonEventContext,
console: DaemonConsoleHandle,
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
}
impl RuntimeControlService {
pub fn new(
events: DaemonEventContext,
console: DaemonConsoleHandle,
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
) -> Self {
Self {
events,
console,
guests,
guest_reconciler_notify,
}
}
}
enum ConsoleDataSelect {
Read(Option<Vec<u8>>),
Write(Option<Result<ConsoleDataRequest, tonic::Status>>),
}
#[tonic::async_trait]
impl ControlService for RuntimeControlService {
type ConsoleDataStream =
Pin<Box<dyn Stream<Item = Result<ConsoleDataReply, Status>> + Send + 'static>>;
type WatchEventsStream =
Pin<Box<dyn Stream<Item = Result<WatchEventsReply, Status>> + Send + 'static>>;
async fn create_guest(
&self,
request: Request<CreateGuestRequest>,
) -> Result<Response<CreateGuestReply>, Status> {
let request = request.into_inner();
let Some(spec) = request.spec else {
return Err(ApiError {
message: "guest spec not provided".to_string(),
}
.into());
};
let uuid = Uuid::new_v4();
self.guests
.update(
uuid,
Guest {
id: uuid.to_string(),
state: Some(GuestState {
status: GuestStatus::Starting.into(),
network: None,
exit_info: None,
error_info: None,
domid: u32::MAX,
}),
spec: Some(spec),
},
)
.await
.map_err(ApiError::from)?;
self.guest_reconciler_notify
.send(uuid)
.await
.map_err(|x| ApiError {
message: x.to_string(),
})?;
Ok(Response::new(CreateGuestReply {
guest_id: uuid.to_string(),
}))
}
async fn destroy_guest(
&self,
request: Request<DestroyGuestRequest>,
) -> Result<Response<DestroyGuestReply>, Status> {
let request = request.into_inner();
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
message: error.to_string(),
})?;
let Some(mut guest) = self.guests.read(uuid).await.map_err(ApiError::from)? else {
return Err(ApiError {
message: "guest not found".to_string(),
}
.into());
};
guest.state = Some(guest.state.as_mut().cloned().unwrap_or_default());
if guest.state.as_ref().unwrap().status() == GuestStatus::Destroyed {
return Err(ApiError {
message: "guest already destroyed".to_string(),
}
.into());
}
guest.state.as_mut().unwrap().status = GuestStatus::Destroying.into();
self.guests
.update(uuid, guest)
.await
.map_err(ApiError::from)?;
self.guest_reconciler_notify
.send(uuid)
.await
.map_err(|x| ApiError {
message: x.to_string(),
})?;
Ok(Response::new(DestroyGuestReply {}))
}
async fn list_guests(
&self,
request: Request<ListGuestsRequest>,
) -> Result<Response<ListGuestsReply>, Status> {
let _ = request.into_inner();
let guests = self.guests.list().await.map_err(ApiError::from)?;
let guests = guests.into_values().collect::<Vec<Guest>>();
Ok(Response::new(ListGuestsReply { guests }))
}
async fn resolve_guest(
&self,
request: Request<ResolveGuestRequest>,
) -> Result<Response<ResolveGuestReply>, Status> {
let request = request.into_inner();
let guests = self.guests.list().await.map_err(ApiError::from)?;
let guests = guests
.into_values()
.filter(|x| {
let comparison_spec = x.spec.as_ref().cloned().unwrap_or_default();
(!request.name.is_empty() && comparison_spec.name == request.name)
|| x.id == request.name
})
.collect::<Vec<Guest>>();
Ok(Response::new(ResolveGuestReply {
guest: guests.first().cloned(),
}))
}
async fn console_data(
&self,
request: Request<Streaming<ConsoleDataRequest>>,
) -> Result<Response<Self::ConsoleDataStream>, Status> {
let mut input = request.into_inner();
let Some(request) = input.next().await else {
return Err(ApiError {
message: "expected to have at least one request".to_string(),
}
.into());
};
let request = request?;
let uuid = Uuid::from_str(&request.guest_id).map_err(|error| ApiError {
message: error.to_string(),
})?;
let guest = self
.guests
.read(uuid)
.await
.map_err(|error| ApiError {
message: error.to_string(),
})?
.ok_or_else(|| ApiError {
message: "guest did not exist in the database".to_string(),
})?;
let Some(ref state) = guest.state else {
return Err(ApiError {
message: "guest did not have state".to_string(),
}
.into());
};
let domid = state.domid;
if domid == 0 {
return Err(ApiError {
message: "invalid domid on the guest".to_string(),
}
.into());
}
let (sender, mut receiver) = channel(100);
let console = self
.console
.attach(domid, sender)
.await
.map_err(|error| ApiError {
message: format!("failed to attach to console: {}", error),
})?;
let output = try_stream! {
yield ConsoleDataReply { data: console.initial.clone(), };
loop {
let what = select! {
x = receiver.recv() => ConsoleDataSelect::Read(x),
x = input.next() => ConsoleDataSelect::Write(x),
};
match what {
ConsoleDataSelect::Read(Some(data)) => {
yield ConsoleDataReply { data, };
},
ConsoleDataSelect::Read(None) => {
break;
}
ConsoleDataSelect::Write(Some(request)) => {
let request = request?;
if !request.data.is_empty() {
console.send(request.data).await.map_err(|error| ApiError {
message: error.to_string(),
})?;
}
},
ConsoleDataSelect::Write(None) => {
break;
}
}
}
};
Ok(Response::new(Box::pin(output) as Self::ConsoleDataStream))
}
async fn watch_events(
&self,
request: Request<WatchEventsRequest>,
) -> Result<Response<Self::WatchEventsStream>, Status> {
let _ = request.into_inner();
let mut events = self.events.subscribe();
let output = try_stream! {
while let Ok(event) = events.recv().await {
yield WatchEventsReply { event: Some(event), };
}
};
Ok(Response::new(Box::pin(output) as Self::WatchEventsStream))
}
}

View File

@ -1,80 +0,0 @@
use std::{collections::HashMap, path::Path, sync::Arc};
use anyhow::Result;
use krata::v1::common::Guest;
use log::error;
use prost::Message;
use redb::{Database, ReadableTable, TableDefinition};
use uuid::Uuid;
const GUESTS: TableDefinition<u128, &[u8]> = TableDefinition::new("guests");
#[derive(Clone)]
pub struct GuestStore {
database: Arc<Database>,
}
impl GuestStore {
pub fn open(path: &Path) -> Result<Self> {
let database = Database::create(path)?;
let write = database.begin_write()?;
let _ = write.open_table(GUESTS);
write.commit()?;
Ok(GuestStore {
database: Arc::new(database),
})
}
pub async fn read(&self, id: Uuid) -> Result<Option<Guest>> {
let read = self.database.begin_read()?;
let table = read.open_table(GUESTS)?;
let Some(entry) = table.get(id.to_u128_le())? else {
return Ok(None);
};
let bytes = entry.value();
Ok(Some(Guest::decode(bytes)?))
}
pub async fn list(&self) -> Result<HashMap<Uuid, Guest>> {
let mut guests: HashMap<Uuid, Guest> = HashMap::new();
let read = self.database.begin_read()?;
let table = read.open_table(GUESTS)?;
for result in table.iter()? {
let (key, value) = result?;
let uuid = Uuid::from_u128_le(key.value());
let state = match Guest::decode(value.value()) {
Ok(state) => state,
Err(error) => {
error!(
"found invalid guest state in database for uuid {}: {}",
uuid, error
);
continue;
}
};
guests.insert(uuid, state);
}
Ok(guests)
}
pub async fn update(&self, id: Uuid, entry: Guest) -> Result<()> {
let write = self.database.begin_write()?;
{
let mut table = write.open_table(GUESTS)?;
let bytes = entry.encode_to_vec();
table.insert(id.to_u128_le(), bytes.as_slice())?;
}
write.commit()?;
Ok(())
}
pub async fn remove(&self, id: Uuid) -> Result<()> {
let write = self.database.begin_write()?;
{
let mut table = write.open_table(GUESTS)?;
table.remove(id.to_u128_le())?;
}
write.commit()?;
Ok(())
}
}

View File

@ -1,176 +0,0 @@
use std::{
collections::{hash_map::Entry, HashMap},
str::FromStr,
time::Duration,
};
use anyhow::Result;
use krata::{
idm::protocol::{idm_event::Event, IdmPacket},
v1::common::{GuestExitInfo, GuestState, GuestStatus},
};
use log::error;
use tokio::{
select,
sync::{
broadcast,
mpsc::{channel, Receiver, Sender},
},
task::JoinHandle,
time,
};
use uuid::Uuid;
use crate::{
db::GuestStore,
idm::{DaemonIdmHandle, DaemonIdmSubscribeHandle},
};
pub type DaemonEvent = krata::v1::control::watch_events_reply::Event;
const EVENT_CHANNEL_QUEUE_LEN: usize = 1000;
const IDM_CHANNEL_QUEUE_LEN: usize = 1000;
#[derive(Clone)]
pub struct DaemonEventContext {
sender: broadcast::Sender<DaemonEvent>,
}
impl DaemonEventContext {
pub fn subscribe(&self) -> broadcast::Receiver<DaemonEvent> {
self.sender.subscribe()
}
pub fn send(&self, event: DaemonEvent) -> Result<()> {
let _ = self.sender.send(event);
Ok(())
}
}
pub struct DaemonEventGenerator {
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
feed: broadcast::Receiver<DaemonEvent>,
idm: DaemonIdmHandle,
idms: HashMap<u32, (Uuid, DaemonIdmSubscribeHandle)>,
idm_sender: Sender<(u32, IdmPacket)>,
idm_receiver: Receiver<(u32, IdmPacket)>,
_event_sender: broadcast::Sender<DaemonEvent>,
}
impl DaemonEventGenerator {
pub async fn new(
guests: GuestStore,
guest_reconciler_notify: Sender<Uuid>,
idm: DaemonIdmHandle,
) -> Result<(DaemonEventContext, DaemonEventGenerator)> {
let (sender, _) = broadcast::channel(EVENT_CHANNEL_QUEUE_LEN);
let (idm_sender, idm_receiver) = channel(IDM_CHANNEL_QUEUE_LEN);
let generator = DaemonEventGenerator {
guests,
guest_reconciler_notify,
feed: sender.subscribe(),
idm,
idms: HashMap::new(),
idm_sender,
idm_receiver,
_event_sender: sender.clone(),
};
let context = DaemonEventContext { sender };
Ok((context, generator))
}
async fn handle_feed_event(&mut self, event: &DaemonEvent) -> Result<()> {
match event {
DaemonEvent::GuestChanged(changed) => {
let Some(ref guest) = changed.guest else {
return Ok(());
};
let Some(ref state) = guest.state else {
return Ok(());
};
let status = state.status();
let id = Uuid::from_str(&guest.id)?;
let domid = state.domid;
match status {
GuestStatus::Started => {
if let Entry::Vacant(e) = self.idms.entry(domid) {
let subscribe =
self.idm.subscribe(domid, self.idm_sender.clone()).await?;
e.insert((id, subscribe));
}
}
GuestStatus::Destroyed => {
if let Some((_, handle)) = self.idms.remove(&domid) {
handle.unsubscribe().await?;
}
}
_ => {}
}
}
}
Ok(())
}
async fn handle_idm_packet(&mut self, id: Uuid, packet: IdmPacket) -> Result<()> {
if let Some(Event::Exit(exit)) = packet.event.and_then(|x| x.event) {
self.handle_exit_code(id, exit.code).await?;
}
Ok(())
}
async fn handle_exit_code(&mut self, id: Uuid, code: i32) -> Result<()> {
if let Some(mut guest) = self.guests.read(id).await? {
guest.state = Some(GuestState {
status: GuestStatus::Exited.into(),
network: guest.state.clone().unwrap_or_default().network,
exit_info: Some(GuestExitInfo { code }),
error_info: None,
domid: guest.state.clone().map(|x| x.domid).unwrap_or(u32::MAX),
});
self.guests.update(id, guest).await?;
self.guest_reconciler_notify.send(id).await?;
}
Ok(())
}
async fn evaluate(&mut self) -> Result<()> {
select! {
x = self.idm_receiver.recv() => match x {
Some((domid, packet)) => {
if let Some((id, _)) = self.idms.get(&domid) {
self.handle_idm_packet(*id, packet).await?;
}
Ok(())
},
None => {
Ok(())
}
},
x = self.feed.recv() => match x {
Ok(event) => {
self.handle_feed_event(&event).await
},
Err(error) => {
Err(error.into())
}
}
}
}
pub async fn launch(mut self) -> Result<JoinHandle<()>> {
Ok(tokio::task::spawn(async move {
loop {
if let Err(error) = self.evaluate().await {
error!("failed to evaluate daemon events: {}", error);
time::sleep(Duration::from_secs(5)).await;
}
}
}))
}
}

View File

@ -1,135 +0,0 @@
use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use bytes::{Buf, BytesMut};
use krata::idm::protocol::IdmPacket;
use kratart::channel::ChannelService;
use log::{error, warn};
use prost::Message;
use tokio::{
sync::{
mpsc::{Receiver, Sender},
Mutex,
},
task::JoinHandle,
};
type ListenerMap = Arc<Mutex<HashMap<u32, Sender<(u32, IdmPacket)>>>>;
#[derive(Clone)]
pub struct DaemonIdmHandle {
listeners: ListenerMap,
task: Arc<JoinHandle<()>>,
}
#[derive(Clone)]
pub struct DaemonIdmSubscribeHandle {
domid: u32,
listeners: ListenerMap,
}
impl DaemonIdmSubscribeHandle {
pub async fn unsubscribe(&self) -> Result<()> {
let mut guard = self.listeners.lock().await;
let _ = guard.remove(&self.domid);
Ok(())
}
}
impl DaemonIdmHandle {
pub async fn subscribe(
&self,
domid: u32,
sender: Sender<(u32, IdmPacket)>,
) -> Result<DaemonIdmSubscribeHandle> {
let mut guard = self.listeners.lock().await;
guard.insert(domid, sender);
Ok(DaemonIdmSubscribeHandle {
domid,
listeners: self.listeners.clone(),
})
}
}
impl Drop for DaemonIdmHandle {
fn drop(&mut self) {
if Arc::strong_count(&self.task) <= 1 {
self.task.abort();
}
}
}
pub struct DaemonIdm {
listeners: ListenerMap,
receiver: Receiver<(u32, Vec<u8>)>,
task: JoinHandle<()>,
}
impl DaemonIdm {
pub async fn new() -> Result<DaemonIdm> {
let (service, _, receiver) = ChannelService::new("krata-channel".to_string(), None).await?;
let task = service.launch().await?;
let listeners = Arc::new(Mutex::new(HashMap::new()));
Ok(DaemonIdm {
receiver,
task,
listeners,
})
}
pub async fn launch(mut self) -> Result<DaemonIdmHandle> {
let listeners = self.listeners.clone();
let task = tokio::task::spawn(async move {
let mut buffers: HashMap<u32, BytesMut> = HashMap::new();
if let Err(error) = self.process(&mut buffers).await {
error!("failed to process idm: {}", error);
}
});
Ok(DaemonIdmHandle {
listeners,
task: Arc::new(task),
})
}
async fn process(&mut self, buffers: &mut HashMap<u32, BytesMut>) -> Result<()> {
loop {
let Some((domid, data)) = self.receiver.recv().await else {
break;
};
let buffer = buffers.entry(domid).or_insert_with_key(|_| BytesMut::new());
buffer.extend_from_slice(&data);
if buffer.len() < 2 {
continue;
}
let size = (buffer[0] as u16 | (buffer[1] as u16) << 8) as usize;
let needed = size + 2;
if buffer.len() < needed {
continue;
}
let mut packet = buffer.split_to(needed);
packet.advance(2);
match IdmPacket::decode(packet) {
Ok(packet) => {
let guard = self.listeners.lock().await;
if let Some(sender) = guard.get(&domid) {
if let Err(error) = sender.try_send((domid, packet)) {
warn!("dropped idm packet from domain {}: {}", domid, error);
}
}
}
Err(packet) => {
warn!("received invalid packet from domain {}: {}", domid, packet);
}
}
}
Ok(())
}
}
impl Drop for DaemonIdm {
fn drop(&mut self) {
self.task.abort();
}
}

View File

@ -1,138 +0,0 @@
use std::{net::SocketAddr, path::PathBuf, str::FromStr};
use anyhow::Result;
use console::{DaemonConsole, DaemonConsoleHandle};
use control::RuntimeControlService;
use db::GuestStore;
use event::{DaemonEventContext, DaemonEventGenerator};
use idm::{DaemonIdm, DaemonIdmHandle};
use krata::{dial::ControlDialAddress, v1::control::control_service_server::ControlServiceServer};
use kratart::Runtime;
use log::info;
use reconcile::guest::GuestReconciler;
use tokio::{
net::UnixListener,
sync::mpsc::{channel, Sender},
task::JoinHandle,
};
use tokio_stream::wrappers::UnixListenerStream;
use tonic::transport::{Identity, Server, ServerTlsConfig};
use uuid::Uuid;
pub mod console;
pub mod control;
pub mod db;
pub mod event;
pub mod idm;
pub mod reconcile;
pub struct Daemon {
store: String,
guests: GuestStore,
events: DaemonEventContext,
guest_reconciler_task: JoinHandle<()>,
guest_reconciler_notify: Sender<Uuid>,
generator_task: JoinHandle<()>,
_idm: DaemonIdmHandle,
console: DaemonConsoleHandle,
}
const GUEST_RECONCILER_QUEUE_LEN: usize = 1000;
impl Daemon {
pub async fn new(store: String, runtime: Runtime) -> Result<Self> {
let guests_db_path = format!("{}/guests.db", store);
let guests = GuestStore::open(&PathBuf::from(guests_db_path))?;
let (guest_reconciler_notify, guest_reconciler_receiver) =
channel::<Uuid>(GUEST_RECONCILER_QUEUE_LEN);
let idm = DaemonIdm::new().await?;
let idm = idm.launch().await?;
let console = DaemonConsole::new().await?;
let console = console.launch().await?;
let (events, generator) =
DaemonEventGenerator::new(guests.clone(), guest_reconciler_notify.clone(), idm.clone())
.await?;
let runtime_for_reconciler = runtime.dupe().await?;
let guest_reconciler = GuestReconciler::new(
guests.clone(),
events.clone(),
runtime_for_reconciler,
guest_reconciler_notify.clone(),
)?;
let guest_reconciler_task = guest_reconciler.launch(guest_reconciler_receiver).await?;
let generator_task = generator.launch().await?;
Ok(Self {
store,
guests,
events,
guest_reconciler_task,
guest_reconciler_notify,
generator_task,
_idm: idm,
console,
})
}
pub async fn listen(&mut self, addr: ControlDialAddress) -> Result<()> {
let control_service = RuntimeControlService::new(
self.events.clone(),
self.console.clone(),
self.guests.clone(),
self.guest_reconciler_notify.clone(),
);
let mut server = Server::builder();
if let ControlDialAddress::Tls {
host: _,
port: _,
insecure,
} = &addr
{
let mut tls_config = ServerTlsConfig::new();
if !insecure {
let certificate_path = format!("{}/tls/daemon.pem", self.store);
let key_path = format!("{}/tls/daemon.key", self.store);
tls_config = tls_config.identity(Identity::from_pem(certificate_path, key_path));
}
server = server.tls_config(tls_config)?;
}
let server = server.add_service(ControlServiceServer::new(control_service));
info!("listening on address {}", addr);
match addr {
ControlDialAddress::UnixSocket { path } => {
let path = PathBuf::from(path);
if path.exists() {
tokio::fs::remove_file(&path).await?;
}
let listener = UnixListener::bind(path)?;
let stream = UnixListenerStream::new(listener);
server.serve_with_incoming(stream).await?;
}
ControlDialAddress::Tcp { host, port } => {
let address = format!("{}:{}", host, port);
server.serve(SocketAddr::from_str(&address)?).await?;
}
ControlDialAddress::Tls {
host,
port,
insecure: _,
} => {
let address = format!("{}:{}", host, port);
server.serve(SocketAddr::from_str(&address)?).await?;
}
}
Ok(())
}
}
impl Drop for Daemon {
fn drop(&mut self) {
self.guest_reconciler_task.abort();
self.generator_task.abort();
}
}

View File

@ -1,352 +0,0 @@
use std::{
collections::{hash_map::Entry, HashMap},
sync::Arc,
time::Duration,
};
use anyhow::{anyhow, Result};
use krata::v1::{
common::{
guest_image_spec::Image, Guest, GuestErrorInfo, GuestExitInfo, GuestNetworkState,
GuestState, GuestStatus,
},
control::GuestChangedEvent,
};
use kratart::{launch::GuestLaunchRequest, GuestInfo, Runtime};
use log::{error, info, trace, warn};
use tokio::{
select,
sync::{
mpsc::{channel, Receiver, Sender},
Mutex, RwLock,
},
task::JoinHandle,
time::sleep,
};
use uuid::Uuid;
use crate::{
db::GuestStore,
event::{DaemonEvent, DaemonEventContext},
};
const PARALLEL_LIMIT: u32 = 5;
#[derive(Debug)]
enum GuestReconcilerResult {
Unchanged,
Changed { rerun: bool },
}
struct GuestReconcilerEntry {
task: JoinHandle<()>,
sender: Sender<()>,
}
impl Drop for GuestReconcilerEntry {
fn drop(&mut self) {
self.task.abort();
}
}
#[derive(Clone)]
pub struct GuestReconciler {
guests: GuestStore,
events: DaemonEventContext,
runtime: Runtime,
tasks: Arc<Mutex<HashMap<Uuid, GuestReconcilerEntry>>>,
guest_reconciler_notify: Sender<Uuid>,
reconcile_lock: Arc<RwLock<()>>,
}
impl GuestReconciler {
pub fn new(
guests: GuestStore,
events: DaemonEventContext,
runtime: Runtime,
guest_reconciler_notify: Sender<Uuid>,
) -> Result<Self> {
Ok(Self {
guests,
events,
runtime,
tasks: Arc::new(Mutex::new(HashMap::new())),
guest_reconciler_notify,
reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
})
}
pub async fn launch(self, mut notify: Receiver<Uuid>) -> Result<JoinHandle<()>> {
Ok(tokio::task::spawn(async move {
if let Err(error) = self.reconcile_runtime(true).await {
error!("runtime reconciler failed: {}", error);
}
loop {
select! {
x = notify.recv() => match x {
None => {
break;
},
Some(uuid) => {
if let Err(error) = self.launch_task_if_needed(uuid).await {
error!("failed to start guest reconciler task {}: {}", uuid, error);
}
let map = self.tasks.lock().await;
if let Some(entry) = map.get(&uuid) {
if let Err(error) = entry.sender.send(()).await {
error!("failed to notify guest reconciler task {}: {}", uuid, error);
}
}
}
},
_ = sleep(Duration::from_secs(5)) => {
if let Err(error) = self.reconcile_runtime(false).await {
error!("runtime reconciler failed: {}", error);
}
}
};
}
}))
}
pub async fn reconcile_runtime(&self, initial: bool) -> Result<()> {
let _permit = self.reconcile_lock.write().await;
trace!("reconciling runtime");
let runtime_guests = self.runtime.list().await?;
let stored_guests = self.guests.list().await?;
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);
match runtime_guest {
None => {
let mut state = stored_guest.state.as_mut().cloned().unwrap_or_default();
if state.status() == GuestStatus::Started {
state.status = GuestStatus::Starting.into();
}
stored_guest.state = Some(state);
}
Some(runtime) => {
let mut state = stored_guest.state.as_mut().cloned().unwrap_or_default();
if let Some(code) = runtime.state.exit_code {
state.status = GuestStatus::Exited.into();
state.exit_info = Some(GuestExitInfo { code });
} else {
state.status = GuestStatus::Started.into();
}
state.network = Some(guestinfo_to_networkstate(runtime));
stored_guest.state = Some(state);
}
}
let changed = stored_guest != previous_guest;
if changed || initial {
self.guests.update(uuid, stored_guest).await?;
let _ = self.guest_reconciler_notify.try_send(uuid);
}
}
Ok(())
}
pub async fn reconcile(&self, uuid: Uuid) -> Result<bool> {
let _runtime_reconcile_permit = self.reconcile_lock.read().await;
let Some(mut guest) = self.guests.read(uuid).await? else {
warn!(
"notified of reconcile for guest {} but it didn't exist",
uuid
);
return Ok(false);
};
info!("reconciling guest {}", uuid);
self.events
.send(DaemonEvent::GuestChanged(GuestChangedEvent {
guest: Some(guest.clone()),
}))?;
let start_status = guest.state.as_ref().map(|x| x.status()).unwrap_or_default();
let result = match start_status {
GuestStatus::Starting => self.start(uuid, &mut guest).await,
GuestStatus::Exited => self.exited(&mut guest).await,
GuestStatus::Destroying => self.destroy(uuid, &mut guest).await,
_ => Ok(GuestReconcilerResult::Unchanged),
};
let result = match result {
Ok(result) => result,
Err(error) => {
guest.state = Some(guest.state.as_mut().cloned().unwrap_or_default());
guest.state.as_mut().unwrap().status = GuestStatus::Failed.into();
guest.state.as_mut().unwrap().error_info = Some(GuestErrorInfo {
message: error.to_string(),
});
warn!("failed to start guest {}: {}", guest.id, error);
GuestReconcilerResult::Changed { rerun: false }
}
};
info!("reconciled guest {}", uuid);
let status = guest.state.as_ref().map(|x| x.status()).unwrap_or_default();
let destroyed = status == GuestStatus::Destroyed;
let rerun = if let GuestReconcilerResult::Changed { rerun } = result {
let event = DaemonEvent::GuestChanged(GuestChangedEvent {
guest: Some(guest.clone()),
});
if destroyed {
self.guests.remove(uuid).await?;
let mut map = self.tasks.lock().await;
map.remove(&uuid);
} else {
self.guests.update(uuid, guest.clone()).await?;
}
self.events.send(event)?;
rerun
} else {
false
};
Ok(rerun)
}
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
let Some(ref spec) = guest.spec else {
return Err(anyhow!("guest spec not specified"));
};
let Some(ref image) = spec.image else {
return Err(anyhow!("image spec not provided"));
};
let oci = match image.image {
Some(Image::Oci(ref oci)) => oci,
None => {
return Err(anyhow!("oci spec not specified"));
}
};
let task = spec.task.as_ref().cloned().unwrap_or_default();
let info = self
.runtime
.launch(GuestLaunchRequest {
uuid: Some(uuid),
name: if spec.name.is_empty() {
None
} else {
Some(&spec.name)
},
image: &oci.image,
vcpus: spec.vcpus,
mem: spec.mem,
env: task
.environment
.iter()
.map(|x| (x.key.clone(), x.value.clone()))
.collect::<HashMap<_, _>>(),
run: empty_vec_optional(task.command.clone()),
debug: false,
})
.await?;
info!("started guest {}", uuid);
guest.state = Some(GuestState {
status: GuestStatus::Started.into(),
network: Some(guestinfo_to_networkstate(&info)),
exit_info: None,
error_info: None,
domid: info.domid,
});
Ok(GuestReconcilerResult::Changed { rerun: false })
}
async fn exited(&self, guest: &mut Guest) -> Result<GuestReconcilerResult> {
if let Some(ref mut state) = guest.state {
state.set_status(GuestStatus::Destroying);
Ok(GuestReconcilerResult::Changed { rerun: true })
} else {
Ok(GuestReconcilerResult::Unchanged)
}
}
async fn destroy(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
if let Err(error) = self.runtime.destroy(uuid).await {
trace!("failed to destroy runtime guest {}: {}", uuid, error);
}
info!("destroyed guest {}", uuid);
guest.state = Some(GuestState {
status: GuestStatus::Destroyed.into(),
network: None,
exit_info: None,
error_info: None,
domid: guest.state.as_ref().map(|x| x.domid).unwrap_or(u32::MAX),
});
Ok(GuestReconcilerResult::Changed { rerun: false })
}
async fn launch_task_if_needed(&self, uuid: Uuid) -> Result<()> {
let mut map = self.tasks.lock().await;
match map.entry(uuid) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
entry.insert(self.launch_task(uuid).await?);
}
}
Ok(())
}
async fn launch_task(&self, uuid: Uuid) -> Result<GuestReconcilerEntry> {
let this = self.clone();
let (sender, mut receiver) = channel(10);
let task = tokio::task::spawn(async move {
'notify_loop: loop {
if receiver.recv().await.is_none() {
break 'notify_loop;
}
'rerun_loop: loop {
let rerun = match this.reconcile(uuid).await {
Ok(rerun) => rerun,
Err(error) => {
error!("failed to reconcile guest {}: {}", uuid, error);
false
}
};
if rerun {
continue 'rerun_loop;
}
break 'rerun_loop;
}
}
});
Ok(GuestReconcilerEntry { task, sender })
}
}
fn empty_vec_optional<T>(value: Vec<T>) -> Option<Vec<T>> {
if value.is_empty() {
None
} else {
Some(value)
}
}
fn guestinfo_to_networkstate(info: &GuestInfo) -> GuestNetworkState {
GuestNetworkState {
guest_ipv4: info.guest_ipv4.map(|x| x.to_string()).unwrap_or_default(),
guest_ipv6: info.guest_ipv6.map(|x| x.to_string()).unwrap_or_default(),
guest_mac: info.guest_mac.as_ref().cloned().unwrap_or_default(),
gateway_ipv4: info.gateway_ipv4.map(|x| x.to_string()).unwrap_or_default(),
gateway_ipv6: info.gateway_ipv6.map(|x| x.to_string()).unwrap_or_default(),
gateway_mac: info.gateway_mac.as_ref().cloned().unwrap_or_default(),
}
}

View File

@ -1 +0,0 @@
pub mod guest;

View File

@ -1,36 +0,0 @@
[package]
name = "krata-guest"
description = "Guest services for the krata hypervisor."
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
anyhow = { workspace = true }
cgroups-rs = { workspace = true }
env_logger = { workspace = true }
futures = { workspace = true }
ipnetwork = { workspace = true }
krata = { path = "../krata", version = "^0.0.7" }
krata-xenstore = { path = "../xen/xenstore", version = "^0.0.7" }
libc = { workspace = true }
log = { workspace = true }
nix = { workspace = true, features = ["ioctl", "process", "fs"] }
oci-spec = { workspace = true }
path-absolutize = { workspace = true }
rtnetlink = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sys-mount = { workspace = true }
tokio = { workspace = true }
walkdir = { workspace = true }
[lib]
name = "krataguest"
[[bin]]
name = "krataguest"
path = "bin/init.rs"

View File

@ -1,28 +0,0 @@
use anyhow::{anyhow, Result};
use env_logger::Env;
use krataguest::{death, init::GuestInit};
use log::error;
use std::env;
#[tokio::main]
async fn main() -> Result<()> {
env::set_var("RUST_BACKTRACE", "1");
env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init();
if env::var("KRATA_UNSAFE_ALWAYS_ALLOW_INIT").unwrap_or("0".to_string()) != "1" {
let pid = std::process::id();
if pid > 3 {
return Err(anyhow!(
"not running because the pid of {} indicates this is probably not \
the right context for the init daemon. \
run with KRATA_UNSAFE_ALWAYS_ALLOW_INIT=1 to bypass this check",
pid
));
}
}
let mut guest = GuestInit::new();
if let Err(error) = guest.init().await {
error!("failed to initialize guest: {}", error);
death(127).await?;
}
Ok(())
}

View File

@ -1,71 +0,0 @@
use crate::{
childwait::{ChildEvent, ChildWait},
death,
};
use anyhow::Result;
use cgroups_rs::Cgroup;
use krata::idm::{
client::IdmClient,
protocol::{idm_event::Event, IdmEvent, IdmExitEvent, IdmPacket},
};
use log::error;
use nix::unistd::Pid;
use tokio::select;
pub struct GuestBackground {
idm: IdmClient,
child: Pid,
_cgroup: Cgroup,
wait: ChildWait,
}
impl GuestBackground {
pub async fn new(idm: IdmClient, cgroup: Cgroup, child: Pid) -> Result<GuestBackground> {
Ok(GuestBackground {
idm,
child,
_cgroup: cgroup,
wait: ChildWait::new()?,
})
}
pub async fn run(&mut self) -> Result<()> {
loop {
select! {
x = self.idm.receiver.recv() => match x {
Some(_packet) => {
},
None => {
error!("idm packet channel closed");
break;
}
},
event = self.wait.recv() => match event {
Some(event) => self.child_event(event).await?,
None => {
break;
}
}
};
}
Ok(())
}
async fn child_event(&mut self, event: ChildEvent) -> Result<()> {
if event.pid == self.child {
self.idm
.sender
.send(IdmPacket {
event: Some(IdmEvent {
event: Some(Event::Exit(IdmExitEvent { code: event.status })),
}),
})
.await?;
death(event.status).await?;
}
Ok(())
}
}

View File

@ -1,85 +0,0 @@
use std::{
ptr::addr_of_mut,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::{self, JoinHandle},
};
use anyhow::Result;
use libc::{c_int, waitpid, WEXITSTATUS, WIFEXITED};
use log::warn;
use nix::unistd::Pid;
use tokio::sync::mpsc::{channel, Receiver, Sender};
const CHILD_WAIT_QUEUE_LEN: usize = 10;
#[derive(Clone, Copy, Debug)]
pub struct ChildEvent {
pub pid: Pid,
pub status: c_int,
}
pub struct ChildWait {
receiver: Receiver<ChildEvent>,
signal: Arc<AtomicBool>,
_task: JoinHandle<()>,
}
impl ChildWait {
pub fn new() -> Result<ChildWait> {
let (sender, receiver) = channel(CHILD_WAIT_QUEUE_LEN);
let signal = Arc::new(AtomicBool::new(false));
let mut processor = ChildWaitTask {
sender,
signal: signal.clone(),
};
let task = thread::spawn(move || {
if let Err(error) = processor.process() {
warn!("failed to process child updates: {}", error);
}
});
Ok(ChildWait {
receiver,
signal,
_task: task,
})
}
pub async fn recv(&mut self) -> Option<ChildEvent> {
self.receiver.recv().await
}
}
struct ChildWaitTask {
sender: Sender<ChildEvent>,
signal: Arc<AtomicBool>,
}
impl ChildWaitTask {
fn process(&mut self) -> Result<()> {
loop {
let mut status: c_int = 0;
let pid = unsafe { waitpid(-1, addr_of_mut!(status), 0) };
if WIFEXITED(status) {
let event = ChildEvent {
pid: Pid::from_raw(pid),
status: WEXITSTATUS(status),
};
let _ = self.sender.try_send(event);
if self.signal.load(Ordering::Acquire) {
return Ok(());
}
}
}
}
}
impl Drop for ChildWait {
fn drop(&mut self) {
self.signal.store(true, Ordering::Release);
}
}

View File

@ -1,590 +0,0 @@
use anyhow::{anyhow, Result};
use cgroups_rs::{Cgroup, CgroupPid};
use futures::stream::TryStreamExt;
use ipnetwork::IpNetwork;
use krata::ethtool::EthtoolHandle;
use krata::idm::client::IdmClient;
use krata::launchcfg::{LaunchInfo, LaunchNetwork};
use libc::{sethostname, setsid, TIOCSCTTY};
use log::{trace, warn};
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 std::collections::HashMap;
use std::ffi::CString;
use std::fs::{File, OpenOptions, Permissions};
use std::io;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::os::fd::AsRawFd;
use std::os::linux::fs::MetadataExt;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{chroot, PermissionsExt};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use sys_mount::{FilesystemType, Mount, MountFlags};
use tokio::fs;
use walkdir::WalkDir;
use crate::background::GuestBackground;
const IMAGE_BLOCK_DEVICE_PATH: &str = "/dev/xvda";
const CONFIG_BLOCK_DEVICE_PATH: &str = "/dev/xvdb";
const IMAGE_MOUNT_PATH: &str = "/image";
const CONFIG_MOUNT_PATH: &str = "/config";
const OVERLAY_MOUNT_PATH: &str = "/overlay";
const OVERLAY_IMAGE_BIND_PATH: &str = "/overlay/image";
const OVERLAY_WORK_PATH: &str = "/overlay/work";
const OVERLAY_UPPER_PATH: &str = "/overlay/upper";
const SYS_PATH: &str = "/sys";
const PROC_PATH: &str = "/proc";
const DEV_PATH: &str = "/dev";
const NEW_ROOT_PATH: &str = "/newroot";
const NEW_ROOT_SYS_PATH: &str = "/newroot/sys";
const NEW_ROOT_PROC_PATH: &str = "/newroot/proc";
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";
ioctl_write_int_bad!(set_controlling_terminal, TIOCSCTTY);
pub struct GuestInit {}
impl Default for GuestInit {
fn default() -> Self {
Self::new()
}
}
impl GuestInit {
pub fn new() -> GuestInit {
GuestInit {}
}
pub async fn init(&mut self) -> Result<()> {
self.early_init().await?;
trace!("opening console descriptor");
match OpenOptions::new()
.read(true)
.write(true)
.open("/dev/console")
{
Ok(console) => self.map_console(&console)?,
Err(error) => warn!("failed to open console: {}", error),
};
let idm = IdmClient::open("/dev/hvc1")
.await
.map_err(|x| anyhow!("failed to open idm client: {}", x))?;
self.mount_squashfs_images().await?;
let config = self.parse_image_config().await?;
let launch = self.parse_launch_config().await?;
self.mount_new_root().await?;
self.nuke_initrd().await?;
self.bind_new_root().await?;
if let Some(hostname) = launch.hostname.clone() {
let result = unsafe {
sethostname(
hostname.as_bytes().as_ptr() as *mut libc::c_char,
hostname.len(),
)
};
if result != 0 {
warn!("failed to set hostname: {}", result);
}
}
if let Some(network) = &launch.network {
trace!("initializing network");
if let Err(error) = self.network_setup(network).await {
warn!("failed to initialize network: {}", error);
}
}
if let Some(cfg) = config.config() {
trace!("running guest task");
self.run(cfg, &launch, idm).await?;
} else {
return Err(anyhow!(
"unable to determine what to execute, image config doesn't tell us"
));
}
Ok(())
}
async fn early_init(&mut self) -> Result<()> {
trace!("early init");
self.create_dir("/dev", Some(0o0755)).await?;
self.create_dir("/proc", None).await?;
self.create_dir("/sys", Some(0o0555)).await?;
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)
.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?;
Ok(())
}
async fn create_dir(&mut self, path: &str, mode: Option<u32>) -> Result<()> {
let path = Path::new(path);
if !path.is_dir() {
trace!("creating directory {:?}", path);
fs::create_dir(path).await?;
}
if let Some(mode) = mode {
let permissions = Permissions::from_mode(mode);
trace!("setting directory {:?} permissions to {:?}", path, mode);
fs::set_permissions(path, permissions).await?;
}
Ok(())
}
async fn mount_kernel_fs(
&mut self,
fstype: &str,
path: &str,
data: &str,
flags: Option<MountFlags>,
) -> 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)?;
Ok(())
}
fn map_console(&mut self, console: &File) -> Result<()> {
trace!("mapping console");
dup2(console.as_raw_fd(), 0)?;
dup2(console.as_raw_fd(), 1)?;
dup2(console.as_raw_fd(), 2)?;
Ok(())
}
async fn mount_squashfs_images(&mut self) -> Result<()> {
trace!("mounting squashfs images");
let image_mount_path = Path::new(IMAGE_MOUNT_PATH);
let config_mount_path = Path::new(CONFIG_MOUNT_PATH);
self.mount_squashfs(Path::new(IMAGE_BLOCK_DEVICE_PATH), image_mount_path)
.await?;
self.mount_squashfs(Path::new(CONFIG_BLOCK_DEVICE_PATH), config_mount_path)
.await?;
Ok(())
}
async fn mount_squashfs(&mut self, from: &Path, to: &Path) -> Result<()> {
trace!("mounting squashfs image {:?} to {:?}", from, to);
if !to.is_dir() {
fs::create_dir(to).await?;
}
Mount::builder()
.fstype(FilesystemType::Manual("squashfs"))
.flags(MountFlags::RDONLY)
.mount(from, to)?;
Ok(())
}
async fn mount_move_subtree(&mut self, from: &Path, to: &Path) -> Result<()> {
trace!("moving subtree {:?} to {:?}", from, to);
if !to.is_dir() {
fs::create_dir(to).await?;
}
Mount::builder()
.fstype(FilesystemType::Manual("none"))
.flags(MountFlags::MOVE)
.mount(from, to)?;
Ok(())
}
async fn mount_new_root(&mut self) -> Result<()> {
trace!("mounting new root");
self.mount_overlay_tmpfs().await?;
self.bind_image_to_overlay_tmpfs().await?;
self.mount_overlay_to_new_root().await?;
std::env::set_current_dir(NEW_ROOT_PATH)?;
trace!("mounted new root");
Ok(())
}
async fn mount_overlay_tmpfs(&mut self) -> Result<()> {
fs::create_dir(OVERLAY_MOUNT_PATH).await?;
Mount::builder()
.fstype(FilesystemType::Manual("tmpfs"))
.mount("tmpfs", OVERLAY_MOUNT_PATH)?;
fs::create_dir(OVERLAY_UPPER_PATH).await?;
fs::create_dir(OVERLAY_WORK_PATH).await?;
Ok(())
}
async fn bind_image_to_overlay_tmpfs(&mut self) -> Result<()> {
fs::create_dir(OVERLAY_IMAGE_BIND_PATH).await?;
Mount::builder()
.fstype(FilesystemType::Manual("none"))
.flags(MountFlags::BIND | MountFlags::RDONLY)
.mount(IMAGE_MOUNT_PATH, OVERLAY_IMAGE_BIND_PATH)?;
Ok(())
}
async fn mount_overlay_to_new_root(&mut self) -> Result<()> {
fs::create_dir(NEW_ROOT_PATH).await?;
Mount::builder()
.fstype(FilesystemType::Manual("overlay"))
.flags(MountFlags::NOATIME)
.data(&format!(
"lowerdir={},upperdir={},workdir={}",
OVERLAY_IMAGE_BIND_PATH, OVERLAY_UPPER_PATH, OVERLAY_WORK_PATH
))
.mount(format!("overlayfs:{}", OVERLAY_MOUNT_PATH), NEW_ROOT_PATH)?;
Ok(())
}
async fn parse_image_config(&mut self) -> Result<ImageConfiguration> {
let image_config_path = Path::new(IMAGE_CONFIG_JSON_PATH);
let content = fs::read_to_string(image_config_path).await?;
let config = serde_json::from_str(&content)?;
Ok(config)
}
async fn parse_launch_config(&mut self) -> Result<LaunchInfo> {
trace!("parsing launch config");
let launch_config = Path::new(LAUNCH_CONFIG_JSON_PATH);
let content = fs::read_to_string(launch_config).await?;
Ok(serde_json::from_str(&content)?)
}
async fn nuke_initrd(&mut self) -> Result<()> {
trace!("nuking initrd");
let initrd_dev = fs::metadata("/").await?.st_dev();
for item in WalkDir::new("/")
.same_file_system(true)
.follow_links(false)
.contents_first(true)
{
if item.is_err() {
continue;
}
let item = item?;
let metadata = match item.metadata() {
Ok(value) => value,
Err(_) => continue,
};
if metadata.st_dev() != initrd_dev {
continue;
}
if metadata.is_symlink() || metadata.is_file() {
let _ = fs::remove_file(item.path()).await;
trace!("deleting file {:?}", item.path());
} else if metadata.is_dir() {
let _ = fs::remove_dir(item.path()).await;
trace!("deleting directory {:?}", item.path());
}
}
trace!("nuked initrd");
Ok(())
}
async fn bind_new_root(&mut self) -> Result<()> {
self.mount_move_subtree(Path::new(SYS_PATH), Path::new(NEW_ROOT_SYS_PATH))
.await?;
self.mount_move_subtree(Path::new(PROC_PATH), Path::new(NEW_ROOT_PROC_PATH))
.await?;
self.mount_move_subtree(Path::new(DEV_PATH), Path::new(NEW_ROOT_DEV_PATH))
.await?;
trace!("binding new root");
Mount::builder()
.fstype(FilesystemType::Manual("none"))
.flags(MountFlags::BIND)
.mount(".", "/")?;
trace!("chrooting into new root");
chroot(".")?;
trace!("setting root as current directory");
std::env::set_current_dir("/")?;
Ok(())
}
async fn network_setup(&mut self, network: &LaunchNetwork) -> Result<()> {
trace!("setting up network for link");
let etc = PathBuf::from_str("/etc")?;
if !etc.exists() {
fs::create_dir(etc).await?;
}
let resolv = PathBuf::from_str("/etc/resolv.conf")?;
let mut lines = vec!["# krata resolver configuration".to_string()];
for nameserver in &network.resolver.nameservers {
lines.push(format!("nameserver {}", nameserver));
}
let mut conf = lines.join("\n");
conf.push('\n');
fs::write(resolv, conf).await?;
self.network_configure_ethtool(network).await?;
self.network_configure_link(network).await?;
Ok(())
}
async fn network_configure_link(&mut self, network: &LaunchNetwork) -> Result<()> {
let (connection, handle, _) = rtnetlink::new_connection()?;
tokio::spawn(connection);
let mut links = handle.link().get().match_name("lo".to_string()).execute();
let Some(link) = links.try_next().await? else {
warn!("unable to find link named lo");
return Ok(());
};
handle.link().set(link.header.index).up().execute().await?;
let ipv4_network: IpNetwork = network.ipv4.address.parse()?;
let ipv4_gateway: Ipv4Addr = network.ipv4.gateway.parse()?;
let ipv6_network: IpNetwork = network.ipv6.address.parse()?;
let ipv6_gateway: Ipv6Addr = network.ipv6.gateway.parse()?;
let mut links = handle
.link()
.get()
.match_name(network.link.clone())
.execute();
let Some(link) = links.try_next().await? else {
warn!("unable to find link named {}", network.link);
return Ok(());
};
handle
.address()
.add(link.header.index, ipv4_network.ip(), ipv4_network.prefix())
.execute()
.await?;
let ipv6_result = handle
.address()
.add(link.header.index, ipv6_network.ip(), ipv6_network.prefix())
.execute()
.await;
let ipv6_ready = match ipv6_result {
Ok(()) => true,
Err(error) => {
warn!("unable to setup ipv6 network: {}", error);
false
}
};
handle.link().set(link.header.index).up().execute().await?;
handle
.route()
.add()
.v4()
.destination_prefix(Ipv4Addr::UNSPECIFIED, 0)
.output_interface(link.header.index)
.gateway(ipv4_gateway)
.execute()
.await?;
if ipv6_ready {
let ipv6_gw_result = handle
.route()
.add()
.v6()
.destination_prefix(Ipv6Addr::UNSPECIFIED, 0)
.output_interface(link.header.index)
.gateway(ipv6_gateway)
.execute()
.await;
if let Err(error) = ipv6_gw_result {
warn!("failed to add ipv6 gateway route: {}", error);
}
}
Ok(())
}
async fn network_configure_ethtool(&mut self, network: &LaunchNetwork) -> Result<()> {
let mut handle = EthtoolHandle::new()?;
handle.set_gso(&network.link, false)?;
handle.set_tso(&network.link, false)?;
Ok(())
}
async fn run(&mut self, config: &Config, launch: &LaunchInfo, idm: IdmClient) -> Result<()> {
let mut cmd = match config.cmd() {
None => vec![],
Some(value) => value.clone(),
};
if launch.run.is_some() {
cmd.clone_from(launch.run.as_ref().unwrap());
}
if let Some(entrypoint) = config.entrypoint() {
for item in entrypoint.iter().rev() {
cmd.insert(0, item.to_string());
}
}
if cmd.is_empty() {
cmd.push("/bin/sh".to_string());
}
let path = cmd.remove(0);
let mut env = HashMap::new();
if let Some(config_env) = config.env() {
env.extend(GuestInit::env_map(config_env));
}
env.extend(launch.env.clone());
env.insert("KRATA_CONTAINER".to_string(), "1".to_string());
env.insert("TERM".to_string(), "vt100".to_string());
let path = GuestInit::resolve_executable(&env, path.into())?;
let Some(file_name) = path.file_name() else {
return Err(anyhow!("cannot get file name of command path"));
};
let Some(file_name) = file_name.to_str() else {
return Err(anyhow!("cannot get file name of command path as str"));
};
cmd.insert(0, file_name.to_string());
let env = GuestInit::env_list(env);
trace!("running guest command: {}", cmd.join(" "));
let path = CString::new(path.as_os_str().as_bytes())?;
let cmd = GuestInit::strings_as_cstrings(cmd)?;
let env = GuestInit::strings_as_cstrings(env)?;
let mut working_dir = config
.working_dir()
.as_ref()
.map(|x| x.to_string())
.unwrap_or("/".to_string());
if working_dir.is_empty() {
working_dir = "/".to_string();
}
let cgroup = self.init_cgroup().await?;
self.fork_and_exec(idm, cgroup, working_dir, path, cmd, env)
.await?;
Ok(())
}
async fn init_cgroup(&self) -> Result<Cgroup> {
trace!("initializing cgroup");
let hierarchy = cgroups_rs::hierarchies::auto();
let cgroup = Cgroup::new(hierarchy, "krata-guest-task")?;
cgroup.set_cgroup_type("threaded")?;
trace!("initialized cgroup");
Ok(cgroup)
}
fn strings_as_cstrings(values: Vec<String>) -> Result<Vec<CString>> {
let mut results: Vec<CString> = vec![];
for value in values {
results.push(CString::new(value.as_bytes().to_vec())?);
}
Ok(results)
}
fn env_map(env: &[String]) -> HashMap<String, String> {
let mut map = HashMap::<String, String>::new();
for item in env {
if let Some((key, value)) = item.split_once('=') {
map.insert(key.to_string(), value.to_string());
}
}
map
}
fn resolve_executable(env: &HashMap<String, String>, path: PathBuf) -> Result<PathBuf> {
if path.is_absolute() {
return Ok(path);
}
if path.is_file() {
return Ok(path.absolutize()?.to_path_buf());
}
if let Some(path_var) = env.get("PATH") {
for item in path_var.split(':') {
let mut exe_path: PathBuf = item.into();
exe_path.push(&path);
if exe_path.is_file() {
return Ok(exe_path);
}
}
}
Ok(path)
}
fn env_list(env: HashMap<String, String>) -> Vec<String> {
env.iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<String>>()
}
async fn fork_and_exec(
&mut self,
idm: IdmClient,
cgroup: Cgroup,
working_dir: String,
path: CString,
cmd: Vec<CString>,
env: Vec<CString>,
) -> Result<()> {
match unsafe { fork()? } {
ForkResult::Parent { child } => self.background(idm, cgroup, child).await,
ForkResult::Child => self.foreground(cgroup, working_dir, path, cmd, env).await,
}
}
async fn foreground(
&mut self,
cgroup: Cgroup,
working_dir: String,
path: CString,
cmd: Vec<CString>,
env: Vec<CString>,
) -> Result<()> {
GuestInit::set_controlling_terminal()?;
std::env::set_current_dir(working_dir)?;
cgroup.add_task(CgroupPid::from(std::process::id() as u64))?;
execve(&path, &cmd, &env)?;
Ok(())
}
fn set_controlling_terminal() -> Result<()> {
unsafe {
setsid();
set_controlling_terminal(io::stdin().as_raw_fd(), 0)?;
}
Ok(())
}
async fn background(&mut self, idm: IdmClient, cgroup: Cgroup, executed: Pid) -> Result<()> {
let mut background = GuestBackground::new(idm, cgroup, executed).await?;
background.run().await?;
Ok(())
}
}

View File

@ -1,20 +0,0 @@
use std::{os::raw::c_int, time::Duration};
use anyhow::Result;
use tokio::time::sleep;
use xenstore::{XsdClient, XsdInterface};
pub mod background;
pub mod childwait;
pub mod init;
pub async fn death(code: c_int) -> Result<()> {
let store = XsdClient::open().await?;
store
.write_string("krata/guest/exit-code", &code.to_string())
.await?;
drop(store);
loop {
sleep(Duration::from_secs(1)).await;
}
}

View File

@ -1,39 +0,0 @@
[package]
name = "krata"
description = "Client library and common services for the krata hypervisor."
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
anyhow = { workspace = true }
bytes = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
once_cell = { workspace = true }
prost = { workspace = true }
prost-reflect = { workspace = true }
serde = { workspace = true }
tonic = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tower = { workspace = true }
url = { workspace = true }
[target.'cfg(unix)'.dependencies]
nix = { workspace = true, features = ["term"] }
[build-dependencies]
tonic-build = { workspace = true }
prost-build = { workspace = true }
prost-reflect-build = { workspace = true }
[lib]
name = "krata"
[[example]]
name = "ethtool"
path = "examples/ethtool.rs"

View File

@ -1,24 +0,0 @@
use std::io::Result;
fn main() -> Result<()> {
let mut config = prost_build::Config::new();
prost_reflect_build::Builder::new()
.descriptor_pool("crate::DESCRIPTOR_POOL")
.configure(
&mut config,
&[
"proto/krata/v1/control.proto",
"proto/krata/internal/idm.proto",
],
&["proto/"],
)?;
tonic_build::configure().compile_with_config(
config,
&[
"proto/krata/v1/control.proto",
"proto/krata/internal/idm.proto",
],
&["proto/"],
)?;
Ok(())
}

View File

@ -1,13 +0,0 @@
use std::env;
use anyhow::Result;
use krata::ethtool::EthtoolHandle;
fn main() -> Result<()> {
let args = env::args().collect::<Vec<String>>();
let interface = args.get(1).unwrap();
let mut handle = EthtoolHandle::new()?;
handle.set_gso(interface, false)?;
handle.set_tso(interface, false)?;
Ok(())
}

View File

@ -1,21 +0,0 @@
syntax = "proto3";
package krata.internal.idm;
option java_multiple_files = true;
option java_package = "dev.krata.proto.internal.idm";
option java_outer_classname = "IdmProto";
message IdmExitEvent {
int32 code = 1;
}
message IdmEvent {
oneof event {
IdmExitEvent exit = 1;
}
}
message IdmPacket {
IdmEvent event = 1;
}

View File

@ -1,82 +0,0 @@
syntax = "proto3";
package krata.v1.common;
option java_multiple_files = true;
option java_package = "dev.krata.proto.v1.common";
option java_outer_classname = "CommonProto";
message Guest {
string id = 1;
GuestSpec spec = 2;
GuestState state = 3;
}
message GuestSpec {
string name = 1;
GuestImageSpec image = 2;
uint32 vcpus = 3;
uint64 mem = 4;
GuestTaskSpec task = 5;
repeated GuestSpecAnnotation annotations = 6;
}
message GuestImageSpec {
oneof image {
GuestOciImageSpec oci = 1;
}
}
message GuestOciImageSpec {
string image = 1;
}
message GuestTaskSpec {
repeated GuestTaskSpecEnvVar environment = 1;
repeated string command = 2;
}
message GuestTaskSpecEnvVar {
string key = 1;
string value = 2;
}
message GuestSpecAnnotation {
string key = 1;
string value = 2;
}
message GuestState {
GuestStatus status = 1;
GuestNetworkState network = 2;
GuestExitInfo exit_info = 3;
GuestErrorInfo error_info = 4;
uint32 domid = 5;
}
enum GuestStatus {
GUEST_STATUS_UNKNOWN = 0;
GUEST_STATUS_STARTING = 1;
GUEST_STATUS_STARTED = 2;
GUEST_STATUS_EXITED = 3;
GUEST_STATUS_DESTROYING = 4;
GUEST_STATUS_DESTROYED = 5;
GUEST_STATUS_FAILED = 6;
}
message GuestNetworkState {
string guest_ipv4 = 1;
string guest_ipv6 = 2;
string guest_mac = 3;
string gateway_ipv4 = 4;
string gateway_ipv6 = 5;
string gateway_mac = 6;
}
message GuestExitInfo {
int32 code = 1;
}
message GuestErrorInfo {
string message = 1;
}

View File

@ -1,67 +0,0 @@
syntax = "proto3";
package krata.v1.control;
option java_multiple_files = true;
option java_package = "dev.krata.proto.v1.control";
option java_outer_classname = "ControlProto";
import "krata/v1/common.proto";
service ControlService {
rpc CreateGuest(CreateGuestRequest) returns (CreateGuestReply);
rpc DestroyGuest(DestroyGuestRequest) returns (DestroyGuestReply);
rpc ResolveGuest(ResolveGuestRequest) returns (ResolveGuestReply);
rpc ListGuests(ListGuestsRequest) returns (ListGuestsReply);
rpc ConsoleData(stream ConsoleDataRequest) returns (stream ConsoleDataReply);
rpc WatchEvents(WatchEventsRequest) returns (stream WatchEventsReply);
}
message CreateGuestRequest {
krata.v1.common.GuestSpec spec = 1;
}
message CreateGuestReply {
string guest_id = 1;
}
message DestroyGuestRequest {
string guest_id = 1;
}
message DestroyGuestReply {}
message ResolveGuestRequest {
string name = 1;
}
message ResolveGuestReply {
krata.v1.common.Guest guest = 1;
}
message ListGuestsRequest {}
message ListGuestsReply {
repeated krata.v1.common.Guest guests = 1;
}
message ConsoleDataRequest {
string guest_id = 1;
bytes data = 2;
}
message ConsoleDataReply {
bytes data = 1;
}
message WatchEventsRequest {}
message WatchEventsReply {
oneof event {
GuestChangedEvent guest_changed = 1;
}
}
message GuestChangedEvent {
krata.v1.common.Guest guest = 1;
}

View File

@ -1,61 +0,0 @@
use crate::{dial::ControlDialAddress, v1::control::control_service_client::ControlServiceClient};
#[cfg(not(unix))]
use anyhow::anyhow;
use anyhow::Result;
#[cfg(unix)]
use tokio::net::UnixStream;
#[cfg(unix)]
use tonic::transport::Uri;
use tonic::transport::{Channel, ClientTlsConfig, Endpoint};
#[cfg(unix)]
use tower::service_fn;
pub struct ControlClientProvider {}
impl ControlClientProvider {
pub async fn dial(addr: ControlDialAddress) -> Result<ControlServiceClient<Channel>> {
let channel = match addr {
ControlDialAddress::UnixSocket { path } => {
#[cfg(not(unix))]
return Err(anyhow!(
"unix sockets are not supported on this platform (path {})",
path
));
#[cfg(unix)]
ControlClientProvider::dial_unix_socket(path).await?
}
ControlDialAddress::Tcp { host, port } => {
Endpoint::try_from(format!("http://{}:{}", host, port))?
.connect()
.await?
}
ControlDialAddress::Tls {
host,
port,
insecure: _,
} => {
let tls_config = ClientTlsConfig::new().domain_name(&host);
let address = format!("https://{}:{}", host, port);
Channel::from_shared(address)?
.tls_config(tls_config)?
.connect()
.await?
}
};
Ok(ControlServiceClient::new(channel))
}
#[cfg(unix)]
async fn dial_unix_socket(path: String) -> Result<Channel> {
// This URL is not actually used but is required to be specified.
Ok(Endpoint::try_from(format!("unix://localhost/{}", path))?
.connect_with_connector(service_fn(|uri: Uri| {
let path = uri.path().to_string();
UnixStream::connect(path)
}))
.await?)
}
}

View File

@ -1,100 +0,0 @@
use std::{fmt::Display, str::FromStr};
use anyhow::anyhow;
use url::{Host, Url};
pub const KRATA_DEFAULT_TCP_PORT: u16 = 4350;
pub const KRATA_DEFAULT_TLS_PORT: u16 = 4353;
#[derive(Clone)]
pub enum ControlDialAddress {
UnixSocket {
path: String,
},
Tcp {
host: String,
port: u16,
},
Tls {
host: String,
port: u16,
insecure: bool,
},
}
impl FromStr for ControlDialAddress {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let url: Url = s.parse()?;
let host = url.host().unwrap_or(Host::Domain("localhost")).to_string();
match url.scheme() {
"unix" => Ok(ControlDialAddress::UnixSocket {
path: url.path().to_string(),
}),
"tcp" => {
let port = url.port().unwrap_or(KRATA_DEFAULT_TCP_PORT);
Ok(ControlDialAddress::Tcp { host, port })
}
"tls" | "tls-insecure" => {
let insecure = url.scheme() == "tls-insecure";
let port = url.port().unwrap_or(KRATA_DEFAULT_TLS_PORT);
Ok(ControlDialAddress::Tls {
host,
port,
insecure,
})
}
_ => Err(anyhow!("unknown control address scheme: {}", url.scheme())),
}
}
}
impl From<ControlDialAddress> for Url {
fn from(val: ControlDialAddress) -> Self {
match val {
ControlDialAddress::UnixSocket { path } => {
let mut url = Url::parse("unix:///").unwrap();
url.set_path(&path);
url
}
ControlDialAddress::Tcp { host, port } => {
let mut url = Url::parse("tcp://").unwrap();
url.set_host(Some(&host)).unwrap();
if port != KRATA_DEFAULT_TCP_PORT {
url.set_port(Some(port)).unwrap();
}
url
}
ControlDialAddress::Tls {
host,
port,
insecure,
} => {
let mut url = Url::parse("tls://").unwrap();
if insecure {
url.set_scheme("tls-insecure").unwrap();
}
url.set_host(Some(&host)).unwrap();
if port != KRATA_DEFAULT_TLS_PORT {
url.set_port(Some(port)).unwrap();
}
url
}
}
}
}
impl Display for ControlDialAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let url: Url = self.clone().into();
write!(f, "{}", url)
}
}

View File

@ -1,81 +0,0 @@
use std::{
os::fd::{AsRawFd, FromRawFd, OwnedFd},
ptr::addr_of_mut,
};
use anyhow::Result;
use libc::{ioctl, socket, AF_INET, SOCK_DGRAM};
#[repr(C)]
struct EthtoolValue {
cmd: u32,
data: u32,
}
const ETHTOOL_SGSO: u32 = 0x00000024;
const ETHTOOL_STSO: u32 = 0x0000001f;
#[cfg(not(target_env = "musl"))]
const SIOCETHTOOL: libc::c_ulong = libc::SIOCETHTOOL;
#[cfg(target_env = "musl")]
const SIOCETHTOOL: libc::c_int = libc::SIOCETHTOOL as i32;
#[repr(C)]
#[derive(Debug)]
struct EthtoolIfreq {
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
ifr_data: libc::uintptr_t,
}
impl EthtoolIfreq {
fn new(interface: &str) -> EthtoolIfreq {
let mut ifreq = EthtoolIfreq {
ifr_name: [0; libc::IF_NAMESIZE],
ifr_data: 0,
};
for (i, byte) in interface.as_bytes().iter().enumerate() {
ifreq.ifr_name[i] = *byte as libc::c_char
}
ifreq
}
fn set_value(&mut self, ptr: *mut libc::c_void) {
self.ifr_data = ptr as libc::uintptr_t;
}
}
pub struct EthtoolHandle {
fd: OwnedFd,
}
impl EthtoolHandle {
pub fn new() -> Result<EthtoolHandle> {
let fd = unsafe { socket(AF_INET, SOCK_DGRAM, 0) };
if fd == -1 {
return Err(std::io::Error::last_os_error().into());
}
Ok(EthtoolHandle {
fd: unsafe { OwnedFd::from_raw_fd(fd) },
})
}
pub fn set_gso(&mut self, interface: &str, value: bool) -> Result<()> {
self.set_value(interface, ETHTOOL_SGSO, if value { 1 } else { 0 })
}
pub fn set_tso(&mut self, interface: &str, value: bool) -> Result<()> {
self.set_value(interface, ETHTOOL_STSO, if value { 1 } else { 0 })
}
fn set_value(&mut self, interface: &str, cmd: u32, value: u32) -> Result<()> {
let mut ifreq = EthtoolIfreq::new(interface);
let mut value = EthtoolValue { cmd, data: value };
ifreq.set_value(addr_of_mut!(value) as *mut libc::c_void);
let result = unsafe { ioctl(self.fd.as_raw_fd(), SIOCETHTOOL, addr_of_mut!(ifreq) as u64) };
if result == -1 {
return Err(std::io::Error::last_os_error().into());
}
Ok(())
}
}

View File

@ -1,87 +0,0 @@
use std::{sync::Arc, time::Duration};
use crate::v1::control::{
control_service_client::ControlServiceClient, watch_events_reply::Event, WatchEventsReply,
WatchEventsRequest,
};
use anyhow::Result;
use log::{error, trace, warn};
use tokio::{sync::broadcast, task::JoinHandle, time::sleep};
use tokio_stream::StreamExt;
use tonic::{transport::Channel, Streaming};
#[derive(Clone)]
pub struct EventStream {
sender: Arc<broadcast::Sender<Event>>,
task: Arc<JoinHandle<()>>,
}
impl EventStream {
pub async fn open(client: ControlServiceClient<Channel>) -> Result<Self> {
let (sender, _) = broadcast::channel(1000);
let emit = sender.clone();
let task = tokio::task::spawn(async move {
if let Err(error) = EventStream::process(client, emit).await {
error!("failed to process event stream: {}", error);
}
});
Ok(Self {
sender: Arc::new(sender),
task: Arc::new(task),
})
}
async fn process(
mut client: ControlServiceClient<Channel>,
emit: broadcast::Sender<Event>,
) -> Result<()> {
let mut events: Option<Streaming<WatchEventsReply>> = None;
loop {
let mut stream = match events {
Some(stream) => stream,
None => {
let result = client.watch_events(WatchEventsRequest {}).await;
if let Err(error) = result {
warn!("failed to watch events: {}", error);
sleep(Duration::from_secs(1)).await;
continue;
}
result.unwrap().into_inner()
}
};
let Some(result) = stream.next().await else {
events = None;
continue;
};
let reply = match result {
Ok(reply) => reply,
Err(error) => {
trace!("event stream processing failed: {}", error);
events = None;
continue;
}
};
let Some(event) = reply.event else {
events = Some(stream);
continue;
};
let _ = emit.send(event);
events = Some(stream);
}
}
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
self.sender.subscribe()
}
}
impl Drop for EventStream {
fn drop(&mut self) {
if Arc::strong_count(&self.task) <= 1 {
self.task.abort();
}
}
}

View File

@ -1,111 +0,0 @@
use std::path::Path;
use super::protocol::IdmPacket;
use anyhow::{anyhow, Result};
use bytes::BytesMut;
use log::{debug, error};
use nix::sys::termios::{cfmakeraw, tcgetattr, tcsetattr, SetArg};
use prost::Message;
use tokio::{
fs::File,
io::{unix::AsyncFd, AsyncReadExt, AsyncWriteExt},
select,
sync::mpsc::{channel, Receiver, Sender},
task::JoinHandle,
};
const IDM_PACKET_QUEUE_LEN: usize = 100;
pub struct IdmClient {
pub receiver: Receiver<IdmPacket>,
pub sender: Sender<IdmPacket>,
task: JoinHandle<()>,
}
impl Drop for IdmClient {
fn drop(&mut self) {
self.task.abort();
}
}
impl IdmClient {
pub async fn open<P: AsRef<Path>>(path: P) -> Result<IdmClient> {
let file = File::options()
.read(true)
.write(true)
.create(false)
.open(path)
.await?;
IdmClient::set_raw_port(&file)?;
let (rx_sender, rx_receiver) = channel(IDM_PACKET_QUEUE_LEN);
let (tx_sender, tx_receiver) = channel(IDM_PACKET_QUEUE_LEN);
let task = tokio::task::spawn(async move {
if let Err(error) = IdmClient::process(file, rx_sender, tx_receiver).await {
debug!("failed to handle idm client processing: {}", error);
}
});
Ok(IdmClient {
receiver: rx_receiver,
sender: tx_sender,
task,
})
}
fn set_raw_port(file: &File) -> Result<()> {
let mut termios = tcgetattr(file)?;
cfmakeraw(&mut termios);
tcsetattr(file, SetArg::TCSANOW, &termios)?;
Ok(())
}
async fn process(
file: File,
sender: Sender<IdmPacket>,
mut receiver: Receiver<IdmPacket>,
) -> Result<()> {
let mut file = AsyncFd::new(file)?;
loop {
select! {
x = file.readable_mut() => match x {
Ok(mut guard) => {
let size = guard.get_inner_mut().read_u16_le().await?;
if size == 0 {
continue;
}
let mut buffer = BytesMut::with_capacity(size as usize);
guard.get_inner_mut().read_exact(&mut buffer).await?;
match IdmPacket::decode(buffer) {
Ok(packet) => {
sender.send(packet).await?;
},
Err(error) => {
error!("received invalid idm packet: {}", error);
}
}
},
Err(error) => {
return Err(anyhow!("failed to read idm client: {}", error));
}
},
x = receiver.recv() => match x {
Some(packet) => {
let data = packet.encode_to_vec();
if data.len() > u16::MAX as usize {
error!("unable to send idm packet, packet size exceeded (tried to send {} bytes)", data.len());
continue;
}
file.get_mut().write_u16_le(data.len() as u16).await?;
file.get_mut().write_all(&data).await?;
},
None => {
break;
}
}
}
}
Ok(())
}
}

View File

@ -1,3 +0,0 @@
#[cfg(unix)]
pub mod client;
pub mod protocol;

View File

@ -1 +0,0 @@
include!(concat!(env!("OUT_DIR"), "/krata.internal.idm.rs"));

View File

@ -1,36 +0,0 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct LaunchNetworkIpv4 {
pub address: String,
pub gateway: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LaunchNetworkIpv6 {
pub address: String,
pub gateway: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LaunchNetworkResolver {
pub nameservers: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LaunchNetwork {
pub link: String,
pub ipv4: LaunchNetworkIpv4,
pub ipv6: LaunchNetworkIpv6,
pub resolver: LaunchNetworkResolver,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LaunchInfo {
pub hostname: Option<String>,
pub network: Option<LaunchNetwork>,
pub env: HashMap<String, String>,
pub run: Option<Vec<String>>,
}

View File

@ -1,20 +0,0 @@
use once_cell::sync::Lazy;
use prost_reflect::DescriptorPool;
pub mod v1;
pub mod client;
pub mod dial;
pub mod events;
pub mod idm;
pub mod launchcfg;
#[cfg(target_os = "linux")]
pub mod ethtool;
pub static DESCRIPTOR_POOL: Lazy<DescriptorPool> = Lazy::new(|| {
DescriptorPool::decode(
include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")).as_ref(),
)
.unwrap()
});

View File

@ -1 +0,0 @@
tonic::include_proto!("krata.v1.common");

View File

@ -1 +0,0 @@
tonic::include_proto!("krata.v1.control");

View File

@ -1,2 +0,0 @@
pub mod common;
pub mod control;

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,40 +0,0 @@
[package]
name = "krata-network"
description = "Networking services for the krata hypervisor."
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
bytes = { workspace = true }
clap = { workspace = true }
env_logger = { workspace = true }
etherparse = { workspace = true }
futures = { workspace = true }
krata = { path = "../krata", version = "^0.0.7" }
krata-advmac = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
rtnetlink = { workspace = true }
smoltcp = { workspace = true }
tonic = { workspace = true }
tokio = { workspace = true }
tokio-tun = { workspace = true }
udp-stream = { workspace = true }
uuid = { workspace = true }
[lib]
name = "kratanet"
[[bin]]
name = "kratanet"
path = "bin/network.rs"
[[example]]
name = "ping"
path = "examples/ping.rs"

View File

@ -1,22 +0,0 @@
use std::str::FromStr;
use anyhow::Result;
use clap::Parser;
use env_logger::Env;
use krata::dial::ControlDialAddress;
use kratanet::NetworkService;
#[derive(Parser, Debug)]
struct NetworkArgs {
#[arg(short, long, default_value = "unix:///var/lib/krata/daemon.socket")]
connection: String,
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let args = NetworkArgs::parse();
let control_dial_address = ControlDialAddress::from_str(&args.connection)?;
let mut service = NetworkService::new(control_dial_address).await?;
service.watch().await
}

View File

@ -1,21 +0,0 @@
use std::{net::Ipv6Addr, str::FromStr, time::Duration};
use anyhow::Result;
use kratanet::icmp::{IcmpClient, IcmpProtocol};
#[tokio::main]
async fn main() -> Result<()> {
let client = IcmpClient::new(IcmpProtocol::Icmpv6)?;
let payload: [u8; 4] = [12u8, 14u8, 16u8, 32u8];
let result = client
.ping6(
Ipv6Addr::from_str("2606:4700:4700::1111")?,
0,
1,
&payload,
Duration::from_secs(10),
)
.await?;
println!("reply: {:?}", result);
Ok(())
}

View File

@ -1,198 +0,0 @@
use anyhow::Result;
use krata::{
events::EventStream,
v1::{
common::Guest,
control::{
control_service_client::ControlServiceClient, watch_events_reply::Event,
ListGuestsRequest,
},
},
};
use log::warn;
use smoltcp::wire::{EthernetAddress, Ipv4Cidr, Ipv6Cidr};
use std::{collections::HashMap, str::FromStr, time::Duration};
use tokio::{select, sync::broadcast::Receiver, time::sleep};
use tonic::transport::Channel;
use uuid::Uuid;
pub struct AutoNetworkWatcher {
control: ControlServiceClient<Channel>,
pub events: EventStream,
known: HashMap<Uuid, NetworkMetadata>,
}
#[derive(Debug, Clone)]
pub struct NetworkSide {
pub ipv4: Ipv4Cidr,
pub ipv6: Ipv6Cidr,
pub mac: EthernetAddress,
}
#[derive(Debug, Clone)]
pub struct NetworkMetadata {
pub domid: u32,
pub uuid: Uuid,
pub guest: NetworkSide,
pub gateway: NetworkSide,
}
impl NetworkMetadata {
pub fn interface(&self) -> String {
format!("vif{}.20", self.domid)
}
}
#[derive(Debug, Clone)]
pub struct AutoNetworkChangeset {
pub added: Vec<NetworkMetadata>,
pub removed: Vec<NetworkMetadata>,
}
impl AutoNetworkWatcher {
pub async fn new(control: ControlServiceClient<Channel>) -> Result<AutoNetworkWatcher> {
let client = control.clone();
Ok(AutoNetworkWatcher {
control,
events: EventStream::open(client).await?,
known: HashMap::new(),
})
}
pub async fn read(&mut self) -> Result<Vec<NetworkMetadata>> {
let mut all_guests: HashMap<Uuid, Guest> = HashMap::new();
for guest in self
.control
.list_guests(ListGuestsRequest {})
.await?
.into_inner()
.guests
{
let Ok(uuid) = Uuid::from_str(&guest.id) else {
continue;
};
all_guests.insert(uuid, guest);
}
let mut networks: Vec<NetworkMetadata> = Vec::new();
for (uuid, guest) in &all_guests {
let Some(ref state) = guest.state else {
continue;
};
if state.domid == u32::MAX {
continue;
}
let Some(ref network) = state.network else {
continue;
};
let Ok(guest_ipv4_cidr) = Ipv4Cidr::from_str(&network.guest_ipv4) else {
continue;
};
let Ok(guest_ipv6_cidr) = Ipv6Cidr::from_str(&network.guest_ipv6) else {
continue;
};
let Ok(guest_mac) = EthernetAddress::from_str(&network.guest_mac) else {
continue;
};
let Ok(gateway_ipv4_cidr) = Ipv4Cidr::from_str(&network.gateway_ipv4) else {
continue;
};
let Ok(gateway_ipv6_cidr) = Ipv6Cidr::from_str(&network.gateway_ipv6) else {
continue;
};
let Ok(gateway_mac) = EthernetAddress::from_str(&network.gateway_mac) else {
continue;
};
networks.push(NetworkMetadata {
domid: state.domid,
uuid: *uuid,
guest: NetworkSide {
ipv4: guest_ipv4_cidr,
ipv6: guest_ipv6_cidr,
mac: guest_mac,
},
gateway: NetworkSide {
ipv4: gateway_ipv4_cidr,
ipv6: gateway_ipv6_cidr,
mac: gateway_mac,
},
});
}
Ok(networks)
}
pub async fn read_changes(&mut self) -> Result<AutoNetworkChangeset> {
let mut seen: Vec<Uuid> = Vec::new();
let mut added: Vec<NetworkMetadata> = Vec::new();
let mut removed: Vec<NetworkMetadata> = Vec::new();
let networks = match self.read().await {
Ok(networks) => networks,
Err(error) => {
warn!("failed to read network changes: {}", error);
return Ok(AutoNetworkChangeset { added, removed });
}
};
for network in networks {
seen.push(network.uuid);
if self.known.contains_key(&network.uuid) {
continue;
}
let _ = self.known.insert(network.uuid, network.clone());
added.push(network);
}
let mut gone: Vec<Uuid> = Vec::new();
for uuid in self.known.keys() {
if seen.contains(uuid) {
continue;
}
gone.push(*uuid);
}
for uuid in &gone {
let Some(network) = self.known.remove(uuid) else {
continue;
};
removed.push(network);
}
Ok(AutoNetworkChangeset { added, removed })
}
pub async fn wait(&mut self, receiver: &mut Receiver<Event>) -> Result<()> {
loop {
select! {
x = receiver.recv() => match x {
Ok(Event::GuestChanged(_)) => {
break;
},
Err(error) => {
warn!("failed to receive event: {}", error);
}
},
_ = sleep(Duration::from_secs(10)) => {
break;
}
};
}
Ok(())
}
pub fn mark_unknown(&mut self, uuid: Uuid) -> Result<bool> {
Ok(self.known.remove(&uuid).is_some())
}
}

View File

@ -1,176 +0,0 @@
use crate::autonet::NetworkMetadata;
use crate::chandev::ChannelDevice;
use crate::nat::Nat;
use crate::proxynat::ProxyNatHandlerFactory;
use crate::raw_socket::{AsyncRawSocketChannel, RawSocketHandle, RawSocketProtocol};
use crate::vbridge::{BridgeJoinHandle, VirtualBridge};
use crate::EXTRA_MTU;
use anyhow::{anyhow, Result};
use bytes::BytesMut;
use futures::TryStreamExt;
use log::{info, trace, warn};
use smoltcp::iface::{Config, Interface, SocketSet};
use smoltcp::phy::Medium;
use smoltcp::time::Instant;
use smoltcp::wire::{HardwareAddress, IpCidr};
use tokio::select;
use tokio::sync::mpsc::{channel, Receiver};
use tokio::task::JoinHandle;
const TX_CHANNEL_BUFFER_LEN: usize = 3000;
#[derive(Clone)]
pub struct NetworkBackend {
metadata: NetworkMetadata,
bridge: VirtualBridge,
}
#[derive(Debug)]
enum NetworkStackSelect {
Receive(Option<BytesMut>),
Send(Option<BytesMut>),
}
struct NetworkStack<'a> {
tx: Receiver<BytesMut>,
kdev: AsyncRawSocketChannel,
udev: ChannelDevice,
interface: Interface,
sockets: SocketSet<'a>,
nat: Nat,
bridge: BridgeJoinHandle,
}
impl NetworkStack<'_> {
async fn poll(&mut self) -> Result<bool> {
let what = select! {
biased;
x = self.kdev.receiver.recv() => NetworkStackSelect::Receive(x),
x = self.tx.recv() => NetworkStackSelect::Send(x),
x = self.bridge.from_bridge_receiver.recv() => NetworkStackSelect::Send(x),
x = self.bridge.from_broadcast_receiver.recv() => NetworkStackSelect::Send(x.ok()),
};
match what {
NetworkStackSelect::Receive(Some(packet)) => {
if let Err(error) = self.bridge.to_bridge_sender.try_send(packet.clone()) {
trace!("failed to send guest packet to bridge: {}", error);
}
if let Err(error) = self.nat.receive_sender.try_send(packet.clone()) {
trace!("failed to send guest packet to nat: {}", error);
}
self.udev.rx = Some(packet);
self.interface
.poll(Instant::now(), &mut self.udev, &mut self.sockets);
}
NetworkStackSelect::Send(Some(packet)) => {
if let Err(error) = self.kdev.sender.try_send(packet) {
warn!("failed to transmit packet to interface: {}", error);
}
}
NetworkStackSelect::Receive(None) | NetworkStackSelect::Send(None) => {
return Ok(false);
}
}
Ok(true)
}
}
impl NetworkBackend {
pub fn new(metadata: NetworkMetadata, bridge: VirtualBridge) -> Result<Self> {
Ok(Self { metadata, bridge })
}
pub async fn init(&mut self) -> Result<()> {
let interface = self.metadata.interface();
let (connection, handle, _) = rtnetlink::new_connection()?;
tokio::spawn(connection);
let mut links = handle.link().get().match_name(interface.clone()).execute();
let link = links.try_next().await?;
if link.is_none() {
return Err(anyhow!(
"unable to find network interface named {}",
interface
));
}
let link = link.unwrap();
handle.link().set(link.header.index).up().execute().await?;
Ok(())
}
pub async fn run(&self) -> Result<()> {
let mut stack = self.create_network_stack().await?;
loop {
if !stack.poll().await? {
break;
}
}
Ok(())
}
async fn create_network_stack(&self) -> Result<NetworkStack> {
let interface = self.metadata.interface();
let proxy = Box::new(ProxyNatHandlerFactory::new());
let addresses: Vec<IpCidr> = vec![
self.metadata.gateway.ipv4.into(),
self.metadata.gateway.ipv6.into(),
];
let mut kdev =
RawSocketHandle::bound_to_interface(&interface, RawSocketProtocol::Ethernet)?;
let mtu = kdev.mtu_of_interface(&interface)? + EXTRA_MTU;
let (tx_sender, tx_receiver) = channel::<BytesMut>(TX_CHANNEL_BUFFER_LEN);
let mut udev = ChannelDevice::new(mtu, Medium::Ethernet, tx_sender.clone());
let mac = self.metadata.gateway.mac;
let nat = Nat::new(mtu, proxy, mac, addresses.clone(), tx_sender.clone())?;
let hardware_addr = HardwareAddress::Ethernet(mac);
let config = Config::new(hardware_addr);
let mut iface = Interface::new(config, &mut udev, Instant::now());
iface.update_ip_addrs(|addrs| {
addrs
.extend_from_slice(&addresses)
.expect("failed to set ip addresses");
});
let sockets = SocketSet::new(vec![]);
let handle = self.bridge.join(self.metadata.guest.mac).await?;
let kdev = AsyncRawSocketChannel::new(mtu, kdev)?;
Ok(NetworkStack {
tx: tx_receiver,
kdev,
udev,
interface: iface,
sockets,
nat,
bridge: handle,
})
}
pub async fn launch(self) -> Result<JoinHandle<()>> {
Ok(tokio::task::spawn(async move {
info!(
"launched network backend for krata guest {}",
self.metadata.uuid
);
if let Err(error) = self.run().await {
warn!(
"network backend for krata guest {} failed: {}",
self.metadata.uuid, error
);
}
}))
}
}
impl Drop for NetworkBackend {
fn drop(&mut self) {
info!(
"destroyed network backend for krata guest {}",
self.metadata.uuid
);
}
}

View File

@ -1,89 +0,0 @@
// Referenced https://github.com/vi/wgslirpy/blob/master/crates/libwgslirpy/src/channelized_smoltcp_device.rs
use bytes::BytesMut;
use log::{debug, warn};
use smoltcp::phy::{Checksum, Device, Medium};
use tokio::sync::mpsc::Sender;
const TEAR_OFF_BUFFER_SIZE: usize = 65536;
pub struct ChannelDevice {
pub mtu: usize,
pub medium: Medium,
pub tx: Sender<BytesMut>,
pub rx: Option<BytesMut>,
tear_off_buffer: BytesMut,
}
impl ChannelDevice {
pub fn new(mtu: usize, medium: Medium, tx: Sender<BytesMut>) -> Self {
Self {
mtu,
medium,
tx,
rx: None,
tear_off_buffer: BytesMut::with_capacity(TEAR_OFF_BUFFER_SIZE),
}
}
}
pub struct RxToken(pub BytesMut);
impl Device for ChannelDevice {
type RxToken<'a> = RxToken where Self: 'a;
type TxToken<'a> = &'a mut ChannelDevice where Self: 'a;
fn receive(
&mut self,
_timestamp: smoltcp::time::Instant,
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
self.rx.take().map(|x| (RxToken(x), self))
}
fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
if self.tx.capacity() == 0 {
debug!("ran out of transmission capacity");
return None;
}
Some(self)
}
fn capabilities(&self) -> smoltcp::phy::DeviceCapabilities {
let mut capabilities = smoltcp::phy::DeviceCapabilities::default();
capabilities.medium = self.medium;
capabilities.max_transmission_unit = self.mtu;
capabilities.checksum = smoltcp::phy::ChecksumCapabilities::ignored();
capabilities.checksum.tcp = Checksum::Tx;
capabilities.checksum.ipv4 = Checksum::Tx;
capabilities.checksum.icmpv4 = Checksum::Tx;
capabilities.checksum.icmpv6 = Checksum::Tx;
capabilities
}
}
impl smoltcp::phy::RxToken for RxToken {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
f(&mut self.0[..])
}
}
impl<'a> smoltcp::phy::TxToken for &'a mut ChannelDevice {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.tear_off_buffer.resize(len, 0);
let result = f(&mut self.tear_off_buffer[..]);
let chunk = self.tear_off_buffer.split();
if let Err(error) = self.tx.try_send(chunk) {
warn!("failed to transmit packet: {}", error);
}
if self.tear_off_buffer.capacity() < self.mtu {
self.tear_off_buffer = BytesMut::with_capacity(TEAR_OFF_BUFFER_SIZE);
}
result
}
}

View File

@ -1,149 +0,0 @@
use std::{
io::ErrorKind,
net::{IpAddr, Ipv4Addr},
};
use advmac::MacAddr6;
use anyhow::{anyhow, Result};
use bytes::BytesMut;
use futures::TryStreamExt;
use log::error;
use smoltcp::wire::EthernetAddress;
use tokio::{select, task::JoinHandle};
use tokio_tun::Tun;
use crate::vbridge::{BridgeJoinHandle, VirtualBridge};
const HOST_IPV4_ADDR: Ipv4Addr = Ipv4Addr::new(10, 75, 0, 1);
#[derive(Debug)]
enum HostBridgeProcessSelect {
Send(Option<BytesMut>),
Receive(std::io::Result<usize>),
}
pub struct HostBridge {
task: JoinHandle<()>,
}
impl HostBridge {
pub async fn new(mtu: usize, interface: String, bridge: &VirtualBridge) -> Result<HostBridge> {
let tun = Tun::builder()
.name(&interface)
.tap(true)
.mtu(mtu as i32)
.packet_info(false)
.try_build()?;
let (connection, handle, _) = rtnetlink::new_connection()?;
tokio::spawn(connection);
let mut mac = MacAddr6::random();
mac.set_local(true);
mac.set_multicast(false);
let mut links = handle.link().get().match_name(interface.clone()).execute();
let link = links.try_next().await?;
if link.is_none() {
return Err(anyhow!(
"unable to find network interface named {}",
interface
));
}
let link = link.unwrap();
handle
.address()
.add(link.header.index, IpAddr::V4(HOST_IPV4_ADDR), 16)
.execute()
.await?;
handle
.address()
.add(link.header.index, IpAddr::V6(mac.to_link_local_ipv6()), 10)
.execute()
.await?;
handle
.link()
.set(link.header.index)
.address(mac.to_array().to_vec())
.up()
.execute()
.await?;
let mac = EthernetAddress(mac.to_array());
let bridge_handle = bridge.join(mac).await?;
let task = tokio::task::spawn(async move {
if let Err(error) = HostBridge::process(mtu, tun, bridge_handle).await {
error!("failed to process host bridge: {}", error);
}
});
Ok(HostBridge { task })
}
async fn process(mtu: usize, tun: Tun, mut bridge_handle: BridgeJoinHandle) -> Result<()> {
let tear_off_size = 100 * mtu;
let mut buffer: BytesMut = BytesMut::with_capacity(tear_off_size);
loop {
if buffer.capacity() < mtu {
buffer = BytesMut::with_capacity(tear_off_size);
}
buffer.resize(mtu, 0);
let selection = select! {
biased;
x = tun.recv(&mut buffer) => HostBridgeProcessSelect::Receive(x),
x = bridge_handle.from_bridge_receiver.recv() => HostBridgeProcessSelect::Send(x),
x = bridge_handle.from_broadcast_receiver.recv() => HostBridgeProcessSelect::Send(x.ok()),
};
match selection {
HostBridgeProcessSelect::Send(Some(bytes)) => match tun.try_send(&bytes) {
Ok(_) => {}
Err(error) => {
if error.kind() == ErrorKind::WouldBlock {
continue;
}
return Err(error.into());
}
},
HostBridgeProcessSelect::Send(None) => {
break;
}
HostBridgeProcessSelect::Receive(result) => match result {
Ok(len) => {
if len == 0 {
continue;
}
let packet = buffer.split_to(len);
let _ = bridge_handle.to_bridge_sender.try_send(packet);
}
Err(error) => {
if error.kind() == ErrorKind::WouldBlock {
continue;
}
error!(
"failed to receive data from tap device to bridge: {}",
error
);
break;
}
},
}
}
Ok(())
}
}
impl Drop for HostBridge {
fn drop(&mut self) {
self.task.abort();
}
}

View File

@ -1,250 +0,0 @@
use crate::raw_socket::{RawSocketHandle, RawSocketProtocol};
use anyhow::{anyhow, Result};
use etherparse::{
IcmpEchoHeader, Icmpv4Header, Icmpv4Slice, Icmpv4Type, Icmpv6Header, Icmpv6Slice, Icmpv6Type,
IpNumber, NetSlice, SlicedPacket,
};
use log::warn;
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
os::fd::{FromRawFd, IntoRawFd},
sync::Arc,
time::Duration,
};
use tokio::{
net::UdpSocket,
sync::{oneshot, Mutex},
task::JoinHandle,
time::timeout,
};
#[derive(Debug)]
pub enum IcmpProtocol {
Icmpv4,
Icmpv6,
}
impl IcmpProtocol {
pub fn to_socket_protocol(&self) -> RawSocketProtocol {
match self {
IcmpProtocol::Icmpv4 => RawSocketProtocol::Icmpv4,
IcmpProtocol::Icmpv6 => RawSocketProtocol::Icmpv6,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct IcmpHandlerToken(IpAddr, Option<u16>, u16);
#[derive(Debug)]
pub enum IcmpReply {
Icmpv4 {
header: Icmpv4Header,
echo: IcmpEchoHeader,
payload: Vec<u8>,
},
Icmpv6 {
header: Icmpv6Header,
echo: IcmpEchoHeader,
payload: Vec<u8>,
},
}
type IcmpHandlerMap = Arc<Mutex<HashMap<IcmpHandlerToken, oneshot::Sender<IcmpReply>>>>;
#[derive(Clone)]
pub struct IcmpClient {
socket: Arc<UdpSocket>,
handlers: IcmpHandlerMap,
task: Arc<JoinHandle<Result<()>>>,
}
impl IcmpClient {
pub fn new(protocol: IcmpProtocol) -> Result<IcmpClient> {
let handle = RawSocketHandle::new(protocol.to_socket_protocol())?;
let socket = unsafe { std::net::UdpSocket::from_raw_fd(handle.into_raw_fd()) };
let socket: Arc<UdpSocket> = Arc::new(socket.try_into()?);
let handlers = Arc::new(Mutex::new(HashMap::new()));
let task = Arc::new(tokio::task::spawn(IcmpClient::process(
protocol,
socket.clone(),
handlers.clone(),
)));
Ok(IcmpClient {
socket,
handlers,
task,
})
}
async fn process(
protocol: IcmpProtocol,
socket: Arc<UdpSocket>,
handlers: IcmpHandlerMap,
) -> Result<()> {
let mut buffer = vec![0u8; 2048];
loop {
let (size, addr) = socket.recv_from(&mut buffer).await?;
let packet = &buffer[0..size];
let (token, reply) = match protocol {
IcmpProtocol::Icmpv4 => {
let sliced = match SlicedPacket::from_ip(packet) {
Ok(sliced) => sliced,
Err(error) => {
warn!("received icmp packet but failed to parse it: {}", error);
continue;
}
};
let Some(NetSlice::Ipv4(ipv4)) = sliced.net else {
continue;
};
if ipv4.header().protocol() != IpNumber::ICMP {
continue;
}
let Ok(icmpv4) = Icmpv4Slice::from_slice(ipv4.payload().payload) else {
continue;
};
let Icmpv4Type::EchoReply(echo) = icmpv4.header().icmp_type else {
continue;
};
let token = IcmpHandlerToken(
IpAddr::V4(ipv4.header().source_addr()),
Some(echo.id),
echo.seq,
);
let reply = IcmpReply::Icmpv4 {
header: icmpv4.header(),
echo,
payload: icmpv4.payload().to_vec(),
};
(token, reply)
}
IcmpProtocol::Icmpv6 => {
let Ok(icmpv6) = Icmpv6Slice::from_slice(packet) else {
continue;
};
let Icmpv6Type::EchoReply(echo) = icmpv6.header().icmp_type else {
continue;
};
let SocketAddr::V6(addr) = addr else {
continue;
};
let token = IcmpHandlerToken(IpAddr::V6(*addr.ip()), Some(echo.id), echo.seq);
let reply = IcmpReply::Icmpv6 {
header: icmpv6.header(),
echo,
payload: icmpv6.payload().to_vec(),
};
(token, reply)
}
};
if let Some(sender) = handlers.lock().await.remove(&token) {
let _ = sender.send(reply);
}
}
}
async fn add_handler(&self, token: IcmpHandlerToken) -> Result<oneshot::Receiver<IcmpReply>> {
let (tx, rx) = oneshot::channel();
if self
.handlers
.lock()
.await
.insert(token.clone(), tx)
.is_some()
{
return Err(anyhow!("duplicate icmp request: {:?}", token));
}
Ok(rx)
}
async fn remove_handler(&self, token: IcmpHandlerToken) -> Result<()> {
self.handlers.lock().await.remove(&token);
Ok(())
}
pub async fn ping4(
&self,
addr: Ipv4Addr,
id: u16,
seq: u16,
payload: &[u8],
deadline: Duration,
) -> Result<Option<IcmpReply>> {
let token = IcmpHandlerToken(IpAddr::V4(addr), Some(id), seq);
let rx = self.add_handler(token.clone()).await?;
let echo = IcmpEchoHeader { id, seq };
let mut header = Icmpv4Header::new(Icmpv4Type::EchoRequest(echo));
header.update_checksum(payload);
let mut buffer: Vec<u8> = Vec::new();
header.write(&mut buffer)?;
buffer.extend_from_slice(payload);
self.socket
.send_to(&buffer, SocketAddr::V4(SocketAddrV4::new(addr, 0)))
.await?;
let result = timeout(deadline, rx).await;
self.remove_handler(token).await?;
let reply = match result {
Ok(Ok(packet)) => Some(packet),
Ok(Err(err)) => return Err(anyhow!("failed to wait for icmp packet: {}", err)),
Err(_) => None,
};
Ok(reply)
}
pub async fn ping6(
&self,
addr: Ipv6Addr,
id: u16,
seq: u16,
payload: &[u8],
deadline: Duration,
) -> Result<Option<IcmpReply>> {
let token = IcmpHandlerToken(IpAddr::V6(addr), Some(id), seq);
let rx = self.add_handler(token.clone()).await?;
let echo = IcmpEchoHeader { id, seq };
let header = Icmpv6Header::new(Icmpv6Type::EchoRequest(echo));
let mut buffer: Vec<u8> = Vec::new();
header.write(&mut buffer)?;
buffer.extend_from_slice(payload);
self.socket
.send_to(&buffer, SocketAddr::V6(SocketAddrV6::new(addr, 0, 0, 0)))
.await?;
let result = timeout(deadline, rx).await;
self.remove_handler(token).await?;
let reply = match result {
Ok(Ok(packet)) => Some(packet),
Ok(Err(err)) => return Err(anyhow!("failed to wait for icmp packet: {}", err)),
Err(_) => None,
};
Ok(reply)
}
}
impl Drop for IcmpClient {
fn drop(&mut self) {
if Arc::strong_count(&self.task) <= 1 {
self.task.abort();
}
}
}

View File

@ -1,129 +0,0 @@
use std::{collections::HashMap, time::Duration};
use anyhow::Result;
use autonet::{AutoNetworkChangeset, AutoNetworkWatcher, NetworkMetadata};
use futures::{future::join_all, TryFutureExt};
use hbridge::HostBridge;
use krata::{
client::ControlClientProvider,
dial::ControlDialAddress,
v1::{common::Guest, control::control_service_client::ControlServiceClient},
};
use log::warn;
use tokio::{task::JoinHandle, time::sleep};
use tonic::transport::Channel;
use uuid::Uuid;
use vbridge::VirtualBridge;
use crate::backend::NetworkBackend;
pub mod autonet;
pub mod backend;
pub mod chandev;
pub mod hbridge;
pub mod icmp;
pub mod nat;
pub mod pkt;
pub mod proxynat;
pub mod raw_socket;
pub mod vbridge;
const HOST_BRIDGE_MTU: usize = 1500;
pub const EXTRA_MTU: usize = 20;
pub struct NetworkService {
pub control: ControlServiceClient<Channel>,
pub guests: HashMap<Uuid, Guest>,
pub backends: HashMap<Uuid, JoinHandle<()>>,
pub bridge: VirtualBridge,
pub hbridge: HostBridge,
}
impl NetworkService {
pub async fn new(control_address: ControlDialAddress) -> Result<NetworkService> {
let control = ControlClientProvider::dial(control_address).await?;
let bridge = VirtualBridge::new()?;
let hbridge =
HostBridge::new(HOST_BRIDGE_MTU + EXTRA_MTU, "krata0".to_string(), &bridge).await?;
Ok(NetworkService {
control,
guests: HashMap::new(),
backends: HashMap::new(),
bridge,
hbridge,
})
}
}
impl NetworkService {
pub async fn watch(&mut self) -> Result<()> {
let mut watcher = AutoNetworkWatcher::new(self.control.clone()).await?;
let mut receiver = watcher.events.subscribe();
loop {
let changeset = watcher.read_changes().await?;
self.process_network_changeset(&mut watcher, changeset)
.await?;
watcher.wait(&mut receiver).await?;
}
}
async fn process_network_changeset(
&mut self,
collector: &mut AutoNetworkWatcher,
changeset: AutoNetworkChangeset,
) -> Result<()> {
for removal in &changeset.removed {
if let Some(handle) = self.backends.remove(&removal.uuid) {
handle.abort();
}
}
let futures = changeset
.added
.iter()
.map(|metadata| {
self.add_network_backend(metadata)
.map_err(|x| (metadata.clone(), x))
})
.collect::<Vec<_>>();
sleep(Duration::from_secs(1)).await;
let mut failed: Vec<Uuid> = Vec::new();
let mut launched: Vec<(Uuid, JoinHandle<()>)> = Vec::new();
let results = join_all(futures).await;
for result in results {
match result {
Ok(launch) => {
launched.push(launch);
}
Err((metadata, error)) => {
warn!(
"failed to launch network backend for krata guest {}: {}",
metadata.uuid, error
);
failed.push(metadata.uuid);
}
};
}
for (uuid, handle) in launched {
self.backends.insert(uuid, handle);
}
for uuid in failed {
collector.mark_unknown(uuid)?;
}
Ok(())
}
async fn add_network_backend(
&self,
metadata: &NetworkMetadata,
) -> Result<(Uuid, JoinHandle<()>)> {
let mut network = NetworkBackend::new(metadata.clone(), self.bridge.clone())?;
network.init().await?;
Ok((metadata.uuid, network.launch().await?))
}
}

View File

@ -1,36 +0,0 @@
use anyhow::Result;
use async_trait::async_trait;
use bytes::BytesMut;
use tokio::sync::mpsc::Sender;
use super::key::NatKey;
#[derive(Debug, Clone)]
pub struct NatHandlerContext {
pub mtu: usize,
pub key: NatKey,
pub transmit_sender: Sender<BytesMut>,
pub reclaim_sender: Sender<NatKey>,
}
impl NatHandlerContext {
pub fn try_transmit(&self, buffer: BytesMut) -> Result<()> {
self.transmit_sender.try_send(buffer)?;
Ok(())
}
pub async fn reclaim(&self) -> Result<()> {
let _ = self.reclaim_sender.try_send(self.key);
Ok(())
}
}
#[async_trait]
pub trait NatHandler: Send {
async fn receive(&self, packet: &[u8]) -> Result<bool>;
}
#[async_trait]
pub trait NatHandlerFactory: Send {
async fn nat(&self, context: NatHandlerContext) -> Option<Box<dyn NatHandler>>;
}

View File

@ -1,29 +0,0 @@
use std::fmt::Display;
use smoltcp::wire::{EthernetAddress, IpEndpoint};
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub enum NatKeyProtocol {
Tcp,
Udp,
Icmp,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct NatKey {
pub protocol: NatKeyProtocol,
pub client_mac: EthernetAddress,
pub local_mac: EthernetAddress,
pub client_ip: IpEndpoint,
pub external_ip: IpEndpoint,
}
impl Display for NatKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} -> {} {:?} {} -> {}",
self.client_mac, self.local_mac, self.protocol, self.client_ip, self.external_ip
)
}
}

View File

@ -1,42 +0,0 @@
use anyhow::Result;
use tokio::sync::mpsc::Sender;
use self::handler::NatHandlerFactory;
use self::processor::NatProcessor;
use bytes::BytesMut;
use smoltcp::wire::EthernetAddress;
use smoltcp::wire::IpCidr;
use tokio::task::JoinHandle;
pub mod handler;
pub mod key;
pub mod processor;
pub mod table;
pub struct Nat {
pub receive_sender: Sender<BytesMut>,
task: JoinHandle<()>,
}
impl Nat {
pub fn new(
mtu: usize,
factory: Box<dyn NatHandlerFactory>,
local_mac: EthernetAddress,
local_cidrs: Vec<IpCidr>,
transmit_sender: Sender<BytesMut>,
) -> Result<Self> {
let (receive_sender, task) =
NatProcessor::launch(mtu, factory, local_mac, local_cidrs, transmit_sender)?;
Ok(Self {
receive_sender,
task,
})
}
}
impl Drop for Nat {
fn drop(&mut self) {
self.task.abort();
}
}

View File

@ -1,330 +0,0 @@
use crate::pkt::RecvPacket;
use crate::pkt::RecvPacketIp;
use anyhow::Result;
use bytes::BytesMut;
use etherparse::Icmpv4Header;
use etherparse::Icmpv4Type;
use etherparse::Icmpv6Header;
use etherparse::Icmpv6Type;
use etherparse::IpNumber;
use etherparse::IpPayloadSlice;
use etherparse::Ipv4Slice;
use etherparse::Ipv6Slice;
use etherparse::SlicedPacket;
use etherparse::TcpHeaderSlice;
use etherparse::UdpHeaderSlice;
use log::warn;
use log::{debug, trace};
use smoltcp::wire::EthernetAddress;
use smoltcp::wire::IpAddress;
use smoltcp::wire::IpCidr;
use smoltcp::wire::IpEndpoint;
use std::collections::hash_map::Entry;
use tokio::select;
use tokio::sync::mpsc::channel;
use tokio::sync::mpsc::Receiver;
use tokio::sync::mpsc::Sender;
use tokio::task::JoinHandle;
use super::handler::NatHandler;
use super::handler::NatHandlerContext;
use super::handler::NatHandlerFactory;
use super::key::NatKey;
use super::key::NatKeyProtocol;
use super::table::NatTable;
const RECEIVE_CHANNEL_QUEUE_LEN: usize = 3000;
const RECLAIM_CHANNEL_QUEUE_LEN: usize = 30;
pub struct NatProcessor {
mtu: usize,
local_mac: EthernetAddress,
local_cidrs: Vec<IpCidr>,
table: NatTable,
factory: Box<dyn NatHandlerFactory>,
transmit_sender: Sender<BytesMut>,
reclaim_sender: Sender<NatKey>,
reclaim_receiver: Receiver<NatKey>,
receive_receiver: Receiver<BytesMut>,
}
enum NatProcessorSelect {
Reclaim(Option<NatKey>),
ReceivedPacket(Option<BytesMut>),
}
impl NatProcessor {
pub fn launch(
mtu: usize,
factory: Box<dyn NatHandlerFactory>,
local_mac: EthernetAddress,
local_cidrs: Vec<IpCidr>,
transmit_sender: Sender<BytesMut>,
) -> Result<(Sender<BytesMut>, JoinHandle<()>)> {
let (reclaim_sender, reclaim_receiver) = channel(RECLAIM_CHANNEL_QUEUE_LEN);
let (receive_sender, receive_receiver) = channel(RECEIVE_CHANNEL_QUEUE_LEN);
let mut processor = Self {
mtu,
local_mac,
local_cidrs,
factory,
table: NatTable::new(),
transmit_sender,
reclaim_sender,
receive_receiver,
reclaim_receiver,
};
let handle = tokio::task::spawn(async move {
if let Err(error) = processor.process().await {
warn!("nat processing failed: {}", error);
}
});
Ok((receive_sender, handle))
}
pub async fn process(&mut self) -> Result<()> {
loop {
let selection = select! {
x = self.reclaim_receiver.recv() => NatProcessorSelect::Reclaim(x),
x = self.receive_receiver.recv() => NatProcessorSelect::ReceivedPacket(x),
};
match selection {
NatProcessorSelect::Reclaim(Some(key)) => {
if self.table.inner.remove(&key).is_some() {
debug!("reclaimed nat key: {}", key);
}
}
NatProcessorSelect::ReceivedPacket(Some(packet)) => {
if let Ok(slice) = SlicedPacket::from_ethernet(&packet) {
let Ok(packet) = RecvPacket::new(&packet, &slice) else {
continue;
};
self.process_packet(&packet).await?;
}
}
NatProcessorSelect::ReceivedPacket(None) | NatProcessorSelect::Reclaim(None) => {
break
}
}
}
Ok(())
}
pub async fn process_reclaim(&mut self) -> Result<Option<NatKey>> {
Ok(if let Some(key) = self.reclaim_receiver.recv().await {
if self.table.inner.remove(&key).is_some() {
debug!("reclaimed nat key: {}", key);
Some(key)
} else {
None
}
} else {
None
})
}
pub async fn process_packet<'a>(&mut self, packet: &RecvPacket<'a>) -> Result<()> {
let Some(ether) = packet.ether else {
return Ok(());
};
let mac = EthernetAddress(ether.destination());
if mac != self.local_mac {
trace!(
"received packet with destination {} which is not the local mac {}",
mac,
self.local_mac
);
return Ok(());
}
let key = match packet.ip {
Some(RecvPacketIp::Ipv4(ipv4)) => self.extract_key_ipv4(packet, ipv4)?,
Some(RecvPacketIp::Ipv6(ipv6)) => self.extract_key_ipv6(packet, ipv6)?,
_ => None,
};
let Some(key) = key else {
return Ok(());
};
for cidr in &self.local_cidrs {
if cidr.contains_addr(&key.external_ip.addr) {
return Ok(());
}
}
let context = NatHandlerContext {
mtu: self.mtu,
key,
transmit_sender: self.transmit_sender.clone(),
reclaim_sender: self.reclaim_sender.clone(),
};
let handler: Option<&mut Box<dyn NatHandler>> = match self.table.inner.entry(key) {
Entry::Occupied(entry) => Some(entry.into_mut()),
Entry::Vacant(entry) => {
if let Some(handler) = self.factory.nat(context).await {
debug!("creating nat entry for key: {}", key);
Some(entry.insert(handler))
} else {
None
}
}
};
if let Some(handler) = handler {
if !handler.receive(packet.raw).await? {
self.reclaim_sender.try_send(key)?;
}
}
Ok(())
}
pub fn extract_key_ipv4<'a>(
&mut self,
packet: &RecvPacket<'a>,
ipv4: &Ipv4Slice<'a>,
) -> Result<Option<NatKey>> {
let source_addr = IpAddress::Ipv4(ipv4.header().source_addr().into());
let dest_addr = IpAddress::Ipv4(ipv4.header().destination_addr().into());
Ok(match ipv4.header().protocol() {
IpNumber::TCP => {
self.extract_key_tcp(packet, source_addr, dest_addr, ipv4.payload())?
}
IpNumber::UDP => {
self.extract_key_udp(packet, source_addr, dest_addr, ipv4.payload())?
}
IpNumber::ICMP => {
self.extract_key_icmpv4(packet, source_addr, dest_addr, ipv4.payload())?
}
_ => None,
})
}
pub fn extract_key_ipv6<'a>(
&mut self,
packet: &RecvPacket<'a>,
ipv6: &Ipv6Slice<'a>,
) -> Result<Option<NatKey>> {
let source_addr = IpAddress::Ipv6(ipv6.header().source_addr().into());
let dest_addr = IpAddress::Ipv6(ipv6.header().destination_addr().into());
Ok(match ipv6.header().next_header() {
IpNumber::TCP => {
self.extract_key_tcp(packet, source_addr, dest_addr, ipv6.payload())?
}
IpNumber::UDP => {
self.extract_key_udp(packet, source_addr, dest_addr, ipv6.payload())?
}
IpNumber::IPV6_ICMP => {
self.extract_key_icmpv6(packet, source_addr, dest_addr, ipv6.payload())?
}
_ => None,
})
}
pub fn extract_key_udp<'a>(
&mut self,
packet: &RecvPacket<'a>,
source_addr: IpAddress,
dest_addr: IpAddress,
payload: &IpPayloadSlice<'a>,
) -> Result<Option<NatKey>> {
let Some(ether) = packet.ether else {
return Ok(None);
};
let header = UdpHeaderSlice::from_slice(payload.payload)?;
let source = IpEndpoint::new(source_addr, header.source_port());
let dest = IpEndpoint::new(dest_addr, header.destination_port());
Ok(Some(NatKey {
protocol: NatKeyProtocol::Udp,
client_mac: EthernetAddress(ether.source()),
local_mac: EthernetAddress(ether.destination()),
client_ip: source,
external_ip: dest,
}))
}
pub fn extract_key_icmpv4<'a>(
&mut self,
packet: &RecvPacket<'a>,
source_addr: IpAddress,
dest_addr: IpAddress,
payload: &IpPayloadSlice<'a>,
) -> Result<Option<NatKey>> {
let Some(ether) = packet.ether else {
return Ok(None);
};
let (header, _) = Icmpv4Header::from_slice(payload.payload)?;
let Icmpv4Type::EchoRequest(_) = header.icmp_type else {
return Ok(None);
};
let source = IpEndpoint::new(source_addr, 0);
let dest = IpEndpoint::new(dest_addr, 0);
Ok(Some(NatKey {
protocol: NatKeyProtocol::Icmp,
client_mac: EthernetAddress(ether.source()),
local_mac: EthernetAddress(ether.destination()),
client_ip: source,
external_ip: dest,
}))
}
pub fn extract_key_icmpv6<'a>(
&mut self,
packet: &RecvPacket<'a>,
source_addr: IpAddress,
dest_addr: IpAddress,
payload: &IpPayloadSlice<'a>,
) -> Result<Option<NatKey>> {
let Some(ether) = packet.ether else {
return Ok(None);
};
let (header, _) = Icmpv6Header::from_slice(payload.payload)?;
let Icmpv6Type::EchoRequest(_) = header.icmp_type else {
return Ok(None);
};
let source = IpEndpoint::new(source_addr, 0);
let dest = IpEndpoint::new(dest_addr, 0);
Ok(Some(NatKey {
protocol: NatKeyProtocol::Icmp,
client_mac: EthernetAddress(ether.source()),
local_mac: EthernetAddress(ether.destination()),
client_ip: source,
external_ip: dest,
}))
}
pub fn extract_key_tcp<'a>(
&mut self,
packet: &RecvPacket<'a>,
source_addr: IpAddress,
dest_addr: IpAddress,
payload: &IpPayloadSlice<'a>,
) -> Result<Option<NatKey>> {
let Some(ether) = packet.ether else {
return Ok(None);
};
let header = TcpHeaderSlice::from_slice(payload.payload)?;
let source = IpEndpoint::new(source_addr, header.source_port());
let dest = IpEndpoint::new(dest_addr, header.destination_port());
Ok(Some(NatKey {
protocol: NatKeyProtocol::Tcp,
client_mac: EthernetAddress(ether.source()),
local_mac: EthernetAddress(ether.destination()),
client_ip: source,
external_ip: dest,
}))
}
}

View File

@ -1,21 +0,0 @@
use std::collections::HashMap;
use super::{handler::NatHandler, key::NatKey};
pub struct NatTable {
pub inner: HashMap<NatKey, Box<dyn NatHandler>>,
}
impl Default for NatTable {
fn default() -> Self {
Self::new()
}
}
impl NatTable {
pub fn new() -> Self {
Self {
inner: HashMap::new(),
}
}
}

View File

@ -1,37 +0,0 @@
use anyhow::Result;
use etherparse::{Ethernet2Slice, Ipv4Slice, Ipv6Slice, LinkSlice, NetSlice, SlicedPacket};
pub enum RecvPacketIp<'a> {
Ipv4(&'a Ipv4Slice<'a>),
Ipv6(&'a Ipv6Slice<'a>),
}
pub struct RecvPacket<'a> {
pub raw: &'a [u8],
pub slice: &'a SlicedPacket<'a>,
pub ether: Option<&'a Ethernet2Slice<'a>>,
pub ip: Option<RecvPacketIp<'a>>,
}
impl RecvPacket<'_> {
pub fn new<'a>(raw: &'a [u8], slice: &'a SlicedPacket<'a>) -> Result<RecvPacket<'a>> {
let ether = match slice.link {
Some(LinkSlice::Ethernet2(ref ether)) => Some(ether),
_ => None,
};
let ip = match slice.net {
Some(NetSlice::Ipv4(ref ipv4)) => Some(RecvPacketIp::Ipv4(ipv4)),
Some(NetSlice::Ipv6(ref ipv6)) => Some(RecvPacketIp::Ipv6(ipv6)),
_ => None,
};
let packet = RecvPacket {
raw,
slice,
ether,
ip,
};
Ok(packet)
}
}

View File

@ -1,276 +0,0 @@
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
time::Duration,
};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use bytes::{BufMut, BytesMut};
use etherparse::{
IcmpEchoHeader, Icmpv4Header, Icmpv4Type, Icmpv6Header, Icmpv6Type, IpNumber, Ipv4Slice,
Ipv6Slice, NetSlice, PacketBuilder, SlicedPacket,
};
use log::{debug, trace, warn};
use smoltcp::wire::IpAddress;
use tokio::{
select,
sync::mpsc::{Receiver, Sender},
};
use crate::{
icmp::{IcmpClient, IcmpProtocol, IcmpReply},
nat::handler::{NatHandler, NatHandlerContext},
};
const ICMP_PING_TIMEOUT_SECS: u64 = 20;
const ICMP_TIMEOUT_SECS: u64 = 30;
pub struct ProxyIcmpHandler {
rx_sender: Sender<BytesMut>,
}
#[async_trait]
impl NatHandler for ProxyIcmpHandler {
async fn receive(&self, data: &[u8]) -> Result<bool> {
if self.rx_sender.is_closed() {
Ok(true)
} else {
self.rx_sender.try_send(data.into())?;
Ok(true)
}
}
}
enum ProxyIcmpSelect {
Internal(BytesMut),
Close,
}
impl ProxyIcmpHandler {
pub fn new(rx_sender: Sender<BytesMut>) -> Self {
ProxyIcmpHandler { rx_sender }
}
pub async fn spawn(
&mut self,
context: NatHandlerContext,
rx_receiver: Receiver<BytesMut>,
) -> Result<()> {
let client = IcmpClient::new(match context.key.external_ip.addr {
IpAddress::Ipv4(_) => IcmpProtocol::Icmpv4,
IpAddress::Ipv6(_) => IcmpProtocol::Icmpv6,
})?;
tokio::spawn(async move {
if let Err(error) = ProxyIcmpHandler::process(client, rx_receiver, context).await {
warn!("processing of icmp proxy failed: {}", error);
}
});
Ok(())
}
async fn process(
client: IcmpClient,
mut rx_receiver: Receiver<BytesMut>,
context: NatHandlerContext,
) -> Result<()> {
loop {
let deadline = tokio::time::sleep(Duration::from_secs(ICMP_TIMEOUT_SECS));
let selection = select! {
x = rx_receiver.recv() => if let Some(data) = x {
ProxyIcmpSelect::Internal(data)
} else {
ProxyIcmpSelect::Close
},
_ = deadline => ProxyIcmpSelect::Close,
};
match selection {
ProxyIcmpSelect::Internal(data) => {
let packet = SlicedPacket::from_ethernet(&data)?;
let Some(ref net) = packet.net else {
continue;
};
match net {
NetSlice::Ipv4(ipv4) => {
ProxyIcmpHandler::process_ipv4(&context, ipv4, &client).await?
}
NetSlice::Ipv6(ipv6) => {
ProxyIcmpHandler::process_ipv6(&context, ipv6, &client).await?
}
}
}
ProxyIcmpSelect::Close => {
break;
}
}
}
context.reclaim().await?;
Ok(())
}
async fn process_ipv4(
context: &NatHandlerContext,
ipv4: &Ipv4Slice<'_>,
client: &IcmpClient,
) -> Result<()> {
if ipv4.header().protocol() != IpNumber::ICMP {
return Ok(());
}
let (header, payload) = Icmpv4Header::from_slice(ipv4.payload().payload)?;
if let Icmpv4Type::EchoRequest(echo) = header.icmp_type {
let IpAddr::V4(external_ipv4) = context.key.external_ip.addr.into() else {
return Ok(());
};
let context = context.clone();
let client = client.clone();
let payload = payload.to_vec();
tokio::task::spawn(async move {
if let Err(error) = ProxyIcmpHandler::process_echo_ipv4(
context,
client,
external_ipv4,
echo,
payload,
)
.await
{
trace!("icmp4 echo failed: {}", error);
}
});
}
Ok(())
}
async fn process_ipv6(
context: &NatHandlerContext,
ipv6: &Ipv6Slice<'_>,
client: &IcmpClient,
) -> Result<()> {
if ipv6.header().next_header() != IpNumber::IPV6_ICMP {
return Ok(());
}
let (header, payload) = Icmpv6Header::from_slice(ipv6.payload().payload)?;
if let Icmpv6Type::EchoRequest(echo) = header.icmp_type {
let IpAddr::V6(external_ipv6) = context.key.external_ip.addr.into() else {
return Ok(());
};
let context = context.clone();
let client = client.clone();
let payload = payload.to_vec();
tokio::task::spawn(async move {
if let Err(error) = ProxyIcmpHandler::process_echo_ipv6(
context,
client,
external_ipv6,
echo,
payload,
)
.await
{
trace!("icmp6 echo failed: {}", error);
}
});
}
Ok(())
}
async fn process_echo_ipv4(
context: NatHandlerContext,
client: IcmpClient,
external_ipv4: Ipv4Addr,
echo: IcmpEchoHeader,
payload: Vec<u8>,
) -> Result<()> {
let reply = client
.ping4(
external_ipv4,
echo.id,
echo.seq,
&payload,
Duration::from_secs(ICMP_PING_TIMEOUT_SECS),
)
.await?;
let Some(IcmpReply::Icmpv4 {
header: _,
echo,
payload,
}) = reply
else {
return Ok(());
};
let packet = PacketBuilder::ethernet2(context.key.local_mac.0, context.key.client_mac.0);
let packet = match (context.key.external_ip.addr, context.key.client_ip.addr) {
(IpAddress::Ipv4(external_addr), IpAddress::Ipv4(client_addr)) => {
packet.ipv4(external_addr.0, client_addr.0, 20)
}
_ => {
return Err(anyhow!("IP endpoint mismatch"));
}
};
let packet = packet.icmpv4_echo_reply(echo.id, echo.seq);
let buffer = BytesMut::with_capacity(packet.size(payload.len()));
let mut writer = buffer.writer();
packet.write(&mut writer, &payload)?;
let buffer = writer.into_inner();
if let Err(error) = context.try_transmit(buffer) {
debug!("failed to transmit icmp packet: {}", error);
}
Ok(())
}
async fn process_echo_ipv6(
context: NatHandlerContext,
client: IcmpClient,
external_ipv6: Ipv6Addr,
echo: IcmpEchoHeader,
payload: Vec<u8>,
) -> Result<()> {
let reply = client
.ping6(
external_ipv6,
echo.id,
echo.seq,
&payload,
Duration::from_secs(ICMP_PING_TIMEOUT_SECS),
)
.await?;
let Some(IcmpReply::Icmpv6 {
header: _,
echo,
payload,
}) = reply
else {
return Ok(());
};
let packet = PacketBuilder::ethernet2(context.key.local_mac.0, context.key.client_mac.0);
let packet = match (context.key.external_ip.addr, context.key.client_ip.addr) {
(IpAddress::Ipv6(external_addr), IpAddress::Ipv6(client_addr)) => {
packet.ipv6(external_addr.0, client_addr.0, 20)
}
_ => {
return Err(anyhow!("IP endpoint mismatch"));
}
};
let packet = packet.icmpv6_echo_reply(echo.id, echo.seq);
let buffer = BytesMut::with_capacity(packet.size(payload.len()));
let mut writer = buffer.writer();
packet.write(&mut writer, &payload)?;
let buffer = writer.into_inner();
if let Err(error) = context.try_transmit(buffer) {
debug!("failed to transmit icmp packet: {}", error);
}
Ok(())
}
}

View File

@ -1,77 +0,0 @@
use async_trait::async_trait;
use bytes::BytesMut;
use log::warn;
use tokio::sync::mpsc::channel;
use crate::proxynat::udp::ProxyUdpHandler;
use crate::nat::handler::{NatHandler, NatHandlerContext, NatHandlerFactory};
use crate::nat::key::NatKeyProtocol;
use self::icmp::ProxyIcmpHandler;
use self::tcp::ProxyTcpHandler;
mod icmp;
mod tcp;
mod udp;
const RX_CHANNEL_QUEUE_LEN: usize = 1000;
pub struct ProxyNatHandlerFactory {}
impl Default for ProxyNatHandlerFactory {
fn default() -> Self {
Self::new()
}
}
impl ProxyNatHandlerFactory {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl NatHandlerFactory for ProxyNatHandlerFactory {
async fn nat(&self, context: NatHandlerContext) -> Option<Box<dyn NatHandler>> {
match context.key.protocol {
NatKeyProtocol::Udp => {
let (rx_sender, rx_receiver) = channel::<BytesMut>(RX_CHANNEL_QUEUE_LEN);
let mut handler = ProxyUdpHandler::new(rx_sender);
if let Err(error) = handler.spawn(context, rx_receiver).await {
warn!("unable to spawn udp proxy handler: {}", error);
None
} else {
Some(Box::new(handler))
}
}
NatKeyProtocol::Icmp => {
let (rx_sender, rx_receiver) = channel::<BytesMut>(RX_CHANNEL_QUEUE_LEN);
let mut handler = ProxyIcmpHandler::new(rx_sender);
if let Err(error) = handler.spawn(context, rx_receiver).await {
warn!("unable to spawn icmp proxy handler: {}", error);
None
} else {
Some(Box::new(handler))
}
}
NatKeyProtocol::Tcp => {
let (rx_sender, rx_receiver) = channel::<BytesMut>(RX_CHANNEL_QUEUE_LEN);
let mut handler = ProxyTcpHandler::new(rx_sender);
if let Err(error) = handler.spawn(context, rx_receiver).await {
warn!("unable to spawn tcp proxy handler: {}", error);
None
} else {
Some(Box::new(handler))
}
}
}
}
}

View File

@ -1,466 +0,0 @@
use std::{
net::{IpAddr, SocketAddr},
time::Duration,
};
use anyhow::Result;
use async_trait::async_trait;
use bytes::BytesMut;
use etherparse::{EtherType, Ethernet2Header};
use log::{debug, warn};
use smoltcp::{
iface::{Config, Interface, SocketSet, SocketStorage},
phy::Medium,
socket::tcp::{self, SocketBuffer, State},
time::Instant,
wire::{HardwareAddress, IpAddress, IpCidr},
};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
select,
sync::mpsc::channel,
};
use tokio::{sync::mpsc::Receiver, sync::mpsc::Sender};
use crate::{
chandev::ChannelDevice,
nat::handler::{NatHandler, NatHandlerContext},
};
const TCP_BUFFER_SIZE: usize = 65535;
const TCP_IP_BUFFER_QUEUE_LEN: usize = 3000;
const TCP_ACCEPT_TIMEOUT_SECS: u64 = 120;
const TCP_DANGLE_TIMEOUT_SECS: u64 = 10;
pub struct ProxyTcpHandler {
rx_sender: Sender<BytesMut>,
}
#[async_trait]
impl NatHandler for ProxyTcpHandler {
async fn receive(&self, data: &[u8]) -> Result<bool> {
if self.rx_sender.is_closed() {
Ok(false)
} else {
self.rx_sender.try_send(data.into())?;
Ok(true)
}
}
}
#[derive(Debug)]
enum ProxyTcpAcceptSelect {
Internal(BytesMut),
TxIpPacket(BytesMut),
TimePassed,
DoNothing,
Close,
}
#[derive(Debug)]
enum ProxyTcpDataSelect {
ExternalRecv(usize),
ExternalSent(usize),
InternalRecv(BytesMut),
TxIpPacket(BytesMut),
TimePassed,
DoNothing,
Close,
}
#[derive(Debug)]
enum ProxyTcpFinishSelect {
InternalRecv(BytesMut),
TxIpPacket(BytesMut),
Close,
}
impl ProxyTcpHandler {
pub fn new(rx_sender: Sender<BytesMut>) -> Self {
ProxyTcpHandler { rx_sender }
}
pub async fn spawn(
&mut self,
context: NatHandlerContext,
rx_receiver: Receiver<BytesMut>,
) -> Result<()> {
let external_addr = match context.key.external_ip.addr {
IpAddress::Ipv4(addr) => {
SocketAddr::new(IpAddr::V4(addr.0.into()), context.key.external_ip.port)
}
IpAddress::Ipv6(addr) => {
SocketAddr::new(IpAddr::V6(addr.0.into()), context.key.external_ip.port)
}
};
let socket = TcpStream::connect(external_addr).await?;
tokio::spawn(async move {
if let Err(error) = ProxyTcpHandler::process(context, socket, rx_receiver).await {
warn!("processing of tcp proxy failed: {}", error);
}
});
Ok(())
}
async fn process(
context: NatHandlerContext,
mut external_socket: TcpStream,
mut rx_receiver: Receiver<BytesMut>,
) -> Result<()> {
let (ip_sender, mut ip_receiver) = channel::<BytesMut>(TCP_IP_BUFFER_QUEUE_LEN);
let mut external_buffer = vec![0u8; TCP_BUFFER_SIZE];
let mut device = ChannelDevice::new(
context.mtu - Ethernet2Header::LEN,
Medium::Ip,
ip_sender.clone(),
);
let config = Config::new(HardwareAddress::Ip);
let tcp_rx_buffer = SocketBuffer::new(vec![0; TCP_BUFFER_SIZE]);
let tcp_tx_buffer = SocketBuffer::new(vec![0; TCP_BUFFER_SIZE]);
let internal_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer);
let mut iface = Interface::new(config, &mut device, Instant::now());
iface.update_ip_addrs(|addrs| {
let _ = addrs.push(IpCidr::new(context.key.external_ip.addr, 0));
});
let mut sockets = SocketSet::new([SocketStorage::EMPTY]);
let internal_socket_handle = sockets.add(internal_socket);
let (mut external_r, mut external_w) = external_socket.split();
{
let socket = sockets.get_mut::<tcp::Socket>(internal_socket_handle);
socket.connect(
iface.context(),
context.key.client_ip,
context.key.external_ip,
)?;
}
iface.poll(Instant::now(), &mut device, &mut sockets);
let mut sleeper: Option<tokio::time::Sleep> = None;
loop {
let socket = sockets.get_mut::<tcp::Socket>(internal_socket_handle);
if socket.is_active() && socket.state() != State::SynSent {
break;
}
if socket.state() == State::Closed {
break;
}
let deadline = tokio::time::sleep(Duration::from_secs(TCP_ACCEPT_TIMEOUT_SECS));
let selection = if let Some(sleep) = sleeper.take() {
select! {
biased;
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpAcceptSelect::Internal(data)
} else {
ProxyTcpAcceptSelect::Close
},
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpAcceptSelect::TxIpPacket(data)
} else {
ProxyTcpAcceptSelect::Close
},
_ = sleep => ProxyTcpAcceptSelect::TimePassed,
_ = deadline => ProxyTcpAcceptSelect::Close,
}
} else {
select! {
biased;
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpAcceptSelect::Internal(data)
} else {
ProxyTcpAcceptSelect::Close
},
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpAcceptSelect::TxIpPacket(data)
} else {
ProxyTcpAcceptSelect::Close
},
_ = std::future::ready(()) => ProxyTcpAcceptSelect::DoNothing,
_ = deadline => ProxyTcpAcceptSelect::Close,
}
};
match selection {
ProxyTcpAcceptSelect::TimePassed => {
iface.poll(Instant::now(), &mut device, &mut sockets);
}
ProxyTcpAcceptSelect::DoNothing => {
sleeper = Some(tokio::time::sleep(Duration::from_micros(100)));
}
ProxyTcpAcceptSelect::Internal(data) => {
let (_, payload) = Ethernet2Header::from_slice(&data)?;
device.rx = Some(payload.into());
iface.poll(Instant::now(), &mut device, &mut sockets);
}
ProxyTcpAcceptSelect::TxIpPacket(payload) => {
let mut buffer = BytesMut::with_capacity(Ethernet2Header::LEN + payload.len());
let header = Ethernet2Header {
source: context.key.local_mac.0,
destination: context.key.client_mac.0,
ether_type: match context.key.external_ip.addr {
IpAddress::Ipv4(_) => EtherType::IPV4,
IpAddress::Ipv6(_) => EtherType::IPV6,
},
};
buffer.extend_from_slice(&header.to_bytes());
buffer.extend_from_slice(&payload);
if let Err(error) = context.try_transmit(buffer) {
debug!("failed to transmit tcp packet: {}", error);
}
}
ProxyTcpAcceptSelect::Close => {
break;
}
}
}
let accepted = if sockets
.get_mut::<tcp::Socket>(internal_socket_handle)
.is_active()
{
true
} else {
debug!("failed to accept tcp connection from client");
false
};
let mut already_shutdown = false;
let mut sleeper: Option<tokio::time::Sleep> = None;
loop {
if !accepted {
break;
}
let socket = sockets.get_mut::<tcp::Socket>(internal_socket_handle);
match socket.state() {
State::Closed
| State::Listen
| State::Closing
| State::LastAck
| State::TimeWait => {
break;
}
State::FinWait1
| State::SynSent
| State::CloseWait
| State::FinWait2
| State::SynReceived
| State::Established => {}
}
let bytes_to_client = if socket.can_send() {
socket.send_capacity() - socket.send_queue()
} else {
0
};
let (bytes_to_external, do_shutdown) = if socket.may_recv() {
if let Ok(data) = socket.peek(TCP_BUFFER_SIZE) {
if data.is_empty() {
(None, false)
} else {
(Some(data), false)
}
} else {
(None, false)
}
} else if !already_shutdown && matches!(socket.state(), State::CloseWait) {
(None, true)
} else {
(None, false)
};
let selection = if let Some(sleep) = sleeper.take() {
if !do_shutdown {
select! {
biased;
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::TxIpPacket(data)
} else {
ProxyTcpDataSelect::Close
},
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::InternalRecv(data)
} else {
ProxyTcpDataSelect::Close
},
x = external_w.write(bytes_to_external.unwrap_or(b"")), if bytes_to_external.is_some() => ProxyTcpDataSelect::ExternalSent(x?),
x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?),
_ = sleep => ProxyTcpDataSelect::TimePassed,
}
} else {
select! {
biased;
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::TxIpPacket(data)
} else {
ProxyTcpDataSelect::Close
},
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::InternalRecv(data)
} else {
ProxyTcpDataSelect::Close
},
_ = external_w.shutdown() => ProxyTcpDataSelect::ExternalSent(0),
x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?),
_ = sleep => ProxyTcpDataSelect::TimePassed,
}
}
} else if !do_shutdown {
select! {
biased;
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::TxIpPacket(data)
} else {
ProxyTcpDataSelect::Close
},
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::InternalRecv(data)
} else {
ProxyTcpDataSelect::Close
},
x = external_w.write(bytes_to_external.unwrap_or(b"")), if bytes_to_external.is_some() => ProxyTcpDataSelect::ExternalSent(x?),
x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?),
_ = std::future::ready(()) => ProxyTcpDataSelect::DoNothing,
}
} else {
select! {
biased;
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::TxIpPacket(data)
} else {
ProxyTcpDataSelect::Close
},
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpDataSelect::InternalRecv(data)
} else {
ProxyTcpDataSelect::Close
},
_ = external_w.shutdown() => ProxyTcpDataSelect::ExternalSent(0),
x = external_r.read(&mut external_buffer[..bytes_to_client]), if bytes_to_client > 0 => ProxyTcpDataSelect::ExternalRecv(x?),
_ = std::future::ready(()) => ProxyTcpDataSelect::DoNothing,
}
};
match selection {
ProxyTcpDataSelect::ExternalRecv(size) => {
if size == 0 {
socket.close();
} else {
socket.send_slice(&external_buffer[..size])?;
}
}
ProxyTcpDataSelect::ExternalSent(size) => {
if size == 0 {
already_shutdown = true;
} else {
socket.recv(|_| (size, ()))?;
}
}
ProxyTcpDataSelect::InternalRecv(data) => {
let (_, payload) = Ethernet2Header::from_slice(&data)?;
device.rx = Some(payload.into());
iface.poll(Instant::now(), &mut device, &mut sockets);
}
ProxyTcpDataSelect::TxIpPacket(payload) => {
let mut buffer = BytesMut::with_capacity(Ethernet2Header::LEN + payload.len());
let header = Ethernet2Header {
source: context.key.local_mac.0,
destination: context.key.client_mac.0,
ether_type: match context.key.external_ip.addr {
IpAddress::Ipv4(_) => EtherType::IPV4,
IpAddress::Ipv6(_) => EtherType::IPV6,
},
};
buffer.extend_from_slice(&header.to_bytes());
buffer.extend_from_slice(&payload);
if let Err(error) = context.try_transmit(buffer) {
debug!("failed to transmit tcp packet: {}", error);
}
}
ProxyTcpDataSelect::TimePassed => {
iface.poll(Instant::now(), &mut device, &mut sockets);
}
ProxyTcpDataSelect::DoNothing => {
sleeper = Some(tokio::time::sleep(Duration::from_micros(100)));
}
ProxyTcpDataSelect::Close => {
break;
}
}
}
let _ = external_socket.shutdown().await;
drop(external_socket);
loop {
let deadline = tokio::time::sleep(Duration::from_secs(TCP_DANGLE_TIMEOUT_SECS));
tokio::pin!(deadline);
let selection = select! {
biased;
x = ip_receiver.recv() => if let Some(data) = x {
ProxyTcpFinishSelect::TxIpPacket(data)
} else {
ProxyTcpFinishSelect::Close
},
x = rx_receiver.recv() => if let Some(data) = x {
ProxyTcpFinishSelect::InternalRecv(data)
} else {
ProxyTcpFinishSelect::Close
},
_ = deadline => ProxyTcpFinishSelect::Close,
};
match selection {
ProxyTcpFinishSelect::InternalRecv(data) => {
let (_, payload) = Ethernet2Header::from_slice(&data)?;
device.rx = Some(payload.into());
iface.poll(Instant::now(), &mut device, &mut sockets);
}
ProxyTcpFinishSelect::TxIpPacket(payload) => {
let mut buffer = BytesMut::with_capacity(Ethernet2Header::LEN + payload.len());
let header = Ethernet2Header {
source: context.key.local_mac.0,
destination: context.key.client_mac.0,
ether_type: match context.key.external_ip.addr {
IpAddress::Ipv4(_) => EtherType::IPV4,
IpAddress::Ipv6(_) => EtherType::IPV6,
},
};
buffer.extend_from_slice(&header.to_bytes());
buffer.extend_from_slice(&payload);
if let Err(error) = context.try_transmit(buffer) {
debug!("failed to transmit tcp packet: {}", error);
}
}
ProxyTcpFinishSelect::Close => {
break;
}
}
}
context.reclaim().await?;
Ok(())
}
}

View File

@ -1,142 +0,0 @@
use std::{
net::{IpAddr, SocketAddr},
time::Duration,
};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use bytes::{BufMut, BytesMut};
use etherparse::{PacketBuilder, SlicedPacket, UdpSlice};
use log::{debug, warn};
use smoltcp::wire::IpAddress;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
select,
};
use tokio::{sync::mpsc::Receiver, sync::mpsc::Sender};
use udp_stream::UdpStream;
use crate::nat::handler::{NatHandler, NatHandlerContext};
const UDP_TIMEOUT_SECS: u64 = 60;
pub struct ProxyUdpHandler {
rx_sender: Sender<BytesMut>,
}
#[async_trait]
impl NatHandler for ProxyUdpHandler {
async fn receive(&self, data: &[u8]) -> Result<bool> {
if self.rx_sender.is_closed() {
Ok(true)
} else {
self.rx_sender.try_send(data.into())?;
Ok(true)
}
}
}
enum ProxyUdpSelect {
External(usize),
Internal(BytesMut),
Close,
}
impl ProxyUdpHandler {
pub fn new(rx_sender: Sender<BytesMut>) -> Self {
ProxyUdpHandler { rx_sender }
}
pub async fn spawn(
&mut self,
context: NatHandlerContext,
rx_receiver: Receiver<BytesMut>,
) -> Result<()> {
let external_addr = match context.key.external_ip.addr {
IpAddress::Ipv4(addr) => {
SocketAddr::new(IpAddr::V4(addr.0.into()), context.key.external_ip.port)
}
IpAddress::Ipv6(addr) => {
SocketAddr::new(IpAddr::V6(addr.0.into()), context.key.external_ip.port)
}
};
let socket = UdpStream::connect(external_addr).await?;
tokio::spawn(async move {
if let Err(error) = ProxyUdpHandler::process(context, socket, rx_receiver).await {
warn!("processing of udp proxy failed: {}", error);
}
});
Ok(())
}
async fn process(
context: NatHandlerContext,
mut socket: UdpStream,
mut rx_receiver: Receiver<BytesMut>,
) -> Result<()> {
let mut external_buffer = vec![0u8; 2048];
loop {
let deadline = tokio::time::sleep(Duration::from_secs(UDP_TIMEOUT_SECS));
let selection = select! {
x = rx_receiver.recv() => if let Some(data) = x {
ProxyUdpSelect::Internal(data)
} else {
ProxyUdpSelect::Close
},
x = socket.read(&mut external_buffer) => ProxyUdpSelect::External(x?),
_ = deadline => ProxyUdpSelect::Close,
};
match selection {
ProxyUdpSelect::External(size) => {
let data = &external_buffer[0..size];
let packet =
PacketBuilder::ethernet2(context.key.local_mac.0, context.key.client_mac.0);
let packet = match (context.key.external_ip.addr, context.key.client_ip.addr) {
(IpAddress::Ipv4(external_addr), IpAddress::Ipv4(client_addr)) => {
packet.ipv4(external_addr.0, client_addr.0, 20)
}
(IpAddress::Ipv6(external_addr), IpAddress::Ipv6(client_addr)) => {
packet.ipv6(external_addr.0, client_addr.0, 20)
}
_ => {
return Err(anyhow!("IP endpoint mismatch"));
}
};
let packet =
packet.udp(context.key.external_ip.port, context.key.client_ip.port);
let buffer = BytesMut::with_capacity(packet.size(data.len()));
let mut writer = buffer.writer();
packet.write(&mut writer, data)?;
let buffer = writer.into_inner();
if let Err(error) = context.try_transmit(buffer) {
debug!("failed to transmit udp packet: {}", error);
}
}
ProxyUdpSelect::Internal(data) => {
let packet = SlicedPacket::from_ethernet(&data)?;
let Some(ref net) = packet.net else {
continue;
};
let Some(ip) = net.ip_payload_ref() else {
continue;
};
let udp = UdpSlice::from_slice(ip.payload)?;
socket.write_all(udp.payload()).await?;
}
ProxyUdpSelect::Close => {
drop(socket);
break;
}
}
}
context.reclaim().await?;
Ok(())
}
}

View File

@ -1,317 +0,0 @@
use anyhow::{anyhow, Result};
use bytes::BytesMut;
use log::{debug, warn};
use std::io::ErrorKind;
use std::os::fd::{FromRawFd, IntoRawFd};
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::Arc;
use std::{io, mem};
use tokio::net::UdpSocket;
use tokio::select;
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::task::JoinHandle;
const RAW_SOCKET_TRANSMIT_QUEUE_LEN: usize = 3000;
const RAW_SOCKET_RECEIVE_QUEUE_LEN: usize = 3000;
#[derive(Debug)]
pub enum RawSocketProtocol {
Icmpv4,
Icmpv6,
Ethernet,
}
impl RawSocketProtocol {
pub fn to_socket_domain(&self) -> i32 {
match self {
RawSocketProtocol::Icmpv4 => libc::AF_INET,
RawSocketProtocol::Icmpv6 => libc::AF_INET6,
RawSocketProtocol::Ethernet => libc::AF_PACKET,
}
}
pub fn to_socket_protocol(&self) -> u16 {
match self {
RawSocketProtocol::Icmpv4 => libc::IPPROTO_ICMP as u16,
RawSocketProtocol::Icmpv6 => libc::IPPROTO_ICMPV6 as u16,
RawSocketProtocol::Ethernet => (libc::ETH_P_ALL as u16).to_be(),
}
}
pub fn to_socket_type(&self) -> i32 {
libc::SOCK_RAW
}
}
const SIOCGIFINDEX: libc::c_ulong = 0x8933;
const SIOCGIFMTU: libc::c_ulong = 0x8921;
#[derive(Debug)]
pub struct RawSocketHandle {
protocol: RawSocketProtocol,
lower: libc::c_int,
}
impl AsRawFd for RawSocketHandle {
fn as_raw_fd(&self) -> RawFd {
self.lower
}
}
impl IntoRawFd for RawSocketHandle {
fn into_raw_fd(self) -> RawFd {
let fd = self.lower;
mem::forget(self);
fd
}
}
impl RawSocketHandle {
pub fn new(protocol: RawSocketProtocol) -> io::Result<RawSocketHandle> {
let lower = unsafe {
let lower = libc::socket(
protocol.to_socket_domain(),
protocol.to_socket_type() | libc::SOCK_NONBLOCK,
protocol.to_socket_protocol() as i32,
);
if lower == -1 {
return Err(io::Error::last_os_error());
}
lower
};
Ok(RawSocketHandle { protocol, lower })
}
pub fn bound_to_interface(interface: &str, protocol: RawSocketProtocol) -> Result<Self> {
let mut socket = RawSocketHandle::new(protocol)?;
socket.bind_to_interface(interface)?;
Ok(socket)
}
pub fn bind_to_interface(&mut self, interface: &str) -> io::Result<()> {
let mut ifreq = ifreq_for(interface);
let sockaddr = libc::sockaddr_ll {
sll_family: libc::AF_PACKET as u16,
sll_protocol: self.protocol.to_socket_protocol(),
sll_ifindex: ifreq_ioctl(self.lower, &mut ifreq, SIOCGIFINDEX)?,
sll_hatype: 1,
sll_pkttype: 0,
sll_halen: 6,
sll_addr: [0; 8],
};
unsafe {
let res = libc::bind(
self.lower,
&sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr,
mem::size_of::<libc::sockaddr_ll>() as libc::socklen_t,
);
if res == -1 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
pub fn mtu_of_interface(&mut self, interface: &str) -> io::Result<usize> {
let mut ifreq = ifreq_for(interface);
ifreq_ioctl(self.lower, &mut ifreq, SIOCGIFMTU).map(|mtu| mtu as usize)
}
pub fn recv(&self, buffer: &mut [u8]) -> io::Result<usize> {
unsafe {
let len = libc::recv(
self.lower,
buffer.as_mut_ptr() as *mut libc::c_void,
buffer.len(),
0,
);
if len == -1 {
return Err(io::Error::last_os_error());
}
Ok(len as usize)
}
}
pub fn send(&self, buffer: &[u8]) -> io::Result<usize> {
unsafe {
let len = libc::send(
self.lower,
buffer.as_ptr() as *const libc::c_void,
buffer.len(),
0,
);
if len == -1 {
return Err(io::Error::last_os_error());
}
Ok(len as usize)
}
}
}
impl Drop for RawSocketHandle {
fn drop(&mut self) {
unsafe {
libc::close(self.lower);
}
}
}
#[repr(C)]
#[derive(Debug)]
struct Ifreq {
ifr_name: [libc::c_char; libc::IF_NAMESIZE],
ifr_data: libc::c_int,
}
fn ifreq_for(name: &str) -> Ifreq {
let mut ifreq = Ifreq {
ifr_name: [0; libc::IF_NAMESIZE],
ifr_data: 0,
};
for (i, byte) in name.as_bytes().iter().enumerate() {
ifreq.ifr_name[i] = *byte as libc::c_char
}
ifreq
}
fn ifreq_ioctl(
lower: libc::c_int,
ifreq: &mut Ifreq,
cmd: libc::c_ulong,
) -> io::Result<libc::c_int> {
unsafe {
let res = libc::ioctl(lower, cmd as _, ifreq as *mut Ifreq);
if res == -1 {
return Err(io::Error::last_os_error());
}
}
Ok(ifreq.ifr_data)
}
pub struct AsyncRawSocketChannel {
pub sender: Sender<BytesMut>,
pub receiver: Receiver<BytesMut>,
_task: Arc<JoinHandle<()>>,
}
enum AsyncRawSocketChannelSelect {
TransmitPacket(Option<BytesMut>),
Readable(()),
}
impl AsyncRawSocketChannel {
pub fn new(mtu: usize, socket: RawSocketHandle) -> Result<AsyncRawSocketChannel> {
let (transmit_sender, transmit_receiver) = channel(RAW_SOCKET_TRANSMIT_QUEUE_LEN);
let (receive_sender, receive_receiver) = channel(RAW_SOCKET_RECEIVE_QUEUE_LEN);
let task = AsyncRawSocketChannel::launch(mtu, socket, transmit_receiver, receive_sender)?;
Ok(AsyncRawSocketChannel {
sender: transmit_sender,
receiver: receive_receiver,
_task: Arc::new(task),
})
}
fn launch(
mtu: usize,
socket: RawSocketHandle,
transmit_receiver: Receiver<BytesMut>,
receive_sender: Sender<BytesMut>,
) -> Result<JoinHandle<()>> {
Ok(tokio::task::spawn(async move {
if let Err(error) =
AsyncRawSocketChannel::process(mtu, socket, transmit_receiver, receive_sender).await
{
warn!("failed to process raw socket: {}", error);
}
}))
}
async fn process(
mtu: usize,
socket: RawSocketHandle,
mut transmit_receiver: Receiver<BytesMut>,
receive_sender: Sender<BytesMut>,
) -> Result<()> {
let socket = unsafe { std::net::UdpSocket::from_raw_fd(socket.into_raw_fd()) };
let socket = UdpSocket::from_std(socket)?;
let tear_off_size = 100 * mtu;
let mut buffer: BytesMut = BytesMut::with_capacity(tear_off_size);
loop {
if buffer.capacity() < mtu {
buffer = BytesMut::with_capacity(tear_off_size);
}
let selection = select! {
x = transmit_receiver.recv() => AsyncRawSocketChannelSelect::TransmitPacket(x),
x = socket.readable() => AsyncRawSocketChannelSelect::Readable(x?),
};
match selection {
AsyncRawSocketChannelSelect::Readable(_) => {
buffer.resize(mtu, 0);
match socket.try_recv(&mut buffer) {
Ok(len) => {
if len == 0 {
continue;
}
let packet = buffer.split_to(len);
if let Err(error) = receive_sender.try_send(packet) {
debug!(
"failed to process received packet from raw socket: {}",
error
);
}
}
Err(ref error) => {
if error.kind() == ErrorKind::WouldBlock {
continue;
}
// device no longer exists
if error.raw_os_error() == Some(6) {
break;
}
return Err(anyhow!("failed to read from raw socket: {}", error));
}
};
}
AsyncRawSocketChannelSelect::TransmitPacket(Some(packet)) => {
match socket.try_send(&packet) {
Ok(_len) => {}
Err(ref error) => {
if error.kind() == ErrorKind::WouldBlock {
debug!("failed to transmit: would block");
continue;
}
// device no longer exists
if error.raw_os_error() == Some(6) {
break;
}
return Err(anyhow!(
"failed to write {} bytes to raw socket: {}",
packet.len(),
error
));
}
};
}
AsyncRawSocketChannelSelect::TransmitPacket(None) => {
break;
}
}
}
Ok(())
}
}

View File

@ -1,208 +0,0 @@
use anyhow::{anyhow, Result};
use bytes::BytesMut;
use etherparse::{EtherType, Ethernet2Header, IpNumber, Ipv4Header, Ipv6Header, TcpHeader};
use log::{debug, trace, warn};
use smoltcp::wire::EthernetAddress;
use std::{
collections::{hash_map::Entry, HashMap},
sync::Arc,
};
use tokio::sync::broadcast::{
channel as broadcast_channel, Receiver as BroadcastReceiver, Sender as BroadcastSender,
};
use tokio::{
select,
sync::{
mpsc::{channel, Receiver, Sender},
Mutex,
},
task::JoinHandle,
};
const TO_BRIDGE_QUEUE_LEN: usize = 3000;
const FROM_BRIDGE_QUEUE_LEN: usize = 3000;
const BROADCAST_QUEUE_LEN: usize = 3000;
const MEMBER_LEAVE_QUEUE_LEN: usize = 30;
#[derive(Debug)]
struct BridgeMember {
pub from_bridge_sender: Sender<BytesMut>,
}
pub struct BridgeJoinHandle {
mac: EthernetAddress,
pub to_bridge_sender: Sender<BytesMut>,
pub from_bridge_receiver: Receiver<BytesMut>,
pub from_broadcast_receiver: BroadcastReceiver<BytesMut>,
member_leave_sender: Sender<EthernetAddress>,
}
impl Drop for BridgeJoinHandle {
fn drop(&mut self) {
if let Err(error) = self.member_leave_sender.try_send(self.mac) {
warn!(
"virtual bridge member {} failed to leave: {}",
self.mac, error
);
}
}
}
type VirtualBridgeMemberMap = Arc<Mutex<HashMap<EthernetAddress, BridgeMember>>>;
#[derive(Clone)]
pub struct VirtualBridge {
to_bridge_sender: Sender<BytesMut>,
from_broadcast_sender: BroadcastSender<BytesMut>,
member_leave_sender: Sender<EthernetAddress>,
members: VirtualBridgeMemberMap,
_task: Arc<JoinHandle<()>>,
}
enum VirtualBridgeSelect {
BroadcastSent,
PacketReceived(Option<BytesMut>),
MemberLeave(Option<EthernetAddress>),
}
impl VirtualBridge {
pub fn new() -> Result<VirtualBridge> {
let (to_bridge_sender, to_bridge_receiver) = channel::<BytesMut>(TO_BRIDGE_QUEUE_LEN);
let (member_leave_sender, member_leave_reciever) =
channel::<EthernetAddress>(MEMBER_LEAVE_QUEUE_LEN);
let (from_broadcast_sender, from_broadcast_receiver) =
broadcast_channel(BROADCAST_QUEUE_LEN);
let members = Arc::new(Mutex::new(HashMap::new()));
let handle = {
let members = members.clone();
let broadcast_rx_sender = from_broadcast_sender.clone();
tokio::task::spawn(async move {
if let Err(error) = VirtualBridge::process(
members,
member_leave_reciever,
to_bridge_receiver,
broadcast_rx_sender,
from_broadcast_receiver,
)
.await
{
warn!("virtual bridge processing task failed: {}", error);
}
})
};
Ok(VirtualBridge {
to_bridge_sender,
from_broadcast_sender,
member_leave_sender,
members,
_task: Arc::new(handle),
})
}
pub async fn join(&self, mac: EthernetAddress) -> Result<BridgeJoinHandle> {
let (from_bridge_sender, from_bridge_receiver) = channel::<BytesMut>(FROM_BRIDGE_QUEUE_LEN);
let member = BridgeMember { from_bridge_sender };
match self.members.lock().await.entry(mac) {
Entry::Occupied(_) => {
return Err(anyhow!("virtual bridge member {} already exists", mac));
}
Entry::Vacant(entry) => {
entry.insert(member);
}
};
debug!("virtual bridge member {} has joined", mac);
Ok(BridgeJoinHandle {
mac,
member_leave_sender: self.member_leave_sender.clone(),
from_bridge_receiver,
from_broadcast_receiver: self.from_broadcast_sender.subscribe(),
to_bridge_sender: self.to_bridge_sender.clone(),
})
}
async fn process(
members: VirtualBridgeMemberMap,
mut member_leave_reciever: Receiver<EthernetAddress>,
mut to_bridge_receiver: Receiver<BytesMut>,
broadcast_rx_sender: BroadcastSender<BytesMut>,
mut from_broadcast_receiver: BroadcastReceiver<BytesMut>,
) -> Result<()> {
loop {
let selection = select! {
biased;
x = to_bridge_receiver.recv() => VirtualBridgeSelect::PacketReceived(x),
_ = from_broadcast_receiver.recv() => VirtualBridgeSelect::BroadcastSent,
x = member_leave_reciever.recv() => VirtualBridgeSelect::MemberLeave(x),
};
match selection {
VirtualBridgeSelect::PacketReceived(Some(mut packet)) => {
let (header, payload) = match Ethernet2Header::from_slice(&packet) {
Ok(data) => data,
Err(error) => {
debug!("virtual bridge failed to parse ethernet header: {}", error);
continue;
}
};
// recalculate TCP checksums when routing packets.
// the xen network backend / frontend drivers for linux
// use checksum offloading but since we bypass some layers
// of the kernel we have to do it ourselves.
if header.ether_type == EtherType::IPV4 {
let (ipv4, payload) = Ipv4Header::from_slice(payload)?;
if ipv4.protocol == IpNumber::TCP {
let (mut tcp, payload) = TcpHeader::from_slice(payload)?;
tcp.checksum = tcp.calc_checksum_ipv4(&ipv4, payload)?;
let tcp_header_offset = Ethernet2Header::LEN + ipv4.header_len();
let mut header = &mut packet[tcp_header_offset..];
tcp.write(&mut header)?;
}
} else if header.ether_type == EtherType::IPV6 {
let (ipv6, payload) = Ipv6Header::from_slice(payload)?;
if ipv6.next_header == IpNumber::TCP {
let (mut tcp, payload) = TcpHeader::from_slice(payload)?;
tcp.checksum = tcp.calc_checksum_ipv6(&ipv6, payload)?;
let tcp_header_offset = Ethernet2Header::LEN + ipv6.header_len();
let mut header = &mut packet[tcp_header_offset..];
tcp.write(&mut header)?;
}
}
let destination = EthernetAddress(header.destination);
if destination.is_multicast() {
broadcast_rx_sender.send(packet)?;
continue;
}
match members.lock().await.get(&destination) {
Some(member) => {
member.from_bridge_sender.try_send(packet)?;
trace!(
"sending bridged packet from {} to {}",
EthernetAddress(header.source),
EthernetAddress(header.destination)
);
}
None => {
trace!("no bridge member with address: {}", destination);
}
}
}
VirtualBridgeSelect::MemberLeave(Some(mac)) => {
if members.lock().await.remove(&mac).is_some() {
debug!("virtual bridge member {} has left", mac);
}
}
VirtualBridgeSelect::PacketReceived(None) => break,
VirtualBridgeSelect::MemberLeave(None) => {}
VirtualBridgeSelect::BroadcastSent => {}
}
}
Ok(())
}
}

View File

@ -1,39 +0,0 @@
[package]
name = "krata-oci"
description = "OCI services for the krata hypervisor."
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[dependencies]
anyhow = { workspace = true }
async-compression = { workspace = true, features = ["tokio", "gzip", "zstd"] }
async-trait = { workspace = true }
backhand = { workspace = true }
bytes = { workspace = true }
krata-tokio-tar = { workspace = true }
log = { workspace = true }
oci-spec = { workspace = true }
path-clean = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha256 = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
url = { workspace = true }
uuid = { workspace = true }
walkdir = { workspace = true }
[lib]
name = "krataoci"
[dev-dependencies]
env_logger = { workspace = true }
[[example]]
name = "krataoci-squashify"
path = "examples/squashify.rs"

View File

@ -1,29 +0,0 @@
use std::{env::args, path::PathBuf};
use anyhow::Result;
use env_logger::Env;
use krataoci::{cache::ImageCache, compiler::ImageCompiler, name::ImageName};
use tokio::fs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let image = ImageName::parse(&args().nth(1).unwrap())?;
let seed = args().nth(2).map(PathBuf::from);
let cache_dir = PathBuf::from("krata-cache");
if !cache_dir.exists() {
fs::create_dir(&cache_dir).await?;
}
let cache = ImageCache::new(&cache_dir)?;
let compiler = ImageCompiler::new(&cache, seed)?;
let info = compiler.compile(&image).await?;
println!(
"generated squashfs of {} to {}",
image,
info.image_squashfs.to_string_lossy()
);
Ok(())
}

View File

@ -1,71 +0,0 @@
use super::compiler::ImageInfo;
use anyhow::Result;
use log::debug;
use oci_spec::image::{ImageConfiguration, ImageManifest};
use std::path::{Path, PathBuf};
use tokio::fs;
#[derive(Clone)]
pub struct ImageCache {
cache_dir: PathBuf,
}
impl ImageCache {
pub fn new(cache_dir: &Path) -> Result<ImageCache> {
Ok(ImageCache {
cache_dir: cache_dir.to_path_buf(),
})
}
pub async fn recall(&self, digest: &str) -> Result<Option<ImageInfo>> {
let mut squashfs_path = self.cache_dir.clone();
let mut config_path = self.cache_dir.clone();
let mut manifest_path = self.cache_dir.clone();
squashfs_path.push(format!("{}.squashfs", digest));
manifest_path.push(format!("{}.manifest.json", digest));
config_path.push(format!("{}.config.json", digest));
Ok(
if squashfs_path.exists() && manifest_path.exists() && config_path.exists() {
let squashfs_metadata = fs::metadata(&squashfs_path).await?;
let manifest_metadata = fs::metadata(&manifest_path).await?;
let config_metadata = fs::metadata(&config_path).await?;
if squashfs_metadata.is_file()
&& manifest_metadata.is_file()
&& config_metadata.is_file()
{
let manifest_text = fs::read_to_string(&manifest_path).await?;
let manifest: ImageManifest = serde_json::from_str(&manifest_text)?;
let config_text = fs::read_to_string(&config_path).await?;
let config: ImageConfiguration = serde_json::from_str(&config_text)?;
debug!("cache hit digest={}", digest);
Some(ImageInfo::new(squashfs_path.clone(), manifest, config)?)
} else {
None
}
} else {
debug!("cache miss digest={}", digest);
None
},
)
}
pub async fn store(&self, digest: &str, info: &ImageInfo) -> Result<ImageInfo> {
debug!("cache store digest={}", digest);
let mut squashfs_path = self.cache_dir.clone();
let mut manifest_path = self.cache_dir.clone();
let mut config_path = self.cache_dir.clone();
squashfs_path.push(format!("{}.squashfs", digest));
manifest_path.push(format!("{}.manifest.json", digest));
config_path.push(format!("{}.config.json", digest));
fs::copy(&info.image_squashfs, &squashfs_path).await?;
let manifest_text = serde_json::to_string_pretty(&info.manifest)?;
fs::write(&manifest_path, manifest_text).await?;
let config_text = serde_json::to_string_pretty(&info.config)?;
fs::write(&config_path, config_text).await?;
ImageInfo::new(
squashfs_path.clone(),
info.manifest.clone(),
info.config.clone(),
)
}
}

View File

@ -1,411 +0,0 @@
use crate::cache::ImageCache;
use crate::fetch::{OciImageDownloader, OciImageLayer};
use crate::name::ImageName;
use crate::registry::OciRegistryPlatform;
use anyhow::{anyhow, Result};
use backhand::compression::Compressor;
use backhand::{FilesystemCompressor, FilesystemWriter, NodeHeader};
use log::{debug, trace, warn};
use oci_spec::image::{ImageConfiguration, ImageManifest};
use std::borrow::Cow;
use std::fs::File;
use std::io::{BufWriter, ErrorKind, Read};
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::pin::Pin;
use tokio::fs;
use tokio::io::AsyncRead;
use tokio_stream::StreamExt;
use tokio_tar::{Archive, Entry};
use uuid::Uuid;
use walkdir::WalkDir;
pub const IMAGE_SQUASHFS_VERSION: u64 = 2;
pub struct ImageInfo {
pub image_squashfs: PathBuf,
pub manifest: ImageManifest,
pub config: ImageConfiguration,
}
impl ImageInfo {
pub fn new(
squashfs: PathBuf,
manifest: ImageManifest,
config: ImageConfiguration,
) -> Result<ImageInfo> {
Ok(ImageInfo {
image_squashfs: squashfs,
manifest,
config,
})
}
}
pub struct ImageCompiler<'a> {
cache: &'a ImageCache,
seed: Option<PathBuf>,
}
impl ImageCompiler<'_> {
pub fn new(cache: &ImageCache, seed: Option<PathBuf>) -> Result<ImageCompiler> {
Ok(ImageCompiler { cache, seed })
}
pub async fn compile(&self, image: &ImageName) -> Result<ImageInfo> {
debug!("compile image={image}");
let mut tmp_dir = std::env::temp_dir().clone();
tmp_dir.push(format!("krata-compile-{}", Uuid::new_v4()));
let mut image_dir = tmp_dir.clone();
image_dir.push("image");
fs::create_dir_all(&image_dir).await?;
let mut layer_dir = tmp_dir.clone();
layer_dir.push("layer");
fs::create_dir_all(&layer_dir).await?;
let mut squash_file = tmp_dir.clone();
squash_file.push("image.squashfs");
let info = self
.download_and_compile(image, &layer_dir, &image_dir, &squash_file)
.await?;
fs::remove_dir_all(&tmp_dir).await?;
Ok(info)
}
async fn download_and_compile(
&self,
image: &ImageName,
layer_dir: &Path,
image_dir: &Path,
squash_file: &Path,
) -> Result<ImageInfo> {
let downloader = OciImageDownloader::new(
self.seed.clone(),
layer_dir.to_path_buf(),
OciRegistryPlatform::current(),
);
let resolved = downloader.resolve(image.clone()).await?;
let cache_key = format!(
"manifest={}:squashfs-version={}\n",
resolved.digest, IMAGE_SQUASHFS_VERSION
);
let cache_digest = sha256::digest(cache_key);
if let Some(cached) = self.cache.recall(&cache_digest).await? {
return Ok(cached);
}
let local = downloader.download(resolved).await?;
for layer in &local.layers {
debug!(
"process layer digest={} compression={:?}",
&layer.digest, layer.compression,
);
let whiteouts = self.process_layer_whiteout(layer, image_dir).await?;
debug!(
"process layer digest={} whiteouts={:?}",
&layer.digest, whiteouts
);
let mut archive = layer.archive().await?;
let mut entries = archive.entries()?;
while let Some(entry) = entries.next().await {
let mut entry = entry?;
let path = entry.path()?;
let mut maybe_whiteout_path_str =
path.to_str().map(|x| x.to_string()).unwrap_or_default();
if whiteouts.contains(&maybe_whiteout_path_str) {
continue;
}
maybe_whiteout_path_str.push('/');
if whiteouts.contains(&maybe_whiteout_path_str) {
continue;
}
let Some(name) = path.file_name() else {
return Err(anyhow!("unable to get file name"));
};
let Some(name) = name.to_str() else {
return Err(anyhow!("unable to get file name as string"));
};
if name.starts_with(".wh.") {
continue;
} else {
self.process_write_entry(&mut entry, layer, image_dir)
.await?;
}
}
}
for layer in &local.layers {
if layer.path.exists() {
fs::remove_file(&layer.path).await?;
}
}
self.squash(image_dir, squash_file)?;
let info = ImageInfo::new(
squash_file.to_path_buf(),
local.image.manifest,
local.config,
)?;
self.cache.store(&cache_digest, &info).await
}
async fn process_layer_whiteout(
&self,
layer: &OciImageLayer,
image_dir: &Path,
) -> Result<Vec<String>> {
let mut whiteouts = Vec::new();
let mut archive = layer.archive().await?;
let mut entries = archive.entries()?;
while let Some(entry) = entries.next().await {
let entry = entry?;
let path = entry.path()?;
let Some(name) = path.file_name() else {
return Err(anyhow!("unable to get file name"));
};
let Some(name) = name.to_str() else {
return Err(anyhow!("unable to get file name as string"));
};
if name.starts_with(".wh.") {
let path = self
.process_whiteout_entry(&entry, name, layer, image_dir)
.await?;
if let Some(path) = path {
whiteouts.push(path);
}
}
}
Ok(whiteouts)
}
async fn process_whiteout_entry(
&self,
entry: &Entry<Archive<Pin<Box<dyn AsyncRead + Send>>>>,
name: &str,
layer: &OciImageLayer,
image_dir: &Path,
) -> Result<Option<String>> {
let path = entry.path()?;
let mut dst = self.check_safe_entry(path.clone(), image_dir)?;
dst.pop();
let mut path = path.to_path_buf();
path.pop();
let opaque = name == ".wh..wh..opq";
if !opaque {
let file = &name[4..];
dst.push(file);
path.push(file);
self.check_safe_path(&dst, image_dir)?;
}
trace!("whiteout entry layer={} path={:?}", &layer.digest, path,);
let whiteout = path
.to_str()
.ok_or(anyhow!("unable to convert path to string"))?
.to_string();
if opaque {
if dst.is_dir() {
let mut reader = fs::read_dir(dst).await?;
while let Some(entry) = reader.next_entry().await? {
let path = entry.path();
if path.is_symlink() || path.is_file() {
fs::remove_file(&path).await?;
} else if path.is_dir() {
fs::remove_dir_all(&path).await?;
} else {
return Err(anyhow!("opaque whiteout entry did not exist"));
}
}
} else {
debug!(
"whiteout opaque entry missing locally layer={} path={:?} local={:?}",
&layer.digest,
entry.path()?,
dst,
);
}
} else if dst.is_file() || dst.is_symlink() {
fs::remove_file(&dst).await?;
} else if dst.is_dir() {
fs::remove_dir_all(&dst).await?;
} else {
debug!(
"whiteout entry missing locally layer={} path={:?} local={:?}",
&layer.digest,
entry.path()?,
dst,
);
}
Ok(if opaque { None } else { Some(whiteout) })
}
async fn process_write_entry(
&self,
entry: &mut Entry<Archive<Pin<Box<dyn AsyncRead + Send>>>>,
layer: &OciImageLayer,
image_dir: &Path,
) -> Result<()> {
let uid = entry.header().uid()?;
let gid = entry.header().gid()?;
trace!(
"unpack entry layer={} path={:?} type={:?} uid={} gid={}",
&layer.digest,
entry.path()?,
entry.header().entry_type(),
uid,
gid,
);
entry.set_preserve_mtime(true);
entry.set_preserve_permissions(true);
entry.set_unpack_xattrs(true);
if let Some(path) = entry.unpack_in(image_dir).await? {
if !path.is_symlink() {
std::os::unix::fs::chown(path, Some(uid as u32), Some(gid as u32))?;
}
}
Ok(())
}
fn check_safe_entry(&self, path: Cow<Path>, image_dir: &Path) -> Result<PathBuf> {
let mut dst = image_dir.to_path_buf();
dst.push(path);
if let Some(name) = dst.file_name() {
if let Some(name) = name.to_str() {
if name.starts_with(".wh.") {
let copy = dst.clone();
dst.pop();
self.check_safe_path(&dst, image_dir)?;
return Ok(copy);
}
}
}
self.check_safe_path(&dst, image_dir)?;
Ok(dst)
}
fn check_safe_path(&self, dst: &Path, image_dir: &Path) -> Result<()> {
let resolved = path_clean::clean(dst);
if !resolved.starts_with(image_dir) {
return Err(anyhow!("layer attempts to work outside image dir"));
}
Ok(())
}
fn squash(&self, image_dir: &Path, squash_file: &Path) -> Result<()> {
let mut writer = FilesystemWriter::default();
writer.set_compressor(FilesystemCompressor::new(Compressor::Gzip, None)?);
let walk = WalkDir::new(image_dir).follow_links(false);
for entry in walk {
let entry = entry?;
let rel = entry
.path()
.strip_prefix(image_dir)?
.to_str()
.ok_or_else(|| anyhow!("failed to strip prefix of tmpdir"))?;
let rel = format!("/{}", rel);
trace!("squash write {}", rel);
let typ = entry.file_type();
let metadata = std::fs::symlink_metadata(entry.path())?;
let uid = metadata.uid();
let gid = metadata.gid();
let mode = metadata.permissions().mode();
let mtime = metadata.mtime();
if rel == "/" {
writer.set_root_uid(uid);
writer.set_root_gid(gid);
writer.set_root_mode(mode as u16);
continue;
}
let header = NodeHeader {
permissions: mode as u16,
uid,
gid,
mtime: mtime as u32,
};
if typ.is_symlink() {
let symlink = std::fs::read_link(entry.path())?;
let symlink = symlink
.to_str()
.ok_or_else(|| anyhow!("failed to read symlink"))?;
writer.push_symlink(symlink, rel, header)?;
} else if typ.is_dir() {
writer.push_dir(rel, header)?;
} else if typ.is_file() {
writer.push_file(ConsumingFileReader::new(entry.path()), rel, header)?;
} else if typ.is_block_device() {
let device = metadata.dev();
writer.push_block_device(device as u32, rel, header)?;
} else if typ.is_char_device() {
let device = metadata.dev();
writer.push_char_device(device as u32, rel, header)?;
} else if typ.is_fifo() {
writer.push_fifo(rel, header)?;
} else if typ.is_socket() {
writer.push_socket(rel, header)?;
} else {
return Err(anyhow!("invalid file type"));
}
}
let squash_file_path = squash_file
.to_str()
.ok_or_else(|| anyhow!("failed to convert squashfs string"))?;
let file = File::create(squash_file)?;
let mut bufwrite = BufWriter::new(file);
trace!("squash generate: {}", squash_file_path);
writer.write(&mut bufwrite)?;
std::fs::remove_dir_all(image_dir)?;
Ok(())
}
}
struct ConsumingFileReader {
path: PathBuf,
file: Option<File>,
}
impl ConsumingFileReader {
fn new(path: &Path) -> ConsumingFileReader {
ConsumingFileReader {
path: path.to_path_buf(),
file: None,
}
}
}
impl Read for ConsumingFileReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.file.is_none() {
self.file = Some(File::open(&self.path)?);
}
let Some(ref mut file) = self.file else {
return Err(std::io::Error::new(
ErrorKind::NotFound,
"file was not opened",
));
};
file.read(buf)
}
}
impl Drop for ConsumingFileReader {
fn drop(&mut self) {
let file = self.file.take();
drop(file);
if let Err(error) = std::fs::remove_file(&self.path) {
warn!("failed to delete consuming file {:?}: {}", self.path, error);
}
}
}

View File

@ -1,281 +0,0 @@
use super::{
name::ImageName,
registry::{OciRegistryClient, OciRegistryPlatform},
};
use std::{
path::{Path, PathBuf},
pin::Pin,
};
use anyhow::{anyhow, Result};
use async_compression::tokio::bufread::{GzipDecoder, ZstdDecoder};
use log::debug;
use oci_spec::image::{
Descriptor, ImageConfiguration, ImageIndex, ImageManifest, MediaType, ToDockerV2S2,
};
use serde::de::DeserializeOwned;
use tokio::{
fs::File,
io::{AsyncRead, AsyncReadExt, BufReader, BufWriter},
};
use tokio_stream::StreamExt;
use tokio_tar::Archive;
pub struct OciImageDownloader {
seed: Option<PathBuf>,
storage: PathBuf,
platform: OciRegistryPlatform,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OciImageLayerCompression {
None,
Gzip,
Zstd,
}
#[derive(Clone, Debug)]
pub struct OciImageLayer {
pub path: PathBuf,
pub digest: String,
pub compression: OciImageLayerCompression,
}
impl OciImageLayer {
pub async fn decompress(&self) -> Result<Pin<Box<dyn AsyncRead + Send>>> {
let file = File::open(&self.path).await?;
let reader = BufReader::new(file);
let reader: Pin<Box<dyn AsyncRead + Send>> = match self.compression {
OciImageLayerCompression::None => Box::pin(reader),
OciImageLayerCompression::Gzip => Box::pin(GzipDecoder::new(reader)),
OciImageLayerCompression::Zstd => Box::pin(ZstdDecoder::new(reader)),
};
Ok(reader)
}
pub async fn archive(&self) -> Result<Archive<Pin<Box<dyn AsyncRead + Send>>>> {
let decompress = self.decompress().await?;
Ok(Archive::new(decompress))
}
}
#[derive(Clone, Debug)]
pub struct OciResolvedImage {
pub name: ImageName,
pub digest: String,
pub manifest: ImageManifest,
}
#[derive(Clone, Debug)]
pub struct OciLocalImage {
pub image: OciResolvedImage,
pub config: ImageConfiguration,
pub layers: Vec<OciImageLayer>,
}
impl OciImageDownloader {
pub fn new(
seed: Option<PathBuf>,
storage: PathBuf,
platform: OciRegistryPlatform,
) -> OciImageDownloader {
OciImageDownloader {
seed,
storage,
platform,
}
}
async fn load_seed_json_blob<T: DeserializeOwned>(
&self,
descriptor: &Descriptor,
) -> Result<Option<T>> {
let digest = descriptor.digest();
let Some((digest_type, digest_content)) = digest.split_once(':') else {
return Err(anyhow!("digest content was not properly formatted"));
};
let want = format!("blobs/{}/{}", digest_type, digest_content);
self.load_seed_json(&want).await
}
async fn load_seed_json<T: DeserializeOwned>(&self, want: &str) -> Result<Option<T>> {
let Some(ref seed) = self.seed else {
return Ok(None);
};
let file = File::open(seed).await?;
let mut archive = Archive::new(file);
let mut entries = archive.entries()?;
while let Some(entry) = entries.next().await {
let mut entry = entry?;
let path = String::from_utf8(entry.path_bytes().to_vec())?;
if path == want {
let mut content = String::new();
entry.read_to_string(&mut content).await?;
let data = serde_json::from_str::<T>(&content)?;
return Ok(Some(data));
}
}
Ok(None)
}
async fn extract_seed_blob(&self, descriptor: &Descriptor, to: &Path) -> Result<bool> {
let Some(ref seed) = self.seed else {
return Ok(false);
};
let digest = descriptor.digest();
let Some((digest_type, digest_content)) = digest.split_once(':') else {
return Err(anyhow!("digest content was not properly formatted"));
};
let want = format!("blobs/{}/{}", digest_type, digest_content);
let seed = File::open(seed).await?;
let mut archive = Archive::new(seed);
let mut entries = archive.entries()?;
while let Some(entry) = entries.next().await {
let mut entry = entry?;
let path = String::from_utf8(entry.path_bytes().to_vec())?;
if path == want {
let file = File::create(to).await?;
let mut bufwrite = BufWriter::new(file);
tokio::io::copy(&mut entry, &mut bufwrite).await?;
return Ok(true);
}
}
Ok(false)
}
pub async fn resolve(&self, image: ImageName) -> Result<OciResolvedImage> {
debug!("resolve manifest image={}", image);
if let Some(index) = self.load_seed_json::<ImageIndex>("index.json").await? {
let mut found: Option<&Descriptor> = None;
for manifest in index.manifests() {
let Some(annotations) = manifest.annotations() else {
continue;
};
let mut image_name = annotations.get("io.containerd.image.name");
if image_name.is_none() {
image_name = annotations.get("org.opencontainers.image.ref.name");
}
let Some(image_name) = image_name else {
continue;
};
if *image_name != image.to_string() {
continue;
}
if let Some(platform) = manifest.platform() {
if *platform.architecture() != self.platform.arch
|| *platform.os() != self.platform.os
{
continue;
}
}
found = Some(manifest);
break;
}
if let Some(found) = found {
if let Some(manifest) = self.load_seed_json_blob(found).await? {
debug!(
"found seeded manifest image={} manifest={}",
image,
found.digest()
);
return Ok(OciResolvedImage {
name: image,
digest: found.digest().clone(),
manifest,
});
}
}
}
let mut client = OciRegistryClient::new(image.registry_url()?, self.platform.clone())?;
let (manifest, digest) = client
.get_manifest_with_digest(&image.name, &image.reference)
.await?;
Ok(OciResolvedImage {
name: image,
digest,
manifest,
})
}
pub async fn download(&self, image: OciResolvedImage) -> Result<OciLocalImage> {
let config: ImageConfiguration;
let mut client = OciRegistryClient::new(image.name.registry_url()?, self.platform.clone())?;
if let Some(seeded) = self
.load_seed_json_blob::<ImageConfiguration>(image.manifest.config())
.await?
{
config = seeded;
} else {
let config_bytes = client
.get_blob(&image.name.name, image.manifest.config())
.await?;
config = serde_json::from_slice(&config_bytes)?;
}
let mut layers = Vec::new();
for layer in image.manifest.layers() {
layers.push(self.acquire_layer(&image.name, layer, &mut client).await?);
}
Ok(OciLocalImage {
image,
config,
layers,
})
}
async fn acquire_layer(
&self,
image: &ImageName,
layer: &Descriptor,
client: &mut OciRegistryClient,
) -> Result<OciImageLayer> {
debug!(
"acquire layer digest={} size={}",
layer.digest(),
layer.size()
);
let mut layer_path = self.storage.clone();
layer_path.push(format!("{}.layer", layer.digest()));
let seeded = self.extract_seed_blob(layer, &layer_path).await?;
if !seeded {
let file = File::create(&layer_path).await?;
let size = client.write_blob_to_file(&image.name, layer, file).await?;
if layer.size() as u64 != size {
return Err(anyhow!(
"downloaded layer size differs from size in manifest",
));
}
}
let mut media_type = layer.media_type().clone();
// docker layer compatibility
if media_type.to_string() == MediaType::ImageLayerGzip.to_docker_v2s2()? {
media_type = MediaType::ImageLayerGzip;
}
let compression = match media_type {
MediaType::ImageLayer => OciImageLayerCompression::None,
MediaType::ImageLayerGzip => OciImageLayerCompression::Gzip,
MediaType::ImageLayerZstd => OciImageLayerCompression::Zstd,
other => return Err(anyhow!("found layer with unknown media type: {}", other)),
};
Ok(OciImageLayer {
path: layer_path,
digest: layer.digest().clone(),
compression,
})
}
}

View File

@ -1,5 +0,0 @@
pub mod cache;
pub mod compiler;
pub mod fetch;
pub mod name;
pub mod registry;

View File

@ -1,88 +0,0 @@
use anyhow::Result;
use std::fmt;
use url::Url;
const DOCKER_HUB_MIRROR: &str = "mirror.gcr.io";
const DEFAULT_IMAGE_TAG: &str = "latest";
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImageName {
pub hostname: String,
pub port: Option<u16>,
pub name: String,
pub reference: String,
}
impl fmt::Display for ImageName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(port) = self.port {
write!(
f,
"{}:{}/{}:{}",
self.hostname, port, self.name, self.reference
)
} else {
write!(f, "{}/{}:{}", self.hostname, self.name, self.reference)
}
}
}
impl Default for ImageName {
fn default() -> Self {
Self::parse(&format!("{}", uuid::Uuid::new_v4().as_hyphenated()))
.expect("UUID hyphenated must be valid name")
}
}
impl ImageName {
pub fn parse(name: &str) -> Result<Self> {
let full_name = name.to_string();
let name = full_name.clone();
let (mut hostname, mut name) = name
.split_once('/')
.map(|x| (x.0.to_string(), x.1.to_string()))
.unwrap_or_else(|| (DOCKER_HUB_MIRROR.to_string(), format!("library/{}", name)));
// heuristic to find any docker hub image formats
// that may be in the hostname format. for example:
// abc/xyz:latest will trigger this if check, but abc.io/xyz:latest will not,
// and neither will abc/hello/xyz:latest
if !hostname.contains('.') && full_name.chars().filter(|x| *x == '/').count() == 1 {
name = format!("{}/{}", hostname, name);
hostname = DOCKER_HUB_MIRROR.to_string();
}
let (hostname, port) = if let Some((hostname, port)) = hostname
.split_once(':')
.map(|x| (x.0.to_string(), x.1.to_string()))
{
(hostname, Some(str::parse(&port)?))
} else {
(hostname, None)
};
let (name, reference) = name
.split_once(':')
.map(|x| (x.0.to_string(), x.1.to_string()))
.unwrap_or((name.to_string(), DEFAULT_IMAGE_TAG.to_string()));
Ok(ImageName {
hostname,
port,
name,
reference,
})
}
pub fn registry_url(&self) -> Result<Url> {
let hostname = if let Some(port) = self.port {
format!("{}:{}", self.hostname, port)
} else {
self.hostname.clone()
};
let url = if self.hostname.starts_with("localhost") {
format!("http://{}", hostname)
} else {
format!("https://{}", hostname)
};
Ok(Url::parse(&url)?)
}
}

View File

@ -1,244 +0,0 @@
use std::collections::HashMap;
use anyhow::{anyhow, Result};
use bytes::Bytes;
use oci_spec::image::{Arch, Descriptor, ImageIndex, ImageManifest, MediaType, Os, ToDockerV2S2};
use reqwest::{Client, RequestBuilder, Response, StatusCode};
use tokio::{fs::File, io::AsyncWriteExt};
use url::Url;
#[derive(Clone, Debug)]
pub struct OciRegistryPlatform {
pub os: Os,
pub arch: Arch,
}
impl OciRegistryPlatform {
#[cfg(target_arch = "x86_64")]
const CURRENT_ARCH: Arch = Arch::Amd64;
#[cfg(target_arch = "aarch64")]
const CURRENT_ARCH: Arch = Arch::ARM64;
pub fn new(os: Os, arch: Arch) -> OciRegistryPlatform {
OciRegistryPlatform { os, arch }
}
pub fn current() -> OciRegistryPlatform {
OciRegistryPlatform {
os: Os::Linux,
arch: OciRegistryPlatform::CURRENT_ARCH,
}
}
}
pub struct OciRegistryClient {
agent: Client,
url: Url,
platform: OciRegistryPlatform,
token: Option<String>,
}
impl OciRegistryClient {
pub fn new(url: Url, platform: OciRegistryPlatform) -> Result<OciRegistryClient> {
Ok(OciRegistryClient {
agent: Client::new(),
url,
platform,
token: None,
})
}
async fn call(&mut self, mut req: RequestBuilder) -> Result<Response> {
if let Some(ref token) = self.token {
req = req.bearer_auth(token);
}
let req_first_try = req.try_clone().ok_or(anyhow!("request is not clonable"))?;
let response = self.agent.execute(req_first_try.build()?).await?;
if response.status() == StatusCode::UNAUTHORIZED && self.token.is_none() {
let Some(www_authenticate) = response.headers().get("www-authenticate") else {
return Err(anyhow!("not authorized to perform this action"));
};
let www_authenticate = www_authenticate.to_str()?;
if !www_authenticate.starts_with("Bearer ") {
return Err(anyhow!("unknown authentication scheme"));
}
let details = &www_authenticate[7..];
let details = details
.split(',')
.map(|x| x.split('='))
.map(|mut x| (x.next(), x.next()))
.filter(|(key, value)| key.is_some() && value.is_some())
.map(|(key, value)| {
(
key.unwrap().trim().to_lowercase(),
value.unwrap().trim().to_string(),
)
})
.map(|(key, value)| (key, value.trim_matches('\"').to_string()))
.collect::<HashMap<_, _>>();
let realm = details.get("realm");
let service = details.get("service");
let scope = details.get("scope");
if realm.is_none() || service.is_none() || scope.is_none() {
return Err(anyhow!(
"unknown authentication scheme: realm, service, and scope are required"
));
}
let mut url = Url::parse(realm.unwrap())?;
url.query_pairs_mut()
.append_pair("service", service.unwrap())
.append_pair("scope", scope.unwrap());
let token_response = self.agent.get(url.clone()).send().await?;
if token_response.status() != StatusCode::OK {
return Err(anyhow!(
"failed to acquire token via {}: status {}",
url,
token_response.status()
));
}
let token_bytes = token_response.bytes().await?;
let token = serde_json::from_slice::<serde_json::Value>(&token_bytes)?;
let token = token
.get("token")
.and_then(|x| x.as_str())
.ok_or(anyhow!("token key missing from response"))?;
self.token = Some(token.to_string());
return Ok(self.agent.execute(req.bearer_auth(token).build()?).await?);
}
if !response.status().is_success() {
return Err(anyhow!(
"request to {} failed: status {}",
req.build()?.url(),
response.status()
));
}
Ok(response)
}
pub async fn get_blob<N: AsRef<str>>(
&mut self,
name: N,
descriptor: &Descriptor,
) -> Result<Bytes> {
let url = self.url.join(&format!(
"/v2/{}/blobs/{}",
name.as_ref(),
descriptor.digest()
))?;
let response = self.call(self.agent.get(url.as_str())).await?;
Ok(response.bytes().await?)
}
pub async fn write_blob_to_file<N: AsRef<str>>(
&mut self,
name: N,
descriptor: &Descriptor,
mut dest: File,
) -> Result<u64> {
let url = self.url.join(&format!(
"/v2/{}/blobs/{}",
name.as_ref(),
descriptor.digest()
))?;
let mut response = self.call(self.agent.get(url.as_str())).await?;
let mut size: u64 = 0;
while let Some(chunk) = response.chunk().await? {
dest.write_all(&chunk).await?;
size += chunk.len() as u64;
}
Ok(size)
}
async fn get_raw_manifest_with_digest<N: AsRef<str>, R: AsRef<str>>(
&mut self,
name: N,
reference: R,
) -> Result<(ImageManifest, String)> {
let url = self.url.join(&format!(
"/v2/{}/manifests/{}",
name.as_ref(),
reference.as_ref()
))?;
let accept = format!(
"{}, {}, {}, {}",
MediaType::ImageManifest.to_docker_v2s2()?,
MediaType::ImageManifest,
MediaType::ImageIndex,
MediaType::ImageIndex.to_docker_v2s2()?,
);
let response = self
.call(self.agent.get(url.as_str()).header("Accept", &accept))
.await?;
let digest = response
.headers()
.get("Docker-Content-Digest")
.ok_or_else(|| anyhow!("fetching manifest did not yield a content digest"))?
.to_str()?
.to_string();
let manifest = serde_json::from_str(&response.text().await?)?;
Ok((manifest, digest))
}
pub async fn get_manifest_with_digest<N: AsRef<str>, R: AsRef<str>>(
&mut self,
name: N,
reference: R,
) -> Result<(ImageManifest, String)> {
let url = self.url.join(&format!(
"/v2/{}/manifests/{}",
name.as_ref(),
reference.as_ref()
))?;
let accept = format!(
"{}, {}, {}, {}",
MediaType::ImageManifest.to_docker_v2s2()?,
MediaType::ImageManifest,
MediaType::ImageIndex,
MediaType::ImageIndex.to_docker_v2s2()?,
);
let response = self
.call(self.agent.get(url.as_str()).header("Accept", &accept))
.await?;
let content_type = response
.headers()
.get("Content-Type")
.ok_or_else(|| anyhow!("registry response did not have a Content-Type header"))?
.to_str()?;
if content_type == MediaType::ImageIndex.to_string()
|| content_type == MediaType::ImageIndex.to_docker_v2s2()?
{
let index = serde_json::from_str(&response.text().await?)?;
let descriptor = self
.pick_manifest(index)
.ok_or_else(|| anyhow!("unable to pick manifest from index"))?;
return self
.get_raw_manifest_with_digest(name, descriptor.digest())
.await;
}
let digest = response
.headers()
.get("Docker-Content-Digest")
.ok_or_else(|| anyhow!("fetching manifest did not yield a content digest"))?
.to_str()?
.to_string();
let manifest = serde_json::from_str(&response.text().await?)?;
Ok((manifest, digest))
}
fn pick_manifest(&mut self, index: ImageIndex) -> Option<Descriptor> {
for item in index.manifests() {
if let Some(platform) = item.platform() {
if *platform.os() == self.platform.os
&& *platform.architecture() == self.platform.arch
{
return Some(item.clone());
}
}
}
None
}
}

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