Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1096,9 +1096,8 @@ public void onComplete(String destDir) {
}
}

// Freshly fast-installed rootfs ships without resolv.conf; give it
// DNS before the companion-data (maps/kiwix) steps that need network.
ensureRuntimeNetworkConfig(debianRootfs);
// DNS is written at the single chokepoint (PRootEngine.executeInContainer),
// so the companion-data proot steps below get a working resolv.conf for free.

if (chkCompanionData.isChecked()) {
editLocalVarsForMaps(debianRootfs, safeTier);
Expand Down Expand Up @@ -1307,16 +1306,8 @@ public void onComplete(String downloadPath) {
// 4. BOOTSTRAP IIAB
mainAct.runOnUiThread(() -> btnAdvancedReset.setText(getString(R.string.install_status_bootstrapping)));

File resolvConf = new File(debianRootfs, "etc/resolv.conf");
if (resolvConf.exists()) resolvConf.delete();
java.io.FileOutputStream fos = new java.io.FileOutputStream(resolvConf);
fos.write("nameserver 1.1.1.1\nnameserver 8.8.8.8\n".getBytes());
fos.close();

File hostsFile = new File(debianRootfs, "etc/hosts");
java.io.FileOutputStream fosH = new java.io.FileOutputStream(hostsFile);
fosH.write("127.0.0.1 localhost\n".getBytes());
fosH.close();
// DNS is written at the chokepoint (PRootEngine) before the
// bootstrap proot run below; no inline write needed here.

if (prootEngine == null)
prootEngine = new PRootEngine();
Expand Down Expand Up @@ -2147,8 +2138,6 @@ private void refreshRestoreButtonLogic() {
tarExtractor.startExtraction(requireContext(), backupFile.getAbsolutePath(), iiabRootDir.getAbsolutePath(), new TarExtractor.ExtractionListener() {
@Override
public void onComplete(String destDir) {
// Restored artifact ships without resolv.conf; the app owns runtime DNS.
ensureRuntimeNetworkConfig(new File(iiabRootDir, "installed-rootfs/iiab"));
mainAct.runOnUiThread(() -> {
isRestoring = false;
disableSystemProtection();
Expand All @@ -2175,33 +2164,6 @@ public void onError(String error) {
}
}

// Runtime network config for the proot guest: proot has no resolver daemon, and our build
// artifact ships WITHOUT /etc/resolv.conf on purpose (the app is the single owner of runtime
// network state). Mirrors the reset path. Called from fast-install and restore.
// TODO [tech-debt]: imperative side-effect inside DeployFragment (god-object); not Clean
// Architecture. Fold into the rootfs domain/data slice during the strangler refactor.
private void ensureRuntimeNetworkConfig(File debianRootfs) {
try {
File etc = new File(debianRootfs, "etc");
if (!etc.isDirectory()) {
Log.w(TAG, "ensureRuntimeNetworkConfig: missing " + etc.getAbsolutePath() + " - skipping DNS write");
return;
}
File resolvConf = new File(etc, "resolv.conf");
if (resolvConf.exists()) resolvConf.delete();
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(resolvConf)) {
fos.write("nameserver 1.1.1.1\nnameserver 8.8.8.8\n".getBytes());
}
File hostsFile = new File(etc, "hosts");
try (java.io.FileOutputStream fosH = new java.io.FileOutputStream(hostsFile)) {
fosH.write("127.0.0.1 localhost\n".getBytes());
}
Log.i(TAG, "ensureRuntimeNetworkConfig: wrote resolv.conf + hosts under " + etc.getAbsolutePath());
} catch (Exception e) {
Log.w(TAG, "ensureRuntimeNetworkConfig failed", e);
}
}

private void importBackupSafely(Uri sourceUri) {
isImporting = true;
updateDynamicButtons();
Expand Down
17 changes: 17 additions & 0 deletions controller/app/src/main/java/org/iiab/controller/PRootEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
import java.util.ArrayList;
import java.util.List;

import org.iiab.controller.network.data.FileResolvConfWriter;
import org.iiab.controller.network.data.PrefsDnsConfigRepository;
import org.iiab.controller.network.domain.ApplyDnsUseCase;

public class PRootEngine {
private static final String TAG = "IIAB-PRootEngine";
private Process currentProcess;
Expand All @@ -44,6 +48,19 @@ public void executeInContainer(Context context, String rootfsDir, String command
throw new Exception("libproot.so not found in native library directory!");
}

// Single DNS injection point: every proot launch passes through here, so we write
// the effective DNS (user's custom config when enabled, else defaults) into the
// guest's resolv.conf BEFORE launching. Replaces the scattered writes that used to
// live in DeployFragment / MainActivity.
try {
new ApplyDnsUseCase(
new PrefsDnsConfigRepository(context),
new FileResolvConfWriter()
).execute(new File(rootfsDir));
} catch (Exception dnsEx) {
Log.w(TAG, "DNS apply skipped: " + dnsEx.getMessage());
}

// =========================================================
// THE W^X TROJAN HORSE: FAKE TERMUX PREFIX
// =========================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* ============================================================================
* Name : FileResolvConfWriter.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : Writes resolv.conf (+ minimal hosts) into a rootfs; migrates the #25 helper logic.
* ============================================================================
*/
package org.iiab.controller.network.data;

import android.util.Log;

import org.iiab.controller.network.domain.DnsConfig;
import org.iiab.controller.network.domain.ResolvConfWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;

/**
* {@link ResolvConfWriter} that writes {@code etc/resolv.conf} (and a minimal
* {@code etc/hosts} if absent) into a guest rootfs. This is the single home for the
* DNS-write logic previously duplicated in {@code DeployFragment}/{@code MainActivity}.
* Guards on {@code etc/} existence and never throws.
*/
public final class FileResolvConfWriter implements ResolvConfWriter {

private static final String TAG = "IIAB-DNS";

@Override
public boolean write(DnsConfig config, File rootfsDir) {
try {
File etc = new File(rootfsDir, "etc");
if (!etc.isDirectory()) {
Log.w(TAG, "resolv.conf skipped: missing " + etc.getAbsolutePath());
return false;
}
File resolv = new File(etc, "resolv.conf");
if (resolv.exists() && !resolv.delete()) {
Log.w(TAG, "could not remove old resolv.conf at " + resolv.getAbsolutePath());
}
try (FileOutputStream fos = new FileOutputStream(resolv)) {
fos.write(config.toResolvConf().getBytes(StandardCharsets.UTF_8));
}
File hosts = new File(etc, "hosts");
if (!hosts.exists()) {
try (FileOutputStream fos = new FileOutputStream(hosts)) {
fos.write("127.0.0.1 localhost\n".getBytes(StandardCharsets.UTF_8));
}
}
Log.i(TAG, "wrote resolv.conf (" + config + ") under " + etc.getAbsolutePath());
return true;
} catch (Exception e) {
Log.w(TAG, "FileResolvConfWriter.write failed", e);
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* ============================================================================
* Name : PrefsDnsConfigRepository.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : SharedPreferences-backed DnsConfigRepository (new keys; not the vestigial VPN ones).
* ============================================================================
*/
package org.iiab.controller.network.data;

import android.content.Context;
import android.content.SharedPreferences;

import org.iiab.controller.network.domain.DnsConfig;
import org.iiab.controller.network.domain.DnsConfigRepository;

/**
* {@link DnsConfigRepository} backed by a dedicated SharedPreferences file. Uses
* fresh keys (NOT the vestigial VPN-era {@code DnsIpv4}/{@code DnsIpv6} in
* {@code Preferences}, which this feature will retire). {@code MODE_MULTI_PROCESS}
* matches the rest of the app.
*/
public final class PrefsDnsConfigRepository implements DnsConfigRepository {

private static final String PREFS = "NetworkDnsPrefs";
private static final String K_CUSTOM = "dns.custom.enabled";
private static final String K_PRIMARY = "dns.primary";
private static final String K_SECONDARY = "dns.secondary";

private final SharedPreferences prefs;

public PrefsDnsConfigRepository(Context context) {
this.prefs = context.getSharedPreferences(PREFS, Context.MODE_MULTI_PROCESS);
}

@Override
public DnsConfig loadEffective() {
return isCustomEnabled() ? loadCustom() : DnsConfig.defaults();
}

@Override
public DnsConfig loadCustom() {
DnsConfig d = DnsConfig.defaults();
String primary = prefs.getString(K_PRIMARY, d.primary());
String secondary = prefs.getString(K_SECONDARY, d.secondary());
return new DnsConfig(primary, secondary);
}

@Override
public boolean isCustomEnabled() {
return prefs.getBoolean(K_CUSTOM, false);
}

@Override
public void saveCustom(DnsConfig config) {
prefs.edit()
.putBoolean(K_CUSTOM, true)
.putString(K_PRIMARY, config.primary())
.putString(K_SECONDARY, config.secondary())
.commit();
}

@Override
public void disableCustom() {
prefs.edit().putBoolean(K_CUSTOM, false).commit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* ============================================================================
* Name : ApplyDnsUseCase.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : Use case: write the effective DNS config into a rootfs (boot-time apply).
* ============================================================================
*/
package org.iiab.controller.network.domain;

import java.io.File;

/**
* Writes the effective DNS config (custom or defaults) into a guest rootfs. Called
* at server boot (and wherever a rootfs is about to run network operations) so the
* proot guest always has a working {@code resolv.conf} without a resolver daemon.
*/
public final class ApplyDnsUseCase {

private final DnsConfigRepository repository;
private final ResolvConfWriter writer;

public ApplyDnsUseCase(DnsConfigRepository repository, ResolvConfWriter writer) {
this.repository = repository;
this.writer = writer;
}

/** @return {@code true} if resolv.conf was written into {@code rootfsDir}. */
public boolean execute(File rootfsDir) {
return writer.write(repository.loadEffective(), rootfsDir);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* ============================================================================
* Name : DnsConfig.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : DNS configuration: two mixed slots (primary/secondary), each one IPv4 or IPv6.
* ============================================================================
*/
package org.iiab.controller.network.domain;

/**
* Immutable DNS configuration: a {@code primary} and an optional {@code secondary}
* nameserver. Each slot holds a SINGLE address that may be IPv4 <em>or</em> IPv6
* (mixed allowed in any order); no comma-separated lists. This keeps the model and
* validation simple while covering every combination the user might want.
*
* <p>Pure domain value object (no Android, no I/O). {@link #defaults()} is the
* preconfigured set the app ships with, so the user never has to configure anything.
*/
public final class DnsConfig {

private final String primary;
private final String secondary;

public DnsConfig(String primary, String secondary) {
this.primary = norm(primary);
this.secondary = norm(secondary);
}

/** Preconfigured defaults: Cloudflare (primary) + Google (secondary), both IPv4. */
public static DnsConfig defaults() {
return new DnsConfig("1.1.1.1", "8.8.8.8");
}

/** The primary nameserver (IPv4 or IPv6); "" if unset. */
public String primary() { return primary; }

/** The secondary nameserver (IPv4 or IPv6); "" if unset. */
public String secondary() { return secondary; }

public boolean hasSecondary() { return !secondary.isEmpty(); }

public boolean isEmpty() { return primary.isEmpty() && secondary.isEmpty(); }

/** Body for {@code /etc/resolv.conf}: one {@code nameserver X} line per non-empty slot. */
public String toResolvConf() {
StringBuilder sb = new StringBuilder();
if (!primary.isEmpty()) sb.append("nameserver ").append(primary).append("\n");
if (!secondary.isEmpty()) sb.append("nameserver ").append(secondary).append("\n");
return sb.toString();
}

private static String norm(String s) { return s == null ? "" : s.trim(); }

@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DnsConfig)) return false;
DnsConfig d = (DnsConfig) o;
return primary.equals(d.primary) && secondary.equals(d.secondary);
}

@Override public int hashCode() { return 31 * primary.hashCode() + secondary.hashCode(); }

@Override public String toString() { return "DnsConfig{primary=" + primary + ", secondary=" + secondary + "}"; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* ============================================================================
* Name : DnsConfigRepository.java
* Author : AppDevForAll
* Copyright : Copyright (c) 2026 AppDevForAll
* Description : Domain port: persistence of the user's DNS choice.
* ============================================================================
*/
package org.iiab.controller.network.domain;

/**
* Domain port for persisting the user's DNS choice. The Data layer backs this with
* SharedPreferences. Semantics support the "Setup DNS" checkbox:
* <ul>
* <li>custom disabled (default) &rarr; effective config is {@link DnsConfig#defaults()};</li>
* <li>custom enabled &rarr; effective config is the saved custom config.</li>
* </ul>
*/
public interface DnsConfigRepository {

/** Config to actually apply: saved custom when enabled, otherwise {@link DnsConfig#defaults()}. */
DnsConfig loadEffective();

/** The saved custom config (or {@link DnsConfig#defaults()} if none saved yet). */
DnsConfig loadCustom();

/** Whether the user enabled custom DNS (the "Setup DNS" check). */
boolean isCustomEnabled();

/** Persist {@code config} as the custom config and mark custom DNS enabled. */
void saveCustom(DnsConfig config);

/** Turn custom DNS off (revert to defaults). Used on test failure / unchecking. */
void disableCustom();
}
Loading
Loading