mirror of
https://github.com/jamesstringer90/appsandbox.git
synced 2026-10-19 01:31:28 -05:00
Use DHCP for Windows-host NAT networking
This commit is contained in:
1 parent
4e2493dfbe
commit
c737c292cf
8 files changed
+155
-509
No files matched your search
+8
-146
@@ -501,8 +501,6 @@ static void save_vm_list(void)
|
||||
fwprintf(f, L"NetAdapter=%s\n", g_vms[i].net_adapter);
|
||||
if (g_vms[i].resources_iso_path[0] != L'\0')
|
||||
fwprintf(f, L"ResourcesIso=%s\n", g_vms[i].resources_iso_path);
|
||||
if (g_vms[i].nat_ip[0] != '\0')
|
||||
fwprintf(f, L"NatIp=%S\n", g_vms[i].nat_ip);
|
||||
if (g_vms[i].is_template)
|
||||
fwprintf(f, L"IsTemplate=1\n");
|
||||
if (g_vms[i].test_mode)
|
||||
@@ -650,8 +648,6 @@ static void load_vm_list(void)
|
||||
wcscpy_s(vm->net_adapter, 256, line + 11);
|
||||
else if (wcsncmp(line, L"ResourcesIso=", 13) == 0)
|
||||
wcscpy_s(vm->resources_iso_path, MAX_PATH, line + 13);
|
||||
else if (wcsncmp(line, L"NatIp=", 6) == 0)
|
||||
WideCharToMultiByte(CP_UTF8, 0, line + 6, -1, vm->nat_ip, sizeof(vm->nat_ip), NULL, NULL);
|
||||
else if (wcsncmp(line, L"IsTemplate=", 11) == 0)
|
||||
vm->is_template = (_wtoi(line + 11) != 0);
|
||||
else if (wcsncmp(line, L"TestMode=", 9) == 0)
|
||||
@@ -829,7 +825,6 @@ static void asb_hcs_state_changed(VmInstance *instance, DWORD event)
|
||||
asb_log(L"VM \"%s\" exited (event=0x%08X).", instance->name, event);
|
||||
|
||||
asb_vm_cleanup_network(instance);
|
||||
instance->nat_ip[0] = '\0';
|
||||
hcs_close_vm(instance);
|
||||
|
||||
/* Template finalization */
|
||||
@@ -920,75 +915,6 @@ ASB_API void asb_vm_cleanup_network(VmInstance *vm)
|
||||
vm->network_cleaned = TRUE;
|
||||
}
|
||||
|
||||
/* ---- NAT IP allocation ---- */
|
||||
|
||||
/* Allocate the next free IP in the chosen NAT /24 (see hcn_nat_subnet_base).
|
||||
Scans g_vms[] for IPs already in use. Returns FALSE if pool exhausted. */
|
||||
static BOOL allocate_nat_ip(VmInstance *vm)
|
||||
{
|
||||
BOOL used[256] = { 0 };
|
||||
const char *base = hcn_nat_subnet_base(); /* e.g. "192.168.42" */
|
||||
int ba, bb, bc;
|
||||
int i, octet;
|
||||
|
||||
if (sscanf_s(base, "%d.%d.%d", &ba, &bb, &bc) != 3) return FALSE;
|
||||
|
||||
for (i = 0; i < g_vm_count; i++) {
|
||||
int a, b, c, d;
|
||||
if (&g_vms[i] == vm) continue;
|
||||
if (g_vms[i].nat_ip[0] == '\0') continue;
|
||||
/* Only consider entries that share our current /24. Stale entries
|
||||
from a prior run with a different subnet are ignored - their
|
||||
endpoints died with the previous NAT network. */
|
||||
if (sscanf_s(g_vms[i].nat_ip, "%d.%d.%d.%d", &a, &b, &c, &d) == 4 &&
|
||||
a == ba && b == bb && c == bc && d >= 2 && d <= 254)
|
||||
used[d] = TRUE;
|
||||
}
|
||||
|
||||
for (octet = 2; octet <= 254; octet++) {
|
||||
if (!used[octet]) {
|
||||
sprintf_s(vm->nat_ip, sizeof(vm->nat_ip), "%s.%d", base, octet);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Try hcn_create_endpoint, retrying on NAT failure by bumping the last
|
||||
octet of nat_ip up to 10 times. The buffer pointed to by nat_ip is
|
||||
mutated in place on a successful retry; the caller is responsible for
|
||||
save_vm_list() if it cares about persistence.
|
||||
|
||||
Defends against HCN_E_ADDR_INVALID_OR_RESERVED (0x803B002F), which
|
||||
HNS returns when an endpoint at the chosen IP was orphaned by a
|
||||
previous run -- the IP is in our allocator's free list but HNS still
|
||||
has a phantom reservation. Bumping past it usually wins. */
|
||||
static HRESULT try_endpoint_with_retry(const GUID *net_id, GUID *ep_id,
|
||||
wchar_t *ep_guid_str, size_t str_len,
|
||||
char *nat_ip, size_t nat_ip_size,
|
||||
BOOL is_nat)
|
||||
{
|
||||
HRESULT hr;
|
||||
int retry;
|
||||
|
||||
hr = hcn_create_endpoint(net_id, ep_id, ep_guid_str, str_len,
|
||||
(nat_ip && nat_ip[0]) ? nat_ip : NULL);
|
||||
if (SUCCEEDED(hr) || !is_nat || !nat_ip || !nat_ip[0]) return hr;
|
||||
|
||||
asb_log(L"Endpoint failed for %S, trying next IP...", nat_ip);
|
||||
for (retry = 0; retry < 10; retry++) {
|
||||
int a, b, c, d;
|
||||
if (sscanf_s(nat_ip, "%d.%d.%d.%d", &a, &b, &c, &d) != 4 || d >= 254) break;
|
||||
sprintf_s(nat_ip, nat_ip_size, "%d.%d.%d.%d", a, b, c, d + 1);
|
||||
asb_log(L"Retrying with %S...", nat_ip);
|
||||
hr = hcn_create_endpoint(net_id, ep_id, ep_guid_str, str_len, nat_ip);
|
||||
if (SUCCEEDED(hr)) return hr;
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
/* ---- Background VM start thread ---- */
|
||||
|
||||
typedef struct {
|
||||
VmInstance *vm;
|
||||
VmConfig config;
|
||||
@@ -1004,18 +930,6 @@ static DWORD WINAPI start_vm_thread(LPVOID param)
|
||||
HRESULT hr;
|
||||
wchar_t endpoint_guid_str[64] = { 0 };
|
||||
|
||||
/* Allocate NAT IP before endpoint creation (only for NAT mode) */
|
||||
if (args->network_mode == NET_NAT) {
|
||||
if (allocate_nat_ip(vm)) {
|
||||
asb_log(L"Allocated NAT IP %S for \"%s\".", vm->nat_ip, vm->name);
|
||||
save_vm_list();
|
||||
} else {
|
||||
asb_log(L"Warning: NAT IP pool exhausted.");
|
||||
}
|
||||
} else {
|
||||
vm->nat_ip[0] = '\0';
|
||||
}
|
||||
|
||||
if (args->network_mode != NET_NONE) {
|
||||
switch (args->network_mode) {
|
||||
case NET_NAT: hr = hcn_create_nat_network(&args->network_id); break;
|
||||
@@ -1024,11 +938,8 @@ static DWORD WINAPI start_vm_thread(LPVOID param)
|
||||
default: hr = E_INVALIDARG; break;
|
||||
}
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = try_endpoint_with_retry(&args->network_id, &args->endpoint_id,
|
||||
endpoint_guid_str, 64,
|
||||
vm->nat_ip, sizeof(vm->nat_ip),
|
||||
args->network_mode == NET_NAT);
|
||||
if (SUCCEEDED(hr) && args->network_mode == NET_NAT) save_vm_list();
|
||||
hr = hcn_create_endpoint(&args->network_id, &args->endpoint_id,
|
||||
endpoint_guid_str, 64);
|
||||
if (FAILED(hr)) {
|
||||
asb_log(L"Error: Network endpoint failed (0x%08X).", hr);
|
||||
if (g_state_cb) g_state_cb(vm_handle(vm), FALSE, g_state_ud);
|
||||
@@ -1304,20 +1215,8 @@ static DWORD WINAPI vhdx_create_thread(LPVOID param)
|
||||
|
||||
args->vhdx_created = TRUE;
|
||||
|
||||
/* Allocate NAT IP before endpoint creation.
|
||||
args->vm_inst isn't set yet (HCS VM not created), so allocate
|
||||
into the g_vms[] entry directly via vm_index. */
|
||||
if (args->config.network_mode == NET_NAT && args->vm_index >= 0 && args->vm_index < g_vm_count) {
|
||||
if (allocate_nat_ip(&g_vms[args->vm_index])) {
|
||||
asb_log(L"Allocated NAT IP %S for new VM.", g_vms[args->vm_index].nat_ip);
|
||||
save_vm_list();
|
||||
}
|
||||
}
|
||||
|
||||
/* Network */
|
||||
if (args->config.network_mode != NET_NONE) {
|
||||
char *nat_ip = (args->vm_index >= 0 && args->vm_index < g_vm_count)
|
||||
? g_vms[args->vm_index].nat_ip : NULL;
|
||||
switch (args->config.network_mode) {
|
||||
case NET_NAT: hr = hcn_create_nat_network(&args->network_id); break;
|
||||
case NET_INTERNAL: hr = hcn_create_internal_network(&args->network_id); break;
|
||||
@@ -1325,15 +1224,10 @@ static DWORD WINAPI vhdx_create_thread(LPVOID param)
|
||||
default: hr = E_INVALIDARG; break;
|
||||
}
|
||||
if (SUCCEEDED(hr)) {
|
||||
BOOL is_nat = (args->config.network_mode == NET_NAT);
|
||||
size_t ip_size = (args->vm_index >= 0 && args->vm_index < g_vm_count)
|
||||
? sizeof(g_vms[args->vm_index].nat_ip) : 0;
|
||||
hr = try_endpoint_with_retry(&args->network_id, &args->endpoint_id,
|
||||
args->endpoint_guid, 64,
|
||||
nat_ip, ip_size, is_nat);
|
||||
hr = hcn_create_endpoint(&args->network_id, &args->endpoint_id,
|
||||
args->endpoint_guid, 64);
|
||||
if (SUCCEEDED(hr)) {
|
||||
args->has_network = TRUE;
|
||||
if (is_nat) save_vm_list();
|
||||
}
|
||||
/* If endpoint create fails, leave the network alone - it may be
|
||||
shared with other VMs. Orphan networks are cleaned up at next
|
||||
@@ -2466,21 +2360,8 @@ static DWORD WINAPI linux_create_thread(LPVOID param)
|
||||
runtime VM — the installed VHDX is fully self-bootable. */
|
||||
args->config.image_path[0] = L'\0';
|
||||
|
||||
/* ---- 2. NAT IP allocation. The chosen static /24 address is baked
|
||||
into the HCN endpoint policy below and pushed to the guest at
|
||||
runtime by the agent (set_ip -> static netplan); the guest never
|
||||
DHCPs in NAT mode. ---- */
|
||||
if (args->config.network_mode == NET_NAT && args->vm_index >= 0 && args->vm_index < g_vm_count) {
|
||||
if (allocate_nat_ip(&g_vms[args->vm_index])) {
|
||||
asb_log(L"Allocated NAT IP %S for new VM.", g_vms[args->vm_index].nat_ip);
|
||||
save_vm_list();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 5. Network + endpoint (same as vhdx_create_thread:1114). ---- */
|
||||
if (args->config.network_mode != NET_NONE) {
|
||||
char *nat_ip = (args->vm_index >= 0 && args->vm_index < g_vm_count)
|
||||
? g_vms[args->vm_index].nat_ip : NULL;
|
||||
switch (args->config.network_mode) {
|
||||
case NET_NAT: hr = hcn_create_nat_network(&args->network_id); break;
|
||||
case NET_INTERNAL: hr = hcn_create_internal_network(&args->network_id); break;
|
||||
@@ -2488,15 +2369,10 @@ static DWORD WINAPI linux_create_thread(LPVOID param)
|
||||
default: hr = E_INVALIDARG; break;
|
||||
}
|
||||
if (SUCCEEDED(hr)) {
|
||||
BOOL is_nat = (args->config.network_mode == NET_NAT);
|
||||
size_t ip_size = (args->vm_index >= 0 && args->vm_index < g_vm_count)
|
||||
? sizeof(g_vms[args->vm_index].nat_ip) : 0;
|
||||
hr = try_endpoint_with_retry(&args->network_id, &args->endpoint_id,
|
||||
args->endpoint_guid, 64,
|
||||
nat_ip, ip_size, is_nat);
|
||||
hr = hcn_create_endpoint(&args->network_id, &args->endpoint_id,
|
||||
args->endpoint_guid, 64);
|
||||
if (SUCCEEDED(hr)) {
|
||||
args->has_network = TRUE;
|
||||
if (is_nat) save_vm_list();
|
||||
}
|
||||
}
|
||||
if (FAILED(hr))
|
||||
@@ -3431,16 +3307,6 @@ ASB_API HRESULT asb_vm_create(const AsbVmConfig *config)
|
||||
|
||||
inst->network_cleaned = FALSE;
|
||||
|
||||
/* Allocate NAT IP before endpoint creation */
|
||||
if (cfg.network_mode == NET_NAT) {
|
||||
if (allocate_nat_ip(inst)) {
|
||||
asb_log(L"Allocated NAT IP %S for \"%s\".", inst->nat_ip, inst->name);
|
||||
save_vm_list();
|
||||
}
|
||||
} else {
|
||||
inst->nat_ip[0] = '\0';
|
||||
}
|
||||
|
||||
/* Networking */
|
||||
if (cfg.network_mode != NET_NONE) {
|
||||
switch (cfg.network_mode) {
|
||||
@@ -3453,11 +3319,8 @@ ASB_API HRESULT asb_vm_create(const AsbVmConfig *config)
|
||||
asb_log(L"Warning: Network failed (0x%08X). Continuing without.", hr);
|
||||
cfg.network_mode = NET_NONE;
|
||||
} else {
|
||||
hr = try_endpoint_with_retry(&inst->network_id, &inst->endpoint_id,
|
||||
endpoint_guid_str, 64,
|
||||
inst->nat_ip, sizeof(inst->nat_ip),
|
||||
cfg.network_mode == NET_NAT);
|
||||
if (SUCCEEDED(hr) && cfg.network_mode == NET_NAT) save_vm_list();
|
||||
hr = hcn_create_endpoint(&inst->network_id, &inst->endpoint_id,
|
||||
endpoint_guid_str, 64);
|
||||
if (FAILED(hr)) {
|
||||
asb_log(L"Warning: Endpoint failed (0x%08X).", hr);
|
||||
/* Leave the shared network alone - other VMs may be using it. */
|
||||
@@ -3681,7 +3544,6 @@ ASB_API HRESULT asb_vm_stop(AsbVm vm)
|
||||
hcs_close_vm(inst);
|
||||
|
||||
asb_vm_cleanup_network(inst);
|
||||
inst->nat_ip[0] = '\0';
|
||||
|
||||
asb_log(L"VM \"%s\" terminated.", inst->name);
|
||||
save_vm_list();
|
||||
|
||||
+82
-139
@@ -4,6 +4,7 @@
|
||||
#include <stdio.h>
|
||||
#include <objbase.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#pragma comment(lib, "iphlpapi.lib")
|
||||
|
||||
@@ -23,6 +24,10 @@ typedef HRESULT (WINAPI *PFN_HcnCloseNetwork)(void *network);
|
||||
typedef HRESULT (WINAPI *PFN_HcnCloseEndpoint)(void *endpoint);
|
||||
typedef HRESULT (WINAPI *PFN_HcnOpenNetwork)(
|
||||
REFGUID id, void **network, PWSTR *errorRecord);
|
||||
typedef HRESULT (WINAPI *PFN_HcnOpenEndpoint)(
|
||||
REFGUID id, void **endpoint, PWSTR *errorRecord);
|
||||
typedef HRESULT (WINAPI *PFN_HcnQueryEndpointProperties)(
|
||||
void *endpoint, PCWSTR query, PWSTR *properties, PWSTR *errorRecord);
|
||||
|
||||
/* ---- Loaded function pointers ---- */
|
||||
|
||||
@@ -35,6 +40,8 @@ static PFN_HcnDeleteEndpoint pfnDeleteEp;
|
||||
static PFN_HcnCloseNetwork pfnCloseNet;
|
||||
static PFN_HcnCloseEndpoint pfnCloseEp;
|
||||
static PFN_HcnOpenNetwork pfnOpenNet;
|
||||
static PFN_HcnOpenEndpoint pfnOpenEp;
|
||||
static PFN_HcnQueryEndpointProperties pfnQueryEp;
|
||||
|
||||
/* ---- Helpers ---- */
|
||||
|
||||
@@ -67,9 +74,12 @@ BOOL hcn_init(void)
|
||||
pfnCloseNet = (PFN_HcnCloseNetwork)GetProcAddress(g_hcn_dll, "HcnCloseNetwork");
|
||||
pfnCloseEp = (PFN_HcnCloseEndpoint)GetProcAddress(g_hcn_dll, "HcnCloseEndpoint");
|
||||
pfnOpenNet = (PFN_HcnOpenNetwork)GetProcAddress(g_hcn_dll, "HcnOpenNetwork");
|
||||
pfnOpenEp = (PFN_HcnOpenEndpoint)GetProcAddress(g_hcn_dll, "HcnOpenEndpoint");
|
||||
pfnQueryEp = (PFN_HcnQueryEndpointProperties)GetProcAddress(g_hcn_dll, "HcnQueryEndpointProperties");
|
||||
pfnEnumNet = (PFN_HcnEnumerateNetworks)GetProcAddress(g_hcn_dll, "HcnEnumerateNetworks");
|
||||
|
||||
if (!pfnCreateNet || !pfnCreateEp || !pfnCloseNet || !pfnCloseEp) {
|
||||
if (!pfnCreateNet || !pfnCreateEp || !pfnCloseNet || !pfnCloseEp ||
|
||||
!pfnOpenEp || !pfnQueryEp) {
|
||||
FreeLibrary(g_hcn_dll);
|
||||
g_hcn_dll = NULL;
|
||||
return FALSE;
|
||||
@@ -210,106 +220,6 @@ void hcn_cleanup(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* NAT subnet selection
|
||||
*
|
||||
* We use a small fixed list of /24 candidates and pick the first one that
|
||||
* doesn't overlap any IPv4 subnet currently bound to a host adapter.
|
||||
* Default Switch (172.20.48.0/20) is built-in on every Windows 11 host;
|
||||
* 192.168.42.0/24 sidesteps it. The fallback 192.168.142.0/24 covers the
|
||||
* unlikely case where the user's home network or VPN claims .42.
|
||||
*
|
||||
* Decided once on first call, stable for process lifetime. No saved-state
|
||||
* coordination, no per-VM adoption -- the same probe runs every start.
|
||||
* HCN's startup network-delete (hcn_cleanup_stale_networks) wipes the
|
||||
* previous run's network so the freshly-picked subnet is always honored.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static char g_nat_base[16]; /* "192.168.42" or "192.168.142", no trailing dot */
|
||||
|
||||
typedef struct { DWORD network; int prefix_len; } SubnetInfo;
|
||||
|
||||
static BOOL ranges_overlap(DWORD a_net, int a_prefix, DWORD b_net, int b_prefix)
|
||||
{
|
||||
DWORD a_mask = (a_prefix == 0) ? 0 : (~0u << (32 - a_prefix));
|
||||
DWORD b_mask = (b_prefix == 0) ? 0 : (~0u << (32 - b_prefix));
|
||||
DWORD a_start = a_net & a_mask;
|
||||
DWORD a_end = a_start | ~a_mask;
|
||||
DWORD b_start = b_net & b_mask;
|
||||
DWORD b_end = b_start | ~b_mask;
|
||||
return (a_start <= b_end) && (b_start <= a_end);
|
||||
}
|
||||
|
||||
static int collect_inuse_subnets(SubnetInfo *out, int cap)
|
||||
{
|
||||
ULONG size = 0;
|
||||
int n = 0;
|
||||
PIP_ADAPTER_ADDRESSES buf = NULL;
|
||||
PIP_ADAPTER_ADDRESSES a;
|
||||
GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST,
|
||||
NULL, NULL, &size);
|
||||
if (size == 0) return 0;
|
||||
buf = (PIP_ADAPTER_ADDRESSES)malloc(size);
|
||||
if (!buf) return 0;
|
||||
if (GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST,
|
||||
NULL, buf, &size) != NO_ERROR) {
|
||||
free(buf); return 0;
|
||||
}
|
||||
for (a = buf; a && n < cap; a = a->Next) {
|
||||
PIP_ADAPTER_UNICAST_ADDRESS u;
|
||||
for (u = a->FirstUnicastAddress; u && n < cap; u = u->Next) {
|
||||
SOCKADDR_IN *sin;
|
||||
if (!u->Address.lpSockaddr) continue;
|
||||
if (u->Address.lpSockaddr->sa_family != AF_INET) continue;
|
||||
if (u->OnLinkPrefixLength > 32) continue;
|
||||
sin = (SOCKADDR_IN *)u->Address.lpSockaddr;
|
||||
out[n].network = ntohl(sin->sin_addr.s_addr);
|
||||
out[n].prefix_len = u->OnLinkPrefixLength;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
return n;
|
||||
}
|
||||
|
||||
static void pick_nat_base_once(void)
|
||||
{
|
||||
static const char *CANDIDATES[] = { "192.168.42", "192.168.142", NULL };
|
||||
SubnetInfo used[64];
|
||||
int n_used, i, j;
|
||||
|
||||
if (g_nat_base[0]) return;
|
||||
|
||||
n_used = collect_inuse_subnets(used, 64);
|
||||
for (i = 0; CANDIDATES[i]; i++) {
|
||||
int a, b, c;
|
||||
DWORD candidate;
|
||||
BOOL conflict = FALSE;
|
||||
if (sscanf_s(CANDIDATES[i], "%d.%d.%d", &a, &b, &c) != 3) continue;
|
||||
candidate = ((DWORD)a << 24) | ((DWORD)b << 16) | ((DWORD)c << 8);
|
||||
for (j = 0; j < n_used; j++) {
|
||||
if (ranges_overlap(candidate, 24, used[j].network, used[j].prefix_len)) {
|
||||
conflict = TRUE; break;
|
||||
}
|
||||
}
|
||||
if (!conflict) {
|
||||
strcpy_s(g_nat_base, sizeof(g_nat_base), CANDIDATES[i]);
|
||||
ui_log(L"NAT subnet: %S.0/24 (gateway %S.1)", g_nat_base, g_nat_base);
|
||||
return;
|
||||
}
|
||||
}
|
||||
strcpy_s(g_nat_base, sizeof(g_nat_base), CANDIDATES[0]);
|
||||
ui_log(L"WARNING: every candidate NAT subnet (192.168.42.0/24, 192.168.142.0/24) overlaps a host adapter. "
|
||||
L"Using %S.0/24 anyway; HCN create may fail. Edit hcn_network.c CANDIDATES to add another.",
|
||||
g_nat_base);
|
||||
}
|
||||
|
||||
const char *hcn_nat_subnet_base(void)
|
||||
{
|
||||
pick_nat_base_once();
|
||||
return g_nat_base;
|
||||
}
|
||||
|
||||
HRESULT hcn_create_nat_network(GUID *network_id)
|
||||
{
|
||||
wchar_t settings[1024];
|
||||
@@ -327,22 +237,14 @@ HRESULT hcn_create_nat_network(GUID *network_id)
|
||||
if (hcn_network_exists(&APPSANDBOX_NAT_GUID))
|
||||
return S_OK;
|
||||
|
||||
pick_nat_base_once();
|
||||
|
||||
swprintf_s(settings, 1024,
|
||||
L"{"
|
||||
L"\"SchemaVersion\":{\"Major\":2,\"Minor\":0},"
|
||||
L"\"Name\":\"AppSandboxNAT\","
|
||||
L"\"Type\":\"NAT\","
|
||||
L"\"Ipams\":[{"
|
||||
L"\"Type\":\"Static\","
|
||||
L"\"Subnets\":[{"
|
||||
L"\"IpAddressPrefix\":\"%S.0/24\","
|
||||
L"\"Routes\":[{\"NextHop\":\"%S.1\",\"DestinationPrefix\":\"0.0.0.0/0\"}]"
|
||||
L"}]"
|
||||
L"}]"
|
||||
L"}",
|
||||
g_nat_base, g_nat_base);
|
||||
L"\"Type\":\"ICS\","
|
||||
L"\"IsolateSwitch\":true,"
|
||||
L"\"Flags\":11"
|
||||
L"}");
|
||||
|
||||
hr = pfnCreateNet(network_id, settings, &network, &error_record);
|
||||
|
||||
@@ -436,8 +338,7 @@ HRESULT hcn_create_external_network(GUID *network_id, const wchar_t *adapter_nam
|
||||
}
|
||||
|
||||
HRESULT hcn_create_endpoint(const GUID *network_id, GUID *endpoint_id,
|
||||
wchar_t *endpoint_guid_str, size_t str_len,
|
||||
const char *nat_ip)
|
||||
wchar_t *endpoint_guid_str, size_t str_len)
|
||||
{
|
||||
wchar_t net_guid_str[64];
|
||||
wchar_t ep_guid_str[64];
|
||||
@@ -459,30 +360,11 @@ HRESULT hcn_create_endpoint(const GUID *network_id, GUID *endpoint_id,
|
||||
guid_to_string(network_id, net_guid_str, 64);
|
||||
guid_to_string(endpoint_id, ep_guid_str, 64);
|
||||
|
||||
/* Static IP for NAT; DHCP for Internal (ICS) and External (Transparent) */
|
||||
if (IsEqualGUID(network_id, &APPSANDBOX_NAT_GUID) && nat_ip && nat_ip[0]) {
|
||||
swprintf_s(settings, 1024,
|
||||
L"{"
|
||||
L"\"SchemaVersion\":{\"Major\":2,\"Minor\":0},"
|
||||
L"\"HostComputeNetwork\":\"%s\","
|
||||
L"\"IpConfigurations\":[{\"IpAddress\":\"%S\",\"PrefixLength\":24}]"
|
||||
L"}", net_guid_str, nat_ip);
|
||||
} else if (IsEqualGUID(network_id, &APPSANDBOX_NAT_GUID)) {
|
||||
pick_nat_base_once();
|
||||
swprintf_s(settings, 1024,
|
||||
L"{"
|
||||
L"\"SchemaVersion\":{\"Major\":2,\"Minor\":0},"
|
||||
L"\"HostComputeNetwork\":\"%s\","
|
||||
L"\"IpConfigurations\":[{\"IpAddress\":\"%S.2\",\"PrefixLength\":24}]"
|
||||
L"}", net_guid_str, g_nat_base);
|
||||
} else {
|
||||
/* Internal (ICS DHCP) or External (LAN DHCP) - no static IP */
|
||||
swprintf_s(settings, 1024,
|
||||
L"{"
|
||||
L"\"SchemaVersion\":{\"Major\":2,\"Minor\":0},"
|
||||
L"\"HostComputeNetwork\":\"%s\""
|
||||
L"}", net_guid_str);
|
||||
}
|
||||
swprintf_s(settings, 1024,
|
||||
L"{"
|
||||
L"\"SchemaVersion\":{\"Major\":2,\"Minor\":0},"
|
||||
L"\"HostComputeNetwork\":\"%s\""
|
||||
L"}", net_guid_str);
|
||||
|
||||
hr = pfnCreateEp(network, endpoint_id, settings, &endpoint, &error_record);
|
||||
|
||||
@@ -500,6 +382,67 @@ HRESULT hcn_create_endpoint(const GUID *network_id, GUID *endpoint_id,
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT hcn_get_endpoint_mac(const wchar_t *endpoint_guid,
|
||||
wchar_t *mac_address, size_t str_len)
|
||||
{
|
||||
GUID endpoint_id;
|
||||
wchar_t guid[40];
|
||||
void *endpoint = NULL;
|
||||
PWSTR properties = NULL;
|
||||
PWSTR error_record = NULL;
|
||||
const wchar_t *value, *end;
|
||||
size_t i;
|
||||
HRESULT hr;
|
||||
|
||||
if (!endpoint_guid || !mac_address || str_len < 18)
|
||||
return E_INVALIDARG;
|
||||
mac_address[0] = L'\0';
|
||||
if (!g_hcn_dll || !pfnOpenEp || !pfnQueryEp || !pfnCloseEp)
|
||||
return E_NOT_VALID_STATE;
|
||||
|
||||
if (endpoint_guid[0] == L'{') {
|
||||
hr = CLSIDFromString(endpoint_guid, &endpoint_id);
|
||||
} else {
|
||||
if (_snwprintf_s(guid, 40, _TRUNCATE, L"{%s}", endpoint_guid) < 0)
|
||||
return E_INVALIDARG;
|
||||
hr = CLSIDFromString(guid, &endpoint_id);
|
||||
}
|
||||
if (FAILED(hr)) return hr;
|
||||
|
||||
hr = pfnOpenEp(&endpoint_id, &endpoint, &error_record);
|
||||
if (error_record) { CoTaskMemFree(error_record); error_record = NULL; }
|
||||
if (FAILED(hr)) goto done;
|
||||
|
||||
hr = pfnQueryEp(endpoint, L"{}", &properties, &error_record);
|
||||
if (FAILED(hr)) goto done;
|
||||
|
||||
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
||||
value = properties ? wcsstr(properties, L"\"MacAddress\"") : NULL;
|
||||
if (!value) goto done;
|
||||
value += wcslen(L"\"MacAddress\"");
|
||||
while (iswspace(*value)) value++;
|
||||
if (*value++ != L':') goto done;
|
||||
while (iswspace(*value)) value++;
|
||||
if (*value++ != L'\"') goto done;
|
||||
end = wcschr(value, L'\"');
|
||||
if (!end || end - value != 17) goto done;
|
||||
for (i = 0; i < 17; i++) {
|
||||
if (i % 3 == 2) {
|
||||
if (value[i] != L'-' && value[i] != L':') goto done;
|
||||
} else if (!iswxdigit(value[i])) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
wcsncpy_s(mac_address, str_len, value, 17);
|
||||
hr = S_OK;
|
||||
|
||||
done:
|
||||
if (error_record) CoTaskMemFree(error_record);
|
||||
if (properties) CoTaskMemFree(properties);
|
||||
if (endpoint) pfnCloseEp(endpoint);
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT hcn_delete_network(const GUID *network_id)
|
||||
{
|
||||
PWSTR error_record = NULL;
|
||||
|
||||
@@ -28,18 +28,9 @@ void hcn_cleanup(void);
|
||||
void hcn_cleanup_stale_networks(void);
|
||||
|
||||
/* Create a NAT network. Returns S_OK on success.
|
||||
network_id: output GUID for the created network.
|
||||
|
||||
The NAT subnet is chosen on first use from a small static candidate
|
||||
list (192.168.42.0/24, then 192.168.142.0/24) - the first that does
|
||||
not overlap a host adapter wins. Decided once per process, stable. */
|
||||
network_id: output GUID for the created network. */
|
||||
HRESULT hcn_create_nat_network(GUID *network_id);
|
||||
|
||||
/* Returns the first 3 octets of the chosen NAT subnet ("192.168.42" or
|
||||
"192.168.142"). Triggers the pick on first call. Stable for process
|
||||
lifetime. Callers build full IPs via printf-style formatting. */
|
||||
const char *hcn_nat_subnet_base(void);
|
||||
|
||||
/* Create an internal (host-only) network. Returns S_OK on success. */
|
||||
HRESULT hcn_create_internal_network(GUID *network_id);
|
||||
|
||||
@@ -51,13 +42,12 @@ HRESULT hcn_create_external_network(GUID *network_id, const wchar_t *adapter_nam
|
||||
/* Create an endpoint on a network.
|
||||
network_id: the network to attach to.
|
||||
endpoint_id: output GUID for the created endpoint.
|
||||
endpoint_guid_str: output string representation of endpoint GUID (for HCS JSON).
|
||||
nat_ip: for NAT networks, the static IP to assign (e.g. "172.20.0.2"); if
|
||||
NULL/empty, NAT endpoints default to "172.20.0.2". Ignored for
|
||||
Internal and External networks (which always use DHCP). */
|
||||
endpoint_guid_str: output string representation of endpoint GUID (for HCS JSON). */
|
||||
HRESULT hcn_create_endpoint(const GUID *network_id, GUID *endpoint_id,
|
||||
wchar_t *endpoint_guid_str, size_t str_len,
|
||||
const char *nat_ip);
|
||||
wchar_t *endpoint_guid_str, size_t str_len);
|
||||
|
||||
HRESULT hcn_get_endpoint_mac(const wchar_t *endpoint_guid,
|
||||
wchar_t *mac_address, size_t str_len);
|
||||
|
||||
/* Delete a network by GUID. */
|
||||
ASB_API HRESULT hcn_delete_network(const GUID *network_id);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "hcs_vm.h"
|
||||
#include "hcn_network.h"
|
||||
#include "vm_agent.h"
|
||||
#include "ui.h"
|
||||
#include "disk_util.h" /* ASB_IS_ARM64 */
|
||||
@@ -935,9 +936,12 @@ BOOL hcs_build_vm_json(const VmConfig *config, const wchar_t *endpoint_guid,
|
||||
/* Network adapter — skip for template VMs (no network during template creation) */
|
||||
net_section[0] = L'\0';
|
||||
if (endpoint_guid && endpoint_guid[0] != L'\0' && !config->is_template) {
|
||||
wchar_t mac_address[18];
|
||||
if (FAILED(hcn_get_endpoint_mac(endpoint_guid, mac_address, 18)))
|
||||
return FALSE;
|
||||
swprintf_s(net_section, 512,
|
||||
L",\"NetworkAdapters\":{\"Default\":{\"EndpointId\":\"%s\"}}",
|
||||
endpoint_guid);
|
||||
L",\"NetworkAdapters\":{\"Default\":{\"EndpointId\":\"%s\",\"MacAddress\":\"%s\"}}",
|
||||
endpoint_guid, mac_address);
|
||||
}
|
||||
|
||||
/* Secure Boot — uses ApplySecureBootTemplate + SecureBootTemplateId
|
||||
|
||||
@@ -99,7 +99,6 @@ typedef struct {
|
||||
volatile ULONGLONG last_heartbeat; /* GetTickCount64() of last agent heartbeat */
|
||||
HANDLE idd_probe_thread; /* Background VDD availability probe */
|
||||
volatile BOOL idd_probe_stop;
|
||||
char nat_ip[32]; /* Allocated NAT IP (e.g. "172.20.0.2"), empty if not NAT */
|
||||
wchar_t admin_user[128]; /* Guest local admin username */
|
||||
BOOL ssh_enabled; /* TRUE = OpenSSH Server installed in guest */
|
||||
DWORD ssh_port; /* Host-side TCP port for SSH tunnel (e.g. 2222) */
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "vm_agent.h"
|
||||
#include "vm_ssh_proxy.h"
|
||||
#include "asb_core.h"
|
||||
#include "hcn_network.h"
|
||||
#include "ui.h"
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -399,15 +398,14 @@ static DWORD WINAPI agent_thread_proc(LPVOID param)
|
||||
ui_log(L"Install complete for \"%s\".", vm->name);
|
||||
}
|
||||
|
||||
/* Send NAT IP to agent (only for NAT mode). Gateway is the chosen
|
||||
subnet's .1; prefix length is always /24 for our NAT. */
|
||||
if (vm->network_mode == NET_NAT && vm->nat_ip[0] != '\0') {
|
||||
char ip_cmd[64];
|
||||
sprintf_s(ip_cmd, sizeof(ip_cmd), "set_ip:%s/24:%s.1",
|
||||
vm->nat_ip, hcn_nat_subnet_base());
|
||||
n = send_tagged_cmd(s, vm, &conn->cmd_seq, ip_cmd, buf, sizeof(buf));
|
||||
if (vm->network_mode == NET_NAT) {
|
||||
DWORD timeout = 65000;
|
||||
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||
n = send_tagged_cmd(s, vm, &conn->cmd_seq, "set_dhcp", buf, sizeof(buf));
|
||||
if (n <= 0) goto disconnected;
|
||||
ui_log(L"NAT IP config for \"%s\": %S", vm->name, buf);
|
||||
timeout = 5000;
|
||||
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||
ui_log(L"NAT DHCP config for \"%s\": %S", vm->name, buf);
|
||||
}
|
||||
|
||||
/* Send GPU share info to agent (if GPU-PV is assigned).
|
||||
|
||||
+23
-96
@@ -9,7 +9,7 @@
|
||||
* when the host sends gpu_query_response with share metadata.
|
||||
*
|
||||
* Supports: ping, shutdown, restart, gpu_copy, gpu_query_response,
|
||||
* gpu_none, idd_connect, set_ip.
|
||||
* gpu_none, idd_connect, set_dhcp.
|
||||
*
|
||||
* Usage:
|
||||
* appsandbox-agent.exe --install Install and start the service
|
||||
@@ -1876,10 +1876,11 @@ static void disable_hyperv_video(AsbConn *notify_sock)
|
||||
/* Forward declaration — defined after SSH proxy section */
|
||||
static void handle_ssh_enable(AsbConn *client, const char *tag);
|
||||
|
||||
static ULONG primary_nic_index(void)
|
||||
static ULONG primary_nic_index(BOOL *dhcp_enabled)
|
||||
{
|
||||
ULONG buf_len = 15000;
|
||||
ULONG index = 0;
|
||||
*dhcp_enabled = FALSE;
|
||||
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
IP_ADAPTER_ADDRESSES *addrs, *cur;
|
||||
@@ -1895,10 +1896,10 @@ static ULONG primary_nic_index(void)
|
||||
for (cur = addrs; cur; cur = cur->Next) {
|
||||
if (cur->IfType != IF_TYPE_ETHERNET_CSMACD || !cur->IfIndex)
|
||||
continue;
|
||||
if (!index) index = cur->IfIndex;
|
||||
if (cur->OperStatus == IfOperStatusUp) {
|
||||
if (!index || cur->OperStatus == IfOperStatusUp) {
|
||||
index = cur->IfIndex;
|
||||
break;
|
||||
*dhcp_enabled = (cur->Flags & IP_ADAPTER_DHCP_ENABLED) != 0;
|
||||
if (cur->OperStatus == IfOperStatusUp) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2041,100 +2042,26 @@ static void handle_client(AsbConn *client)
|
||||
else if (strcmp(cmd, "ssh_enable") == 0) {
|
||||
handle_ssh_enable(client, tag);
|
||||
}
|
||||
else if (strncmp(cmd, "set_ip:", 7) == 0) {
|
||||
/* set_ip:172.20.0.X/PREFIX:GATEWAY */
|
||||
char ip[32] = {0}, prefix[8] = {0}, gateway[32] = {0};
|
||||
char *slash, *colon2;
|
||||
char *arg = cmd + 7;
|
||||
else if (strcmp(cmd, "set_dhcp") == 0) {
|
||||
wchar_t wcmd[256];
|
||||
wchar_t nic[32] = L"Ethernet";
|
||||
BOOL dhcp_enabled;
|
||||
ULONG nic_index = primary_nic_index(&dhcp_enabled);
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
|
||||
/* Parse IP/prefix:gateway */
|
||||
slash = strchr(arg, '/');
|
||||
colon2 = slash ? strchr(slash, ':') : NULL;
|
||||
if (slash && colon2) {
|
||||
int ip_len = (int)(slash - arg);
|
||||
int pfx_len = (int)(colon2 - slash - 1);
|
||||
if (ip_len > 0 && ip_len < (int)sizeof(ip))
|
||||
strncpy_s(ip, sizeof(ip), arg, ip_len);
|
||||
if (pfx_len > 0 && pfx_len < (int)sizeof(prefix))
|
||||
strncpy_s(prefix, sizeof(prefix), slash + 1, pfx_len);
|
||||
strncpy_s(gateway, sizeof(gateway), colon2 + 1, sizeof(gateway) - 1);
|
||||
if (nic_index)
|
||||
swprintf_s(nic, 32, L"%lu", nic_index);
|
||||
if (!dhcp_enabled) {
|
||||
swprintf_s(wcmd, 256,
|
||||
L"netsh interface ipv4 set address name=\"%s\" source=dhcp", nic);
|
||||
result = run_quiet(wcmd);
|
||||
}
|
||||
|
||||
if (ip[0] && prefix[0] && gateway[0]) {
|
||||
wchar_t wcmd[512];
|
||||
wchar_t nic[32] = L"Ethernet";
|
||||
ULONG nic_index = primary_nic_index();
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
DWORD exit_code = 1;
|
||||
|
||||
if (nic_index)
|
||||
swprintf_s(nic, 32, L"%lu", nic_index);
|
||||
|
||||
swprintf_s(wcmd, 512,
|
||||
L"netsh interface ip set address \"%s\" static %S %S %S",
|
||||
nic, ip,
|
||||
/* Convert prefix length to subnet mask */
|
||||
atoi(prefix) == 16 ? "255.255.0.0" :
|
||||
atoi(prefix) == 24 ? "255.255.255.0" :
|
||||
atoi(prefix) == 8 ? "255.0.0.0" : "255.255.255.0",
|
||||
gateway);
|
||||
|
||||
agent_log("Setting IP: %S", wcmd);
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
if (CreateProcessW(NULL, wcmd, NULL, NULL, FALSE,
|
||||
CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
|
||||
WaitForSingleObject(pi.hProcess, 10000);
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
/* Set DNS to gateway (host NAT) + 8.8.8.8 fallback */
|
||||
if (exit_code == 0) {
|
||||
wchar_t dns_cmd[512];
|
||||
STARTUPINFOW si2;
|
||||
PROCESS_INFORMATION pi2;
|
||||
|
||||
swprintf_s(dns_cmd, 512,
|
||||
L"netsh interface ip set dns \"%s\" static %S", nic, gateway);
|
||||
ZeroMemory(&si2, sizeof(si2));
|
||||
si2.cb = sizeof(si2);
|
||||
ZeroMemory(&pi2, sizeof(pi2));
|
||||
if (CreateProcessW(NULL, dns_cmd, NULL, NULL, FALSE,
|
||||
CREATE_NO_WINDOW, NULL, NULL, &si2, &pi2)) {
|
||||
WaitForSingleObject(pi2.hProcess, 10000);
|
||||
CloseHandle(pi2.hProcess);
|
||||
CloseHandle(pi2.hThread);
|
||||
}
|
||||
|
||||
swprintf_s(dns_cmd, 512,
|
||||
L"netsh interface ip add dns \"%s\" 8.8.8.8 index=2", nic);
|
||||
ZeroMemory(&si2, sizeof(si2));
|
||||
si2.cb = sizeof(si2);
|
||||
ZeroMemory(&pi2, sizeof(pi2));
|
||||
if (CreateProcessW(NULL, dns_cmd, NULL, NULL, FALSE,
|
||||
CREATE_NO_WINDOW, NULL, NULL, &si2, &pi2)) {
|
||||
WaitForSingleObject(pi2.hProcess, 10000);
|
||||
CloseHandle(pi2.hProcess);
|
||||
CloseHandle(pi2.hThread);
|
||||
}
|
||||
|
||||
agent_log("IP configured: %s/%s gw %s dns %s,8.8.8.8",
|
||||
ip, prefix, gateway, gateway);
|
||||
REPLY("ok");
|
||||
} else {
|
||||
agent_log("netsh failed (exit %lu)", exit_code);
|
||||
REPLY("error:netsh_failed");
|
||||
}
|
||||
} else {
|
||||
agent_log("set_ip: bad format: %s", cmd);
|
||||
REPLY("error:bad_format");
|
||||
if (result == ERROR_SUCCESS) {
|
||||
swprintf_s(wcmd, 256,
|
||||
L"netsh interface ipv4 set dnsservers name=\"%s\" source=dhcp", nic);
|
||||
result = run_quiet(wcmd);
|
||||
}
|
||||
REPLY(result == ERROR_SUCCESS ? "ok" : "error:dhcp_failed");
|
||||
}
|
||||
else if (strcmp(cmd, "gpu_copy") == 0) {
|
||||
/* Host re-triggered GPU copy — ask for share list */
|
||||
|
||||
@@ -483,121 +483,44 @@ static void *heartbeat_thread(void *arg)
|
||||
|
||||
/* ---- Command handlers ---- */
|
||||
|
||||
/* set_ip:<ip>/<prefix>:<gw>
|
||||
* e.g. set_ip:192.168.42.2/24:192.168.42.1
|
||||
*
|
||||
* Mirrors the Windows agent's `netsh ip set static` clobber semantics:
|
||||
* 1. Disable cloud-init's network module so it stops re-writing
|
||||
* /etc/netplan/50-cloud-init.yaml at next boot.
|
||||
* 2. Remove every other *.yaml in /etc/netplan so the merge has only
|
||||
* our file to consider — no coexistence games.
|
||||
* 3. Write /etc/netplan/99-appsandbox.yaml with the host-assigned
|
||||
* address + gateway + DNS (gateway primary, 8.8.8.8 fallback —
|
||||
* same DNS layout the Windows agent uses).
|
||||
* 4. `netplan apply` then `systemctl restart systemd-networkd` so the
|
||||
* kernel actually drops any stale addresses from a prior config.
|
||||
*
|
||||
* Reply: "ok" on success, "error:..." on failure. */
|
||||
static void handle_set_ip(int fd, const char *tag, const char *args)
|
||||
static void handle_set_dhcp(int fd, const char *tag)
|
||||
{
|
||||
char ip[64], prefix[8], gw[64];
|
||||
const char *slash = strchr(args, '/');
|
||||
const char *colon2 = slash ? strchr(slash, ':') : NULL;
|
||||
size_t ip_len, pfx_len;
|
||||
char cmd[2400];
|
||||
int n, rc;
|
||||
|
||||
if (!slash || !colon2) {
|
||||
send_reply(fd, tag, "error:bad_format");
|
||||
return;
|
||||
}
|
||||
ip_len = (size_t)(slash - args);
|
||||
pfx_len = (size_t)(colon2 - slash - 1);
|
||||
if (ip_len >= sizeof(ip) || pfx_len >= sizeof(prefix)) {
|
||||
send_reply(fd, tag, "error:bad_format");
|
||||
return;
|
||||
}
|
||||
memcpy(ip, args, ip_len); ip[ip_len] = '\0';
|
||||
memcpy(prefix, slash + 1, pfx_len); prefix[pfx_len] = '\0';
|
||||
snprintf(gw, sizeof(gw), "%s", colon2 + 1);
|
||||
|
||||
n = snprintf(cmd, sizeof(cmd),
|
||||
/* Pick the renderer by probing whether the NetworkManager service
|
||||
* is active (Ubuntu Desktop); otherwise default to systemd-networkd
|
||||
* (Server). Writing a netplan with the wrong renderer makes the
|
||||
* active one stop managing the NIC entirely (which is why "no
|
||||
* network in Ubuntu Settings" happened). */
|
||||
const char *cmd =
|
||||
"RENDERER=networkd; "
|
||||
"if systemctl is-active --quiet NetworkManager; then "
|
||||
" RENDERER=NetworkManager; "
|
||||
"fi; "
|
||||
/* Disable cloud-init's network module so it stops re-writing
|
||||
* /etc/netplan/50-cloud-init.yaml on every boot. Idempotent. */
|
||||
"mkdir -p /etc/cloud/cloud.cfg.d && "
|
||||
"mkdir -p /etc/cloud/cloud.cfg.d /etc/netplan || exit 1; "
|
||||
"printf 'network: {config: disabled}\\n' "
|
||||
" > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg && "
|
||||
/* Remove every other netplan dropfile so merge can't reintroduce
|
||||
* a conflicting interface key (e.g. cloud-init's `enp0s5: dhcp4`
|
||||
* + our `appsbnic` both binding the same NIC). */
|
||||
" > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg || exit 1; "
|
||||
"find /etc/netplan -maxdepth 1 -type f -name '*.yaml' "
|
||||
" ! -name '99-appsandbox.yaml' -delete; "
|
||||
/* Our authoritative config. Heredoc is unquoted so $RENDERER
|
||||
* expands; nothing else in the YAML uses '$'. */
|
||||
"umask 077 && cat > /etc/netplan/99-appsandbox.yaml <<EOF\n"
|
||||
" ! -name '99-appsandbox.yaml' -delete || exit 1; "
|
||||
"umask 077; cat > /etc/netplan/99-appsandbox.yaml <<EOF || exit 1\n"
|
||||
"network:\n"
|
||||
" version: 2\n"
|
||||
" renderer: $RENDERER\n"
|
||||
" ethernets:\n"
|
||||
" appsbnic:\n"
|
||||
" match: { name: \"e*\" }\n"
|
||||
" dhcp4: false\n"
|
||||
" dhcp4: true\n"
|
||||
" dhcp6: false\n"
|
||||
" addresses: [\"%s/%s\"]\n"
|
||||
" routes:\n"
|
||||
" - to: default\n"
|
||||
" via: %s\n"
|
||||
" nameservers:\n"
|
||||
" addresses: [%s, 8.8.8.8]\n"
|
||||
"EOF\n"
|
||||
/* Apply, then restart the renderer to drop stale leases / state
|
||||
* left behind by cloud-init or a previous run. netplan apply
|
||||
* alone is sometimes a no-op against a NIC that already has an
|
||||
* address the renderer hasn't released.
|
||||
*
|
||||
* IMPORTANT: reset umask BEFORE `netplan apply`. netplan's
|
||||
* systemd-networkd backend writes the generated
|
||||
* /run/systemd/network/10-netplan-*.network file via Python
|
||||
* open() with no explicit mode, so the file ends up at
|
||||
* (0666 & ~umask). With our 077 umask the generated file is 600
|
||||
* (root:systemd-network), and systemd-networkd — running as
|
||||
* `systemd-network` user, only in the systemd-network group —
|
||||
* gets "Permission denied" trying to read it, falls back to the
|
||||
* dracut catch-all `.network`, and eth0 ends up with no IP.
|
||||
* 022 → generated .network is 644 → systemd-network can read it. */
|
||||
"chmod 600 /etc/netplan/99-appsandbox.yaml && "
|
||||
"umask 022 && "
|
||||
"netplan apply 2>&1; "
|
||||
"chmod 600 /etc/netplan/99-appsandbox.yaml || exit 1; "
|
||||
"umask 022; "
|
||||
"if [ \"$RENDERER\" = NetworkManager ]; then "
|
||||
" systemctl restart NetworkManager 2>&1; "
|
||||
" netplan generate 2>&1 && nmcli connection reload 2>&1 || exit 1; "
|
||||
" if nmcli -t -f NAME connection show --active | grep -Fxq netplan-appsbnic && "
|
||||
" nmcli -g DHCP4.OPTION connection show id netplan-appsbnic | grep -q . && "
|
||||
" nmcli -g IP4.ADDRESS connection show id netplan-appsbnic | grep -q . && "
|
||||
" nmcli -g IP4.GATEWAY connection show id netplan-appsbnic | grep -q .; then "
|
||||
" exit 0; "
|
||||
" fi; "
|
||||
" nmcli --wait 10 connection up id netplan-appsbnic 2>&1; "
|
||||
"else "
|
||||
" systemctl restart systemd-networkd 2>&1; "
|
||||
"fi; "
|
||||
/* Verify the address actually materialised. netplan apply / NM
|
||||
* reload are async — the host might try to SSH before the new
|
||||
* IP claims the wire. Poll for up to 5 seconds. */
|
||||
"for i in 1 2 3 4 5 6 7 8 9 10; do "
|
||||
" ip -4 addr show | grep -q '%s/' && exit 0; "
|
||||
" sleep 0.5; "
|
||||
"done; "
|
||||
"echo 'set_ip: address never appeared'; ip -4 addr show; exit 1",
|
||||
ip, prefix, gw, gw, ip);
|
||||
if (n < 0 || n >= (int)sizeof(cmd)) {
|
||||
send_reply(fd, tag, "error:cmd_too_long");
|
||||
return;
|
||||
}
|
||||
rc = run_sync(cmd);
|
||||
agent_log("set_ip: %s/%s via %s → rc=%d", ip, prefix, gw, rc);
|
||||
send_reply(fd, tag, rc == 0 ? "ok" : "error:netplan_failed");
|
||||
" netplan apply 2>&1 && systemctl restart systemd-networkd 2>&1; "
|
||||
"fi";
|
||||
int rc = run_sync(cmd);
|
||||
send_reply(fd, tag, rc == 0 ? "ok" : "error:dhcp_failed");
|
||||
}
|
||||
|
||||
/* ---- SSH proxy: AF_VSOCK :7 ↔ localhost:22 ----
|
||||
@@ -1109,8 +1032,8 @@ static void handle_client(int fd)
|
||||
agent_log("restart requested");
|
||||
spawn_detached("sleep 1 && systemctl reboot");
|
||||
}
|
||||
else if (strncmp(cmd, "set_ip:", 7) == 0) {
|
||||
handle_set_ip(fd, tag, cmd + 7);
|
||||
else if (strcmp(cmd, "set_dhcp") == 0) {
|
||||
handle_set_dhcp(fd, tag);
|
||||
}
|
||||
else if (strcmp(cmd, "ssh_enable") == 0) {
|
||||
handle_ssh_enable(fd, tag);
|
||||
|
||||
Reference in new issue
Block a user