diff --git a/CHANGELOG.md b/CHANGELOG.md index b00a67c..dda2d51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,123 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## [0.9.23] — 2026-05-09 + +**Rootless track, atomic single-iface verbs.** Twenty-fourth +0.9.x release. **First verb-set expansion since 0.9.0** — +previously the 14-verb taxonomy was treated as frozen, but +the `crate run` host-side bridge plumbing has primitives +(`setUp`, `disableOffload`) that don't compose well with +the existing `configure_iface` composite verb. + +### What lands + +#### Two new privops verbs + +- **`set_iface_up`** — wraps `IfconfigOps::setUp(ifname)`. + Single string arg; idempotent (already-up succeeds). +- **`disable_iface_offload`** — wraps + `IfconfigOps::disableOffload(ifname)`. Same shape; the + FreeBSD 15 checksum-offload workaround. + +Why these two together: same shape (1 ifname, no +response data), called as a sibling pair in +`run_net.cpp::setupBridgeEpair`, and small enough that +splitting into separate releases would be unhelpful. + +#### Wire-up across the stack + +- `lib/privops_pure.{h,cpp}` — `Verb::SetIfaceUp`, + `Verb::DisableIfaceOffload` + request structs + + validators (delegate to existing `validateIfaceName`) +- `lib/privops_wire_pure.{h,cpp}` — JSON parsers + + `formatSetIfaceUpSuccess` / `formatDisableIfaceOffloadSuccess` + builders + dispatcher cases +- `lib/privops_nv_pure.{h,cpp}` — nv parsers (FieldMap) +- `lib/privops_client.h` + `lib/privops_client_pure.cpp` — + `buildSetIfaceUp` / `buildDisableIfaceOffload` +- `daemon/privops_handlers.{h,cpp}` — + `handleSetIfaceUp` / `handleDisableIfaceOffload` + + dispatcher cases (HTTP + libnv transports) + +#### CLI wiring + +`lib/run_net.cpp` gets two new file-static helpers +(`setUpPrivopsOrLocal`, `disableOffloadPrivopsOrLocal`) +mirroring the 0.9.20 `moveToVnetPrivopsOrLocal` pattern. +Five call-sites are migrated: + +| Site | Op | +|------|-----| +| `createEpair` line 160 | disableOffload(ifaceA) | +| `createEpair` line 161 | disableOffload(ifaceB) | +| `setupBridgeEpair` line 436 | disableOffload(ifaceA) | +| `setupBridgeEpair` line 437 | disableOffload(ifaceB) | +| `setupBridgeEpair` line 440 | setUp(ifaceA) | + +### Why expand the verb set + +The `configure_iface` composite verb (0.9.6) bundles +move + IP/MAC config + bridge attach. It assumes a +spec-driven "give me everything at once" usage. The +`run_net.cpp` orchestration is streaming — interleave +IfconfigOps calls with state-tracking and other ops. +For that pattern, atomic verbs match better. The +0.9.0 taxonomy contract isn't broken; new verbs append +to the closed set. + +### Trade-offs + +- **No rollback** of either op. The handler succeeds or + fails the entire op atomically. setUp + disableOffload + are themselves idempotent, so retry-on-failure works. +- **Other host-side IfconfigOps still need root.** + `bridgeAddMember`, `setInetAddr`, `createEpair` haven't + got matching verbs yet. 0.9.24 plan: `bridge_add_member` + + `set_iface_inet_addr`. 0.9.25: `create_epair` (returns + pair names — first verb with non-trivial response data). + +### Series state + +CLI call-sites wired: +- `crate retune` → set_rctl / clear_rctl (0.9.15) +- `crate stop` → destroy_jail (0.9.17) +- `crate run` ZFS attach + detach → attach_zfs / detach_zfs (0.9.18) +- `crate run` nullfs mounts (8 sites) → mount_nullfs (0.9.19) +- `crate run` vnet moveToVnet (4 sites) → configure_iface move-only (0.9.20) +- `crate run` removeJail teardown → destroy_jail (0.9.21) +- `crate run` createJail → create_jail (0.9.22) +- **`crate run` setUp + disableOffload (5 sites) → set_iface_up / disable_iface_offload ← this release** + +Remaining: +- 0.9.24 — `bridge_add_member` + `set_iface_inet_addr` verbs +- 0.9.25 — `create_epair` (first response-data verb) +- 0.9.26 — `network_lease.cpp` per-user paths + RCTL umbrella +- 0.9.27 — default flip +- 1.0.0 — setuid removed + +### Tests + +2 new ATF tests (`set_iface_up_minimal`, +`disable_iface_offload_minimal`) in privops_pure_test. +`verb_token_roundtrips_for_every_verb` updated to include +the 2 new verbs (catches future enum-add-without-mapping). +Suite: 1294 → **1296**. + +### Files + +- `lib/privops_pure.{h,cpp}` — verb enum + structs + validators +- `lib/privops_wire_pure.{h,cpp}` — JSON parsers + format builders +- `lib/privops_nv_pure.{h,cpp}` — nv parsers +- `lib/privops_client.h` + `lib/privops_client_pure.cpp` — builders +- `daemon/privops_handlers.{h,cpp}` — handlers + dispatcher cases +- `lib/run_net.cpp` — 2 new helpers + 5 call-site replacements +- `tests/unit/privops_pure_test.cpp` — 2 new tests +- `cli/args.cpp` — version `crate 0.9.23` +- `CHANGELOG.md` — entry + +--- + ## [0.9.22] — 2026-05-09 **Rootless track, `createJail` via privops.** Twenty-third diff --git a/cli/args.cpp b/cli/args.cpp index 736e539..b1fc2f6 100644 --- a/cli/args.cpp +++ b/cli/args.cpp @@ -753,7 +753,7 @@ Args parseArguments(int argc, char** argv, unsigned &processed) { args.noColor = true; break; } else if (strEq(argv[a], "--version")) { - std::cout << "crate 0.9.22" << std::endl; + std::cout << "crate 0.9.23" << std::endl; exit(0); } else if (auto argShort = isShort(argv[a])) { switch (argShort) { @@ -764,7 +764,7 @@ Args parseArguments(int argc, char** argv, unsigned &processed) { args.logProgress = true; break; case 'V': - std::cout << "crate 0.9.22" << std::endl; + std::cout << "crate 0.9.23" << std::endl; exit(0); default: err("unsupported short option '%s'", argv[a]); diff --git a/daemon/privops_handlers.cpp b/daemon/privops_handlers.cpp index 6b7b1f0..e81b326 100644 --- a/daemon/privops_handlers.cpp +++ b/daemon/privops_handlers.cpp @@ -372,6 +372,28 @@ DispatchResult handleDestroyJail(const PrivOpsPure::DestroyJailReq &r) { return {200, PrivOpsWirePure::formatDestroyJailSuccess(r.name)}; } +// --- handleSetIfaceUp / handleDisableIfaceOffload (0.9.23) --- + +DispatchResult handleSetIfaceUp(const PrivOpsPure::SetIfaceUpReq &r) { + try { + IfconfigOps::setUp(r.ifname); + } catch (const std::exception &e) { + return {500, PrivOpsWirePure::formatHandlerError("ifconfig_failed", + e.what())}; + } + return {200, PrivOpsWirePure::formatSetIfaceUpSuccess(r.ifname)}; +} + +DispatchResult handleDisableIfaceOffload(const PrivOpsPure::DisableIfaceOffloadReq &r) { + try { + IfconfigOps::disableOffload(r.ifname); + } catch (const std::exception &e) { + return {500, PrivOpsWirePure::formatHandlerError("ifconfig_failed", + e.what())}; + } + return {200, PrivOpsWirePure::formatDisableIfaceOffloadSuccess(r.ifname)}; +} + // --- Top-level dispatcher --- namespace { @@ -508,6 +530,22 @@ DispatchResult dispatchPrivOp(Verb v, const std::string &body, return {400, PrivOpsWirePure::formatValidateError(e)}; return handleDestroyJail(r); } + case Verb::SetIfaceUp: { + PrivOpsPure::SetIfaceUpReq r; + if (auto e = PrivOpsWirePure::parseSetIfaceUp(body, r); !e.empty()) + return {400, PrivOpsWirePure::formatParseError(e)}; + if (auto e = PrivOpsPure::validateSetIfaceUp(r); !e.empty()) + return {400, PrivOpsWirePure::formatValidateError(e)}; + return handleSetIfaceUp(r); + } + case Verb::DisableIfaceOffload: { + PrivOpsPure::DisableIfaceOffloadReq r; + if (auto e = PrivOpsWirePure::parseDisableIfaceOffload(body, r); !e.empty()) + return {400, PrivOpsWirePure::formatParseError(e)}; + if (auto e = PrivOpsPure::validateDisableIfaceOffload(r); !e.empty()) + return {400, PrivOpsWirePure::formatValidateError(e)}; + return handleDisableIfaceOffload(r); + } default: return PrivOpsWirePure::parseValidateAndDispatch(v, body); } @@ -638,6 +676,22 @@ DispatchResult dispatchPrivOpFromMap(const PrivOpsNvPure::FieldMap &m, return {400, PrivOpsWirePure::formatValidateError(e)}; return handleDestroyJail(r); } + case Verb::SetIfaceUp: { + PrivOpsPure::SetIfaceUpReq r; + if (auto e = PrivOpsNvPure::parseSetIfaceUp(m, r); !e.empty()) + return {400, PrivOpsWirePure::formatParseError(e)}; + if (auto e = PrivOpsPure::validateSetIfaceUp(r); !e.empty()) + return {400, PrivOpsWirePure::formatValidateError(e)}; + return handleSetIfaceUp(r); + } + case Verb::DisableIfaceOffload: { + PrivOpsPure::DisableIfaceOffloadReq r; + if (auto e = PrivOpsNvPure::parseDisableIfaceOffload(m, r); !e.empty()) + return {400, PrivOpsWirePure::formatParseError(e)}; + if (auto e = PrivOpsPure::validateDisableIfaceOffload(r); !e.empty()) + return {400, PrivOpsWirePure::formatValidateError(e)}; + return handleDisableIfaceOffload(r); + } case Verb::Unknown: return {404, std::string("{\"error\":\"unknown or missing 'verb' field\"}")}; diff --git a/daemon/privops_handlers.h b/daemon/privops_handlers.h index a47af8b..152acf4 100644 --- a/daemon/privops_handlers.h +++ b/daemon/privops_handlers.h @@ -145,4 +145,11 @@ PrivOpsWirePure::DispatchResult handleRemoveIpfwRule(const PrivOpsPure::RemoveIp PrivOpsWirePure::DispatchResult handleCreateJail(const PrivOpsPure::CreateJailReq &r); PrivOpsWirePure::DispatchResult handleDestroyJail(const PrivOpsPure::DestroyJailReq &r); +// 0.9.23: atomic single-iface ops needed by setupBridgeEpair flow. +// Wrap IfconfigOps::setUp / disableOffload (which themselves use +// libifconfig with shell fallback). Both succeed silently on +// idempotent calls (already up / already disabled). +PrivOpsWirePure::DispatchResult handleSetIfaceUp(const PrivOpsPure::SetIfaceUpReq &r); +PrivOpsWirePure::DispatchResult handleDisableIfaceOffload(const PrivOpsPure::DisableIfaceOffloadReq &r); + } // namespace Crated diff --git a/lib/privops_client.h b/lib/privops_client.h index 6bf6a01..de4e44a 100644 --- a/lib/privops_client.h +++ b/lib/privops_client.h @@ -108,6 +108,10 @@ PrivOpsNvPure::FieldMap buildCreateJail(const std::string &name, PrivOpsNvPure::FieldMap buildDestroyJail(const std::string &name, bool force); +// 0.9.23: atomic single-iface ops. +PrivOpsNvPure::FieldMap buildSetIfaceUp(const std::string &ifname); +PrivOpsNvPure::FieldMap buildDisableIfaceOffload(const std::string &ifname); + // --- Wire transport (FreeBSD-only) --- struct Response { diff --git a/lib/privops_client_pure.cpp b/lib/privops_client_pure.cpp index 84e2853..725887e 100644 --- a/lib/privops_client_pure.cpp +++ b/lib/privops_client_pure.cpp @@ -219,4 +219,18 @@ PrivOpsNvPure::FieldMap buildDestroyJail(const std::string &name, bool force) { }; } +PrivOpsNvPure::FieldMap buildSetIfaceUp(const std::string &ifname) { + return { + {"verb", "set_iface_up"}, + {"ifname", ifname}, + }; +} + +PrivOpsNvPure::FieldMap buildDisableIfaceOffload(const std::string &ifname) { + return { + {"verb", "disable_iface_offload"}, + {"ifname", ifname}, + }; +} + } // namespace PrivOpsClient diff --git a/lib/privops_nv_pure.cpp b/lib/privops_nv_pure.cpp index 4aba71e..97eaa96 100644 --- a/lib/privops_nv_pure.cpp +++ b/lib/privops_nv_pure.cpp @@ -200,6 +200,18 @@ std::string parseRemoveIpfwRule(const FieldMap &m, return ""; } +std::string parseSetIfaceUp(const FieldMap &m, + PrivOpsPure::SetIfaceUpReq &out) { + if (auto e = requireString(m, "ifname", out.ifname); !e.empty()) return e; + return ""; +} + +std::string parseDisableIfaceOffload(const FieldMap &m, + PrivOpsPure::DisableIfaceOffloadReq &out) { + if (auto e = requireString(m, "ifname", out.ifname); !e.empty()) return e; + return ""; +} + // --- Verb routing --- PrivOpsPure::Verb extractVerb(const FieldMap &m) { diff --git a/lib/privops_nv_pure.h b/lib/privops_nv_pure.h index da2737b..c9f4b05 100644 --- a/lib/privops_nv_pure.h +++ b/lib/privops_nv_pure.h @@ -119,6 +119,10 @@ std::string parseAddIpfwRule(const FieldMap &m, PrivOpsPure::AddIpfwRuleReq &out); std::string parseRemoveIpfwRule(const FieldMap &m, PrivOpsPure::RemoveIpfwRuleReq &out); +std::string parseSetIfaceUp(const FieldMap &m, + PrivOpsPure::SetIfaceUpReq &out); +std::string parseDisableIfaceOffload(const FieldMap &m, + PrivOpsPure::DisableIfaceOffloadReq &out); // --- Verb routing --- diff --git a/lib/privops_pure.cpp b/lib/privops_pure.cpp index a4737fb..243f26b 100644 --- a/lib/privops_pure.cpp +++ b/lib/privops_pure.cpp @@ -85,6 +85,8 @@ const char *verbName(Verb v) { case Verb::RemovePfRule: return "remove_pf_rule"; case Verb::AddIpfwRule: return "add_ipfw_rule"; case Verb::RemoveIpfwRule: return "remove_ipfw_rule"; + case Verb::SetIfaceUp: return "set_iface_up"; + case Verb::DisableIfaceOffload: return "disable_iface_offload"; case Verb::Unknown: return "unknown"; } return "unknown"; @@ -105,6 +107,8 @@ Verb parseVerb(const std::string &name) { if (name == "remove_pf_rule") return Verb::RemovePfRule; if (name == "add_ipfw_rule") return Verb::AddIpfwRule; if (name == "remove_ipfw_rule") return Verb::RemoveIpfwRule; + if (name == "set_iface_up") return Verb::SetIfaceUp; + if (name == "disable_iface_offload") return Verb::DisableIfaceOffload; return Verb::Unknown; } @@ -465,4 +469,12 @@ std::string validateRemoveIpfwRule(const RemoveIpfwRuleReq &r) { return ""; } +std::string validateSetIfaceUp(const SetIfaceUpReq &r) { + return validateIfaceName(r.ifname); +} + +std::string validateDisableIfaceOffload(const DisableIfaceOffloadReq &r) { + return validateIfaceName(r.ifname); +} + } // namespace PrivOpsPure diff --git a/lib/privops_pure.h b/lib/privops_pure.h index c84866b..130e212 100644 --- a/lib/privops_pure.h +++ b/lib/privops_pure.h @@ -98,6 +98,15 @@ enum class Verb { RemovePfRule, AddIpfwRule, RemoveIpfwRule, + + // 0.9.23: atomic host-side iface ops needed by `crate run`'s + // setupBridgeEpair flow. The 0.9.0 ConfigureIface verb is the + // composite "move + config + bridge attach" path; these atomic + // verbs are the host-side primitives operators chain manually + // when they need finer control. Targets the `IfconfigOps::setUp` + // and `IfconfigOps::disableOffload` call sites in lib/run_net.cpp. + SetIfaceUp, + DisableIfaceOffload, }; // Returns the verb's canonical wire-format token (lowercase, no @@ -193,6 +202,15 @@ struct RemoveIpfwRuleReq { unsigned number = 0; }; +// 0.9.23: atomic single-iface ops. +struct SetIfaceUpReq { + std::string ifname; +}; + +struct DisableIfaceOffloadReq { + std::string ifname; +}; + // --- Per-verb validators --- // // Each `validate*(req)` returns "" on success, otherwise a one-line @@ -215,6 +233,8 @@ std::string validateAddPfRule(const AddPfRuleReq &r); std::string validateRemovePfRule(const RemovePfRuleReq &r); std::string validateAddIpfwRule(const AddIpfwRuleReq &r); std::string validateRemoveIpfwRule(const RemoveIpfwRuleReq &r); +std::string validateSetIfaceUp(const SetIfaceUpReq &r); +std::string validateDisableIfaceOffload(const DisableIfaceOffloadReq &r); // --- Field-level validators (exposed for tests + reuse) --- // diff --git a/lib/privops_wire_pure.cpp b/lib/privops_wire_pure.cpp index 590f498..4da1f66 100644 --- a/lib/privops_wire_pure.cpp +++ b/lib/privops_wire_pure.cpp @@ -336,6 +336,18 @@ std::string parseRemoveIpfwRule(const std::string &body, return ""; } +std::string parseSetIfaceUp(const std::string &body, + PrivOpsPure::SetIfaceUpReq &out) { + if (auto e = requireStringField(body, "ifname", out.ifname); !e.empty()) return e; + return ""; +} + +std::string parseDisableIfaceOffload(const std::string &body, + PrivOpsPure::DisableIfaceOffloadReq &out) { + if (auto e = requireStringField(body, "ifname", out.ifname); !e.empty()) return e; + return ""; +} + // --- Verb routing helper --- PrivOpsPure::Verb parseVerbFromPath(const std::string &path) { @@ -574,6 +586,22 @@ std::string formatDestroyJailSuccess(const std::string &name) { return o.str(); } +std::string formatSetIfaceUpSuccess(const std::string &ifname) { + std::ostringstream o; + o << "{\"up\":true" + << ",\"ifname\":\"" << escape(ifname) << "\"" + << "}"; + return o.str(); +} + +std::string formatDisableIfaceOffloadSuccess(const std::string &ifname) { + std::ostringstream o; + o << "{\"offload_disabled\":true" + << ",\"ifname\":\"" << escape(ifname) << "\"" + << "}"; + return o.str(); +} + DispatchResult parseValidateAndDispatch(PrivOpsPure::Verb v, const std::string &body) { using namespace PrivOpsPure; @@ -606,6 +634,10 @@ DispatchResult parseValidateAndDispatch(PrivOpsPure::Verb v, return runVerb(body, v, parseAddIpfwRule, validateAddIpfwRule); case Verb::RemoveIpfwRule: return runVerb(body, v, parseRemoveIpfwRule, validateRemoveIpfwRule); + case Verb::SetIfaceUp: + return runVerb(body, v, parseSetIfaceUp, validateSetIfaceUp); + case Verb::DisableIfaceOffload: + return runVerb(body, v, parseDisableIfaceOffload, validateDisableIfaceOffload); case Verb::Unknown: break; } diff --git a/lib/privops_wire_pure.h b/lib/privops_wire_pure.h index fe634dd..d9fd4a6 100644 --- a/lib/privops_wire_pure.h +++ b/lib/privops_wire_pure.h @@ -156,6 +156,12 @@ std::string parseAddIpfwRule(const std::string &body, std::string parseRemoveIpfwRule(const std::string &body, PrivOpsPure::RemoveIpfwRuleReq &out); +std::string parseSetIfaceUp(const std::string &body, + PrivOpsPure::SetIfaceUpReq &out); + +std::string parseDisableIfaceOffload(const std::string &body, + PrivOpsPure::DisableIfaceOffloadReq &out); + // --- Verb routing helper --- // // Parse the URL path's verb segment. The route pattern is @@ -261,4 +267,8 @@ std::string formatCreateJailSuccess(const std::string &name, const std::string &path); std::string formatDestroyJailSuccess(const std::string &name); +// 0.9.23: 200 OK bodies for atomic single-iface ops. +std::string formatSetIfaceUpSuccess(const std::string &ifname); +std::string formatDisableIfaceOffloadSuccess(const std::string &ifname); + } // namespace PrivOpsWirePure diff --git a/lib/run_net.cpp b/lib/run_net.cpp index 60e9681..c3fca61 100644 --- a/lib/run_net.cpp +++ b/lib/run_net.cpp @@ -64,6 +64,40 @@ static void moveToVnetPrivopsOrLocal(const std::string &iface, int jid) { IfconfigOps::moveToVnet(iface, jid); } +// 0.9.23: privops-aware wrappers for setUp + disableOffload, sibling +// pattern to moveToVnetPrivopsOrLocal above. +static void setUpPrivopsOrLocal(const std::string &iface) { + std::string sock = PrivOpsClient::detectSocketPath(); + if (!sock.empty()) { + auto resp = PrivOpsClient::sendRequest(sock, + PrivOpsClient::buildSetIfaceUp(iface)); + if (!resp.transportError.empty()) + ERR2("run_net", "privops set_iface_up '" << iface + << "' transport error: " << resp.transportError) + if (resp.status >= 400) + ERR2("run_net", "privops set_iface_up '" << iface + << "' failed (status " << resp.status << "): " << resp.body) + return; + } + IfconfigOps::setUp(iface); +} + +static void disableOffloadPrivopsOrLocal(const std::string &iface) { + std::string sock = PrivOpsClient::detectSocketPath(); + if (!sock.empty()) { + auto resp = PrivOpsClient::sendRequest(sock, + PrivOpsClient::buildDisableIfaceOffload(iface)); + if (!resp.transportError.empty()) + ERR2("run_net", "privops disable_iface_offload '" << iface + << "' transport error: " << resp.transportError) + if (resp.status >= 400) + ERR2("run_net", "privops disable_iface_offload '" << iface + << "' failed (status " << resp.status << "): " << resp.body) + return; + } + IfconfigOps::disableOffload(iface); +} + GatewayInfo detectGateway() { GatewayInfo gw; @@ -123,8 +157,8 @@ EpairInfo createEpair(int jid, const std::string &jidStr, info.ipB = epairNumToIp(info.num, 1); // disable checksum offload on epair interfaces to work around FreeBSD 15.0 bug - IfconfigOps::disableOffload(info.ifaceA); - IfconfigOps::disableOffload(info.ifaceB); + disableOffloadPrivopsOrLocal(info.ifaceA); + disableOffloadPrivopsOrLocal(info.ifaceB); // transfer the interface into jail moveToVnetPrivopsOrLocal(info.ifaceB, jid); @@ -399,11 +433,11 @@ BridgeInfo createBridgeEpair(int jid, const std::string &jidStr, info.num = Util::toUInt(info.ifaceA.substr(5, info.ifaceA.size()-5-1)); // disable checksum offload (FreeBSD 15 workaround) - IfconfigOps::disableOffload(info.ifaceA); - IfconfigOps::disableOffload(info.ifaceB); + disableOffloadPrivopsOrLocal(info.ifaceA); + disableOffloadPrivopsOrLocal(info.ifaceB); // bring host-side up and add to bridge - IfconfigOps::setUp(info.ifaceA); + setUpPrivopsOrLocal(info.ifaceA); IfconfigOps::bridgeAddMember(bridgeIface, info.ifaceA); // move jail-side into jail diff --git a/tests/unit/privops_pure_test.cpp b/tests/unit/privops_pure_test.cpp index 51dd688..66e72e0 100644 --- a/tests/unit/privops_pure_test.cpp +++ b/tests/unit/privops_pure_test.cpp @@ -23,6 +23,7 @@ ATF_TEST_CASE_BODY(verb_token_roundtrips_for_every_verb) { Verb::ConfigureIface, Verb::TeardownIface, Verb::AddPfRule, Verb::RemovePfRule, Verb::AddIpfwRule, Verb::RemoveIpfwRule, + Verb::SetIfaceUp, Verb::DisableIfaceOffload, }; for (Verb v : verbs) { std::string token = verbName(v); @@ -488,6 +489,26 @@ ATF_TEST_CASE_BODY(teardown_iface_minimal) { ATF_REQUIRE(!validateTeardownIface(r).empty()); } +ATF_TEST_CASE_WITHOUT_HEAD(set_iface_up_minimal); +ATF_TEST_CASE_BODY(set_iface_up_minimal) { + SetIfaceUpReq r; + r.ifname = "epair0a"; + ATF_REQUIRE_EQ(validateSetIfaceUp(r), std::string()); + r.ifname = ""; // empty -> reject + ATF_REQUIRE(!validateSetIfaceUp(r).empty()); + r.ifname = "name with space"; + ATF_REQUIRE(!validateSetIfaceUp(r).empty()); +} + +ATF_TEST_CASE_WITHOUT_HEAD(disable_iface_offload_minimal); +ATF_TEST_CASE_BODY(disable_iface_offload_minimal) { + DisableIfaceOffloadReq r; + r.ifname = "epair0a"; + ATF_REQUIRE_EQ(validateDisableIfaceOffload(r), std::string()); + r.ifname = "name;rm"; + ATF_REQUIRE(!validateDisableIfaceOffload(r).empty()); +} + ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, verb_token_roundtrips_for_every_verb); ATF_ADD_TEST_CASE(tcs, verb_unknown_token_returns_unknown); @@ -545,4 +566,6 @@ ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, add_ipfw_rule_validates_set_number_action); ATF_ADD_TEST_CASE(tcs, destroy_jail_minimal); ATF_ADD_TEST_CASE(tcs, teardown_iface_minimal); + ATF_ADD_TEST_CASE(tcs, set_iface_up_minimal); + ATF_ADD_TEST_CASE(tcs, disable_iface_offload_minimal); }