Compare commits

...
3 Commits
Author SHA1 Message Date
edi-oai 28bb29df4a Validate DHCP client identity and source address (#191)
* Validate DHCP client identity and source address

* Use CFBoolean instead of CFNumber for dhcp_ignore_client_identifier

* Serialize bootpd preference updates
2026-08-10 22:10:20 -04:00
dependabot[bot] d079057ecf Bump the all-updates group with 4 updates (#190)
Bumps the all-updates group with 4 updates: [clap](https://github.com/clap-rs/clap), [sentry](https://github.com/getsentry/sentry-rust), [sentry-anyhow](https://github.com/getsentry/sentry-rust) and [ipnet](https://github.com/krisprice/ipnet).


Updates `clap` from 4.6.4 to 4.6.5
- [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.6.4...clap_complete-v4.6.5)

Updates `sentry` from 0.49.0 to 0.49.1
- [Release notes](https://github.com/getsentry/sentry-rust/releases)
- [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-rust/compare/0.49.0...0.49.1)

Updates `sentry-anyhow` from 0.49.0 to 0.49.1
- [Release notes](https://github.com/getsentry/sentry-rust/releases)
- [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-rust/compare/0.49.0...0.49.1)

Updates `ipnet` from 2.12.0 to 2.12.1
- [Release notes](https://github.com/krisprice/ipnet/releases)
- [Changelog](https://github.com/krisprice/ipnet/blob/master/RELEASES.md)
- [Commits](https://github.com/krisprice/ipnet/commits)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: sentry
  dependency-version: 0.49.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: sentry-anyhow
  dependency-version: 0.49.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: ipnet
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 00:47:15 +01:00
dependabot[bot]andNikolay Edigaryev 237aec1df5 Bump the all-updates group across 1 directory with 5 updates (#189)
* Bump the all-updates group across 1 directory with 5 updates

Bumps the all-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [libc](https://github.com/rust-lang/libc) | `0.2.188` | `0.2.189` |
| [clap](https://github.com/clap-rs/clap) | `4.6.3` | `4.6.4` |
| [sentry](https://github.com/getsentry/sentry-rust) | `0.48.5` | `0.49.0` |
| [sentry-anyhow](https://github.com/getsentry/sentry-rust) | `0.48.5` | `0.49.0` |
| [serial_test](https://github.com/palfrey/serial_test) | `3.5.0` | `4.0.1` |



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

Updates `clap` from 4.6.3 to 4.6.4
- [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.6.3...clap_complete-v4.6.4)

Updates `sentry` from 0.48.5 to 0.49.0
- [Release notes](https://github.com/getsentry/sentry-rust/releases)
- [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-rust/compare/0.48.5...0.49.0)

Updates `sentry-anyhow` from 0.48.5 to 0.49.0
- [Release notes](https://github.com/getsentry/sentry-rust/releases)
- [Changelog](https://github.com/getsentry/sentry-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-rust/compare/0.48.5...0.49.0)

Updates `serial_test` from 3.5.0 to 4.0.1
- [Release notes](https://github.com/palfrey/serial_test/releases)
- [Commits](https://github.com/palfrey/serial_test/compare/v3.5.0...v4.0.1)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: libc
  dependency-version: 0.2.189
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-updates
- dependency-name: sentry
  dependency-version: 0.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: sentry-anyhow
  dependency-version: 0.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-updates
- dependency-name: serial_test
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-updates
...

Signed-off-by: dependabot[bot] <support@github.com>

* Work around Sentry 0.49 marking ClientOptions as #[non_exhaustive]

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nikolay Edigaryev <edi@openai.com>
2026-08-04 14:50:20 +01:00
5 changed files with 224 additions and 186 deletions
Generated
+49 -141
View File
@@ -246,12 +246,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -374,9 +368,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.6.3"
version = "4.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776"
checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf"
dependencies = [
"clap_builder",
"clap_derive",
@@ -384,9 +378,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.6.2"
version = "4.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078"
dependencies = [
"anstream",
"anstyle",
@@ -396,14 +390,14 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.6.3"
version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077"
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 3.0.2",
]
[[package]]
@@ -895,25 +889,6 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hash32"
version = "0.3.1"
@@ -1091,7 +1066,6 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2",
"http 1.1.0",
"http-body",
"httparse",
@@ -1102,22 +1076,6 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http 1.1.0",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
@@ -1335,9 +1293,9 @@ checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1"
[[package]]
name = "ipnet"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
[[package]]
name = "iri-string"
@@ -1452,9 +1410,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.188"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22053b6a34f84abc97f9129e61334f40174659a1b9bd18c970b83db6a9a6348b"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "linux-raw-sys"
@@ -2066,12 +2024,10 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http 1.1.0",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
@@ -2094,20 +2050,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.15",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -2149,19 +2091,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
@@ -2180,17 +2109,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.21"
@@ -2258,9 +2176,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "sentry"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d631477761f57c76148456e55e80e9a479ff3fa4c65b2b4a0c3acf1167fd4638"
checksum = "63207365db50cb817402f0ec8097d6dad3d644ef3fffd6ba30c75b3077e514da"
dependencies = [
"cfg_aliases",
"httpdate",
@@ -2271,6 +2189,7 @@ dependencies = [
"sentry-contexts",
"sentry-core",
"sentry-debug-images",
"sentry-log",
"sentry-panic",
"sentry-tracing",
"tokio",
@@ -2279,9 +2198,9 @@ dependencies = [
[[package]]
name = "sentry-actix"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "119ede4e37790ec04e8a14073c8414f0a4b2648402856c0563495a9a5cf54d57"
checksum = "4a0eb1ed18478fb48db007aeaa672a3de176055357feb768eee0c3471802d0ce"
dependencies = [
"actix-http",
"actix-web",
@@ -2292,9 +2211,9 @@ dependencies = [
[[package]]
name = "sentry-anyhow"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8f73466a403f4da78c7e576049d6044b31d2b16dfcc26b51705f318ed74b804"
checksum = "6265521f1b724f709bc07747ecb01c5289b974cb70b348026df01780280fc136"
dependencies = [
"anyhow",
"sentry-backtrace",
@@ -2303,9 +2222,9 @@ dependencies = [
[[package]]
name = "sentry-backtrace"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "448b0981fbde6cdc9eb087ba3dc01035a253fef9a0d9e79aaf198fc26acb2e64"
checksum = "e3bcc2497c2327998146207b7600599ef7592233920f4d4e1d41ddeeba0e4210"
dependencies = [
"backtrace",
"regex",
@@ -2314,9 +2233,9 @@ dependencies = [
[[package]]
name = "sentry-contexts"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e909d02170ba6d1dc5ebd05bef7999280f5d960e3eaee5d1a18d88d41b334"
checksum = "1cb04cba225b38f59d08f9e3c29ab7259d9cc94fbb2c46d613a51cb0a4a7ee05"
dependencies = [
"hostname",
"libc",
@@ -2328,9 +2247,9 @@ dependencies = [
[[package]]
name = "sentry-core"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecf0b1a4a4e9ec88395b52e6fa4868b95564c1fa96b26a0606f5f6288a0f7149"
checksum = "48759bc392fb5e3b36b4efcb485f51074c0ba8479711cd5c8cf79e86f9f75d41"
dependencies = [
"rand 0.9.4",
"sentry-types",
@@ -2341,19 +2260,30 @@ dependencies = [
[[package]]
name = "sentry-debug-images"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81d7749c57fc78ed52134889e8121da874065bc40da788d5798cce0f8c19f15"
checksum = "0a5bd325059b70b21ca6e42b0f2409821272dddc1cf37923de37269af55389b5"
dependencies = [
"findshlibs",
"sentry-core",
]
[[package]]
name = "sentry-panic"
version = "0.48.5"
name = "sentry-log"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7d93d6ecb55d2251c5fc084c55c03a2bc68904918d76117e602c999e92f00f"
checksum = "f9553a2f0f75bc8550137ebc753e8d2161af63ff780ca59983935f8df8f01655"
dependencies = [
"bitflags 2.9.4",
"log",
"sentry-core",
]
[[package]]
name = "sentry-panic"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a685d22f672b1562ebeff3f2affceb5960595648cc936dae73da3e1d6a55fbd1"
dependencies = [
"sentry-backtrace",
"sentry-core",
@@ -2361,9 +2291,9 @@ dependencies = [
[[package]]
name = "sentry-tracing"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58d26379236c4ef97eaf081bca3c7bf0ef6f06b3aa881eca2ee8e8dbc923e5b8"
checksum = "8aa7d8e0db4cccddbac01ddabd5344ad03b7c2f954b3ff850cecb1d9cf5d4758"
dependencies = [
"bitflags 2.9.4",
"sentry-backtrace",
@@ -2374,9 +2304,9 @@ dependencies = [
[[package]]
name = "sentry-types"
version = "0.48.5"
version = "0.49.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2239099d47e76857b0825a182ecdb90159add7aa1097b60247c6e7ca6bf49c6a"
checksum = "fab146d15a30ab4897a95fd15d6a038b8d7fbf2661c5f8b13738ce8df7a095b7"
dependencies = [
"debugid",
"hex",
@@ -2446,9 +2376,9 @@ dependencies = [
[[package]]
name = "serial_test"
version = "3.5.0"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
checksum = "a6df5ed973ad8d834e09f824f9e9f449af6b9a3745f78dec7cc752770bd3bf11"
dependencies = [
"futures-executor",
"futures-util",
@@ -2460,13 +2390,13 @@ dependencies = [
[[package]]
name = "serial_test_derive"
version = "3.5.0"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
checksum = "a22144e767da4ddd8416dbf383700542ffd8a5dc493dfecedfe1fe3ad03c98ae"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
"syn 3.0.2",
]
[[package]]
@@ -2611,12 +2541,6 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
@@ -2827,16 +2751,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.15"
@@ -2998,12 +2912,6 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.0.12"
+1 -1
View File
@@ -32,7 +32,7 @@ prefix-trie = "0"
ipnet = "2"
oslog = "0.2.0"
log = "0.4.29"
serial_test = "3"
serial_test = "4"
coarsetime = "0.1.37"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+13 -6
View File
@@ -1,5 +1,5 @@
use dhcproto::Decodable;
use dhcproto::v4::{DhcpOption, HType, MessageType, Opcode, OptionCode};
use dhcproto::v4::{DhcpOption, HType, Message, MessageType, Opcode, OptionCode};
use smoltcp::wire::Ipv4Address;
use std::collections::HashSet;
use std::time::Duration;
@@ -32,11 +32,7 @@ impl DhcpSnooper {
// hardware address to avoid acting on another VM's lease transition
//
// [1]: https://datatracker.ietf.org/doc/html/rfc2131#section-4.1
if message.opcode() != Opcode::BootReply
|| message.htype() != HType::Eth
|| message.hlen() != self.vm_mac_address.len() as u8
|| message.chaddr() != self.vm_mac_address
{
if !message_matches_bootp_client(&message, Opcode::BootReply, self.vm_mac_address) {
return;
}
@@ -120,6 +116,17 @@ impl Lease {
}
}
pub(crate) fn message_matches_bootp_client(
message: &Message,
opcode: Opcode,
mac: [u8; 6],
) -> bool {
message.opcode() == opcode
&& message.htype() == HType::Eth
&& message.hlen() == mac.len() as u8
&& message.chaddr() == mac
}
#[cfg(test)]
mod tests {
use super::{DhcpSnooper, Lease};
+107 -20
View File
@@ -1,9 +1,11 @@
use crate::dhcp_snooper::Lease;
use crate::dhcp_snooper::{Lease, message_matches_bootp_client};
use crate::proxy::flows::{FlowDirection, FlowMatch};
use crate::proxy::udp_packet_helper::UdpPacketHelper;
use crate::proxy::{Direction, PolicyDecision, Proxy};
use anyhow::Context;
use anyhow::Result;
use dhcproto::Decodable;
use dhcproto::v4::Opcode;
use smoltcp::phy::ChecksumCapabilities;
use smoltcp::wire::{
ArpOperation, ArpPacket, ArpRepr, EthernetFrame, EthernetProtocol, IpProtocol, Ipv4Address,
@@ -61,7 +63,12 @@ impl Proxy<'_> {
{
// Unicast DHCP renewal is required to maintain the VM's lease
// and must bypass user-specified rules
if is_allowed_dhcp_request(&ipv4_pkt, Some(self.host.gateway_ip)) {
if is_allowed_dhcp_request(
&ipv4_pkt,
Some(self.host.gateway_ip),
self.vm_mac_address,
self.dhcp_snooper.lease(),
) {
return Some(());
}
@@ -123,7 +130,12 @@ impl Proxy<'_> {
// Allow outgoing DHCP requests to the bootpd(8) broadcast address,
// otherwise DHCP snooper will never be populated
if is_allowed_dhcp_request(&ipv4_pkt, None) {
if is_allowed_dhcp_request(
&ipv4_pkt,
None,
self.vm_mac_address,
self.dhcp_snooper.lease(),
) {
return Some(());
}
@@ -134,7 +146,20 @@ impl Proxy<'_> {
fn is_allowed_dhcp_request(
ipv4_pkt: &Ipv4Packet<&[u8]>,
unicast_target: Option<Ipv4Address>,
vm_mac_address: smoltcp::wire::EthernetAddress,
lease: &Option<Lease>,
) -> bool {
// Require the source address to be either:
// * covered by the VM's current lease
// * unspecified on the broadcast DHCP path
let src_addr = ipv4_pkt.src_addr();
let src_has_valid_lease = lease
.as_ref()
.is_some_and(|lease| lease.is_valid_for(src_addr));
if !src_has_valid_lease && !(unicast_target.is_none() && src_addr.is_unspecified()) {
return false;
}
let dst_addr = ipv4_pkt.dst_addr();
// Keep the common path cheap and inspect UDP only for a permitted DHCP target
@@ -150,7 +175,18 @@ fn is_allowed_dhcp_request(
return false;
};
udp_pkt.is_dhcp_request()
// Require the standard DHCP client and server ports
if !udp_pkt.is_dhcp_request() {
return false;
}
// Require the BOOTP client hardware address to match this VM
let mut decoder = dhcproto::v4::Decoder::new(udp_pkt.payload());
let Ok(message) = dhcproto::v4::Message::decode(&mut decoder) else {
return false;
};
message_matches_bootp_client(&message, Opcode::BootRequest, vm_mac_address.0)
}
fn vm_arp_allowed(
@@ -191,6 +227,8 @@ fn vm_arp_allowed(
#[cfg(test)]
mod tests {
use crate::dhcp_snooper::Lease;
use dhcproto::v4::{DhcpOption, Message, MessageType};
use dhcproto::{Encodable, Encoder};
use smoltcp::wire::{
ArpHardware, ArpOperation, ArpPacket, EthernetAddress, EthernetProtocol, IpProtocol,
Ipv4Address, Ipv4Packet, UdpPacket,
@@ -198,15 +236,31 @@ mod tests {
use std::collections::HashSet;
use std::time::Duration;
#[test]
fn test_allowed_dhcp_request_targets() {
let gateway = Ipv4Address::new(192, 168, 64, 1);
let other = Ipv4Address::new(192, 168, 64, 2);
const VM_MAC: EthernetAddress = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
assert!(allowed_dhcp_request(Ipv4Address::BROADCAST, None));
assert!(allowed_dhcp_request(gateway, Some(gateway)));
assert!(!allowed_dhcp_request(gateway, None));
assert!(!allowed_dhcp_request(other, Some(gateway)));
#[test]
fn test_allowed_dhcp_request_policy() {
let gateway = Ipv4Address::new(192, 168, 64, 1);
let lease_ip = Ipv4Address::new(192, 168, 64, 2);
let other = Ipv4Address::new(192, 168, 64, 3);
let no_lease = None;
let lease = Some(Lease::new(
lease_ip,
Duration::from_secs(600),
HashSet::new(),
));
let initial = |src, chaddr| {
allowed_dhcp_request(src, Ipv4Address::BROADCAST, None, chaddr, &no_lease)
};
let renewal = |src, dst| allowed_dhcp_request(src, dst, Some(gateway), VM_MAC.0, &lease);
let other_mac = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02];
assert!(initial(Ipv4Address::UNSPECIFIED, VM_MAC.0));
assert!(renewal(lease_ip, gateway));
assert!(!renewal(other, gateway));
assert!(!renewal(Ipv4Address::UNSPECIFIED, gateway));
assert!(!renewal(lease_ip, other));
assert!(!initial(Ipv4Address::UNSPECIFIED, other_mac));
}
#[test]
@@ -308,21 +362,54 @@ mod tests {
buf
}
fn allowed_dhcp_request(dst_addr: Ipv4Address, unicast_target: Option<Ipv4Address>) -> bool {
let mut buf = vec![0; 28];
fn allowed_dhcp_request(
src_addr: Ipv4Address,
dst_addr: Ipv4Address,
unicast_target: Option<Ipv4Address>,
chaddr: [u8; 6],
lease: &Option<Lease>,
) -> bool {
let mut buf = dhcp_request(chaddr);
let mut ipv4_pkt = Ipv4Packet::new_unchecked(buf.as_mut_slice());
ipv4_pkt.set_src_addr(src_addr);
ipv4_pkt.set_dst_addr(dst_addr);
let ipv4_pkt = Ipv4Packet::new_checked(buf.as_slice()).unwrap();
super::is_allowed_dhcp_request(&ipv4_pkt, unicast_target, VM_MAC, lease)
}
fn dhcp_request(chaddr: [u8; 6]) -> Vec<u8> {
let mut message = Message::new(
Ipv4Address::UNSPECIFIED,
Ipv4Address::UNSPECIFIED,
Ipv4Address::UNSPECIFIED,
Ipv4Address::UNSPECIFIED,
&chaddr,
);
message
.opts_mut()
.insert(DhcpOption::MessageType(MessageType::Discover));
let mut dhcp_payload = Vec::new();
message
.encode(&mut Encoder::new(&mut dhcp_payload))
.unwrap();
let total_len = 20 + 8 + dhcp_payload.len();
let mut buf = vec![0; total_len];
let mut ipv4_pkt = Ipv4Packet::new_unchecked(buf.as_mut_slice());
ipv4_pkt.set_version(4);
ipv4_pkt.set_header_len(20);
ipv4_pkt.set_total_len(28);
ipv4_pkt.set_total_len(total_len as u16);
ipv4_pkt.set_next_header(IpProtocol::Udp);
ipv4_pkt.set_dst_addr(dst_addr);
ipv4_pkt.set_src_addr(Ipv4Address::UNSPECIFIED);
ipv4_pkt.set_dst_addr(Ipv4Address::BROADCAST);
let mut udp_pkt = UdpPacket::new_unchecked(ipv4_pkt.payload_mut());
udp_pkt.set_src_port(68);
udp_pkt.set_dst_port(67);
udp_pkt.set_len(8);
let ipv4_pkt = Ipv4Packet::new_checked(buf.as_slice()).unwrap();
super::is_allowed_dhcp_request(&ipv4_pkt, unicast_target)
udp_pkt.set_len((8 + dhcp_payload.len()) as u16);
udp_pkt.payload_mut().copy_from_slice(&dhcp_payload);
buf
}
}
+54 -18
View File
@@ -8,18 +8,21 @@ use softnet::NetType;
use softnet::proxy::ExposedPort;
use softnet::proxy::Proxy;
use softnet::proxy::Rule;
use std::borrow::Cow;
use std::env;
use std::os::raw::c_int;
use std::os::unix::io::RawFd;
use std::os::unix::process::CommandExt;
use std::process::{Command, ExitCode};
use system_configuration::core_foundation::base::TCFType;
use system_configuration::core_foundation::boolean::CFBoolean;
use system_configuration::core_foundation::dictionary::CFDictionary;
use system_configuration::core_foundation::number::CFNumber;
use system_configuration::core_foundation::string::CFString;
use system_configuration::preferences::SCPreferences;
use system_configuration::sys::preferences::{SCPreferencesCommitChanges, SCPreferencesSetValue};
use system_configuration::sys::preferences::{
SCPreferencesApplyChanges, SCPreferencesCommitChanges, SCPreferencesLock,
SCPreferencesSetValue, SCPreferencesUnlock,
};
use uzers::{get_current_groupname, get_current_username, get_effective_uid};
#[derive(Parser, Debug)]
@@ -130,10 +133,10 @@ fn main() -> ExitCode {
}
// Initialize Sentry
let _sentry = sentry::init(sentry::ClientOptions {
release: option_env!("CIRRUS_TAG").map(|tag| Cow::from(format!("softnet@{tag}"))),
..Default::default()
});
let _sentry = sentry::init(
sentry::ClientOptions::default()
.maybe_release(option_env!("CIRRUS_TAG").map(|tag| format!("softnet@{tag}"))),
);
// Enrich future events with Cirrus CI-specific tags
if let Ok(tags) = env::var("CIRRUS_SENTRY_TAGS") {
@@ -216,8 +219,8 @@ fn try_main() -> anyhow::Result<()> {
));
}
// Set bootpd(8) min/max lease time while still having the root privileges
set_bootpd_lease_time(args.bootpd_lease_time);
// Configure bootpd(8) while still having the root privileges
configure_bootpd(args.bootpd_lease_time)?;
// Initialize the proxy while still having the root privileges
let mut proxy = Proxy::new(
@@ -269,26 +272,59 @@ fn sudo_escalation_works() -> bool {
.unwrap_or(false)
}
fn set_bootpd_lease_time(lease_time: u32) {
fn configure_bootpd(lease_time: u32) -> anyhow::Result<()> {
let prefs = SCPreferences::group(
&CFString::new("softnet"),
&CFString::new("com.apple.InternetSharing.default.plist"),
);
let bootpd_dict = CFDictionary::from_CFType_pairs(&[(
CFString::new("DHCPLeaseTimeSecs"),
CFNumber::from(lease_time as i32),
)]);
let bootpd_dict = CFDictionary::from_CFType_pairs(&[
(
CFString::new("DHCPLeaseTimeSecs"),
CFNumber::from(lease_time as i32).as_CFType(),
),
(
CFString::new("dhcp_ignore_client_identifier"),
CFBoolean::true_value().as_CFType(),
),
]);
unsafe {
SCPreferencesSetValue(
prefs.as_concrete_TypeRef(),
CFString::new("bootpd").as_concrete_TypeRef(),
bootpd_dict.as_concrete_TypeRef().cast(),
let prefs = prefs.as_concrete_TypeRef();
anyhow::ensure!(
SCPreferencesLock(prefs, 1) != 0,
"failed to lock bootpd preferences"
);
SCPreferencesCommitChanges(prefs.as_concrete_TypeRef());
let result = (|| -> anyhow::Result<()> {
anyhow::ensure!(
SCPreferencesSetValue(
prefs,
CFString::new("bootpd").as_concrete_TypeRef(),
bootpd_dict.as_concrete_TypeRef().cast(),
) != 0,
"failed to set bootpd preferences"
);
anyhow::ensure!(
SCPreferencesCommitChanges(prefs) != 0,
"failed to commit bootpd preferences"
);
anyhow::ensure!(
SCPreferencesApplyChanges(prefs) != 0,
"failed to apply bootpd preferences"
);
Ok(())
})();
let unlocked = SCPreferencesUnlock(prefs) != 0;
result?;
anyhow::ensure!(unlocked, "failed to unlock bootpd preferences");
}
Ok(())
}
#[cfg(test)]