Skip to content
Open
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
@@ -1,6 +1,7 @@
package cn.classfun.droidvm.daemon.network.backend;

import static cn.classfun.droidvm.lib.Constants.DATA_DIR;
import static cn.classfun.droidvm.lib.utils.AssetUtils.getPrebuiltBinaryPath;
import static cn.classfun.droidvm.lib.utils.StringUtils.fmt;
import static cn.classfun.droidvm.lib.utils.StringUtils.pathJoin;

Expand All @@ -13,9 +14,12 @@
import java.io.File;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import cn.classfun.droidvm.daemon.network.NetworkInstance;
import cn.classfun.droidvm.lib.network.IPv4Address;
import cn.classfun.droidvm.lib.network.IPv4Network;
import cn.classfun.droidvm.lib.store.network.NetworkState;
import cn.classfun.droidvm.lib.utils.ProcessUtils;

Expand All @@ -31,27 +35,50 @@ public Dnsmasq(NetworkInstance inst) {
this.inst = inst;
}

@NonNull
private static String resolveDnsmasqBinary() {
var prebuilt = getPrebuiltBinaryPath("dnsmasq");
var file = new File(prebuilt);
if (file.isFile() && file.canExecute())
return prebuilt;
return "dnsmasq";
}

private void startDnsmasqProcess(
) {
var bridge = inst.item.optString("bridge_name", "");
var rangeStart = inst.item.optString("dhcp_range_start", "");
var rangeEnd = inst.item.optString("dhcp_range_end", "");
Log.i(TAG, fmt("Starting dnsmasq on %s (%s - %s)", bridge, rangeStart, rangeEnd));
var router = findRouterAddress(rangeStart);
var dnsmasq = resolveDnsmasqBinary();
Log.i(TAG, fmt("Starting dnsmasq on %s (%s - %s) using %s",
bridge, rangeStart, rangeEnd, dnsmasq));
var pidFile = getDnsmasqPidFile();
var leaseFile = getDnsmasqLeaseFile();
try {
var pb = new ProcessBuilder(
"dnsmasq",
fmt("--interface=%s", bridge),
"--bind-interfaces",
fmt("--dhcp-range=%s,%s,12h", rangeStart, rangeEnd),
"--no-daemon",
"--keep-in-foreground",
fmt("--pid-file=%s", pidFile),
fmt("--dhcp-leasefile=%s", leaseFile),
"--log-queries",
"--log-dhcp"
);
var args = new ArrayList<String>();
args.add(dnsmasq);
args.add(fmt("--interface=%s", bridge));
args.add("--bind-interfaces");
args.add(fmt("--dhcp-range=%s,%s,12h", rangeStart, rangeEnd));
if (router != null)
args.add(fmt("--dhcp-option=option:router,%s", router));
var dnsServers = new ArrayList<String>();
for (var iter : inst.item.get("dns_servers")) {
var s = iter.getValue().asString();
if (s != null && !s.isEmpty()) dnsServers.add(s);
}
if (!dnsServers.isEmpty())
args.add(fmt("--dhcp-option=option:dns-server,%s", String.join(",", dnsServers)));
args.add("--port=0");
args.add("--no-resolv");
args.add("--no-daemon");
args.add("--keep-in-foreground");
args.add(fmt("--pid-file=%s", pidFile));
args.add(fmt("--dhcp-leasefile=%s", leaseFile));
args.add("--log-queries");
Comment on lines +74 to +79
args.add("--log-dhcp");
var pb = new ProcessBuilder(args);
pb.redirectErrorStream(true);
dnsmasqExitCode = 0;
dnsmasqProcess = pb.start();
Expand All @@ -61,6 +88,27 @@ private void startDnsmasqProcess(
}
}

@Nullable
private String findRouterAddress(@NonNull String rangeStart) {
IPv4Address dhcpStart = null;
try {
if (!rangeStart.isEmpty()) dhcpStart = IPv4Address.parse(rangeStart);
} catch (Exception ignored) {
}

IPv4Network fallback = null;
for (var iter : inst.item.get("ipv4_addresses")) {
try {
var cidr = IPv4Network.parse(iter.getValue().asString());
if (fallback == null) fallback = cidr;
if (dhcpStart != null && cidr.contains(dhcpStart))
return cidr.address().toString();
} catch (Exception ignored) {
}
}
return fallback != null ? fallback.address().toString() : null;
Comment on lines +99 to +109
Comment on lines +99 to +109
}

private void stopDnsmasqProcess(@Nullable Process process) {
var bridge = inst.item.optString("bridge_name", "");
Log.i(TAG, fmt("Stopping dnsmasq on %s", bridge));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.util.UUID;

import cn.classfun.droidvm.R;
import cn.classfun.droidvm.lib.network.IPNetwork;
import cn.classfun.droidvm.lib.network.IPv4Address;
import cn.classfun.droidvm.lib.network.IPv4Network;
import cn.classfun.droidvm.lib.network.IPv6Network;
Expand All @@ -47,14 +46,15 @@ public final class NetworkEditActivity extends AppCompatActivity {
public static final String EXTRA_NETWORK_ID = "network_id";
private final List<IPv4Network> ipv4Addresses = new ArrayList<>();
private final List<IPv6Network> ipv6Addresses = new ArrayList<>();
private final List<IPv4Address> dnsServers = new ArrayList<>();
private boolean editMode = false;
private UUID editNetworkId = null;
private CollapsingToolbarLayout collapsingToolbar;
private TextInputRowWidget inputName, inputBridgeName, inputMac;
private SwitchRowWidget swAutoUp, swStp, swNat, swDhcp;
private TextInputRowWidget inputIPv4, inputIPv6;
private LinearLayout layoutIPv4, layoutIPv6, groupDhcp;
private TextView tvIPv4Empty, tvIPv6Empty;
private TextInputRowWidget inputIPv4, inputIPv6, inputDns;
private LinearLayout layoutIPv4, layoutIPv6, layoutDns, groupDhcp;
private TextView tvIPv4Empty, tvIPv6Empty, tvDnsEmpty;
private TextInputRowWidget inputDhcpStart, inputDhcpEnd;
private FloatingActionButton fab;
private NetworkStore store;
Expand Down Expand Up @@ -87,10 +87,13 @@ protected void onCreate(Bundle savedInstanceState) {
swDhcp = findViewById(R.id.sw_dhcp);
inputIPv4 = findViewById(R.id.input_ipv4);
inputIPv6 = findViewById(R.id.input_ipv6);
inputDns = findViewById(R.id.input_dns);
layoutIPv4 = findViewById(R.id.layout_ipv4);
layoutIPv6 = findViewById(R.id.layout_ipv6);
layoutDns = findViewById(R.id.layout_dns);
tvIPv4Empty = findViewById(R.id.tv_ipv4_empty);
tvIPv6Empty = findViewById(R.id.tv_ipv6_empty);
tvDnsEmpty = findViewById(R.id.tv_dns_empty);
groupDhcp = findViewById(R.id.group_dhcp);
inputDhcpStart = findViewById(R.id.input_dhcp_start);
inputDhcpEnd = findViewById(R.id.input_dhcp_end);
Expand All @@ -104,6 +107,7 @@ private void initialize() {
store.load(this);
inputIPv4.setEndIconOnClickListener(v -> onAddIPv4());
inputIPv6.setEndIconOnClickListener(v -> onAddIPv6());
inputDns.setEndIconOnClickListener(v -> onAddDns());
inputIPv4.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Expand All @@ -116,6 +120,12 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
inputIPv6.setError(null);
}
});
inputDns.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
inputDns.setError(null);
}
});
swDhcp.setOnCheckedChangeListener((btn, checked) ->
groupDhcp.setVisibility(checked ? VISIBLE : GONE));
fab.setOnClickListener(v -> onSaveClicked());
Expand All @@ -130,8 +140,9 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
collapsingToolbar.setTitle(getString(R.string.network_create_title));
generateDefaults();
}
buildAddressList(layoutIPv4, tvIPv4Empty, ipv4Addresses, true);
buildAddressList(layoutIPv6, tvIPv6Empty, ipv6Addresses, false);
buildAddressList(layoutIPv4, tvIPv4Empty, ipv4Addresses, this::updateDhcpFromIPv4);
buildAddressList(layoutIPv6, tvIPv6Empty, ipv6Addresses, null);
buildAddressList(layoutDns, tvDnsEmpty, dnsServers, null);
}

private void installManualEditWatchers() {
Expand Down Expand Up @@ -177,10 +188,18 @@ private void generateDefaults() {
setTextProgrammatic(inputBridgeName, bridgeNameFromMac(mac));
var ipv4Cidr = generateRandomIPv4();
ipv4Addresses.add(ipv4Cidr);
addDefaultDnsServers();
swDhcp.setChecked(true);
updateDhcpFromIPv4();
}

private void addDefaultDnsServers() {
var a = IPv4Address.parse("8.8.8.8");
var b = IPv4Address.parse("1.1.1.1");
if (a != null) dnsServers.add(a);
if (b != null) dnsServers.add(b);
}

@NonNull
private String bridgeNameFromMac(@NonNull String mac) {
return fmt("br%s", mac.replace(":", "").toLowerCase());
Expand Down Expand Up @@ -268,6 +287,9 @@ private void loadExistingConfig() {
parseIPv4Addresses(config, ipv4Addresses);
ipv6Addresses.clear();
parseIPv6Addresses(config, ipv6Addresses);
dnsServers.clear();
parseDnsServers(config, dnsServers);
if (dnsServers.isEmpty()) addDefaultDnsServers();
macManuallyEdited = true;
bridgeManuallyEdited = true;
ipv4ManuallyEdited = true;
Expand All @@ -293,7 +315,7 @@ private void onAddIPv4() {
ipv4Addresses.add(ip);
ipv4ManuallyEdited = true;
inputIPv4.setText("");
buildAddressList(layoutIPv4, tvIPv4Empty, ipv4Addresses, true);
buildAddressList(layoutIPv4, tvIPv4Empty, ipv4Addresses, this::updateDhcpFromIPv4);
updateDhcpFromIPv4();
}

Expand All @@ -314,14 +336,38 @@ private void onAddIPv6() {
inputIPv6.setError(null);
ipv6Addresses.add(ip);
inputIPv6.setText("");
buildAddressList(layoutIPv6, tvIPv6Empty, ipv6Addresses, false);
buildAddressList(layoutIPv6, tvIPv6Empty, ipv6Addresses, null);
}

private void onAddDns() {
var addr = inputDns.getText().trim();
if (addr.isEmpty()) return;
if (!IPv4Address.isValid(addr)) {
inputDns.setError(getString(R.string.network_edit_error_dns_invalid));
return;
}
var ip = IPv4Address.parse(addr);
if (ip == null) {
Comment on lines +349 to +350
inputDns.setError(getString(R.string.network_edit_error_dns_invalid));
return;
}
for (var existing : dnsServers) {
if (existing.value() == ip.value()) {
inputDns.setText("");
return;
}
}
inputDns.setError(null);
dnsServers.add(ip);
inputDns.setText("");
buildAddressList(layoutDns, tvDnsEmpty, dnsServers, null);
}
Comment on lines +342 to 364

private void buildAddressList(
@NonNull LinearLayout container,
@NonNull TextView emptyView,
@NonNull List<? extends IPNetwork<?, ?, ?>> list,
boolean isIPv4
@NonNull List<?> list,
@Nullable Runnable onRemoved
) {
Comment on lines 366 to 371
container.removeAllViews();
if (list.isEmpty()) {
Expand All @@ -347,8 +393,8 @@ private void buildAddressList(
btn.setContentDescription(getString(R.string.network_edit_remove_address));
btn.setOnClickListener(v -> {
list.remove(idx);
buildAddressList(container, emptyView, list, isIPv4);
if (isIPv4) updateDhcpFromIPv4();
buildAddressList(container, emptyView, list, onRemoved);
if (onRemoved != null) onRemoved.run();
});
row.addView(btn);
container.addView(row);
Expand All @@ -362,6 +408,7 @@ private void onSaveClicked() {
inputMac.setError(null);
inputIPv4.setError(null);
inputIPv6.setError(null);
inputDns.setError(null);
inputDhcpStart.setError(null);
inputDhcpEnd.setError(null);
if (!inputIPv4.getText().trim().isEmpty()) {
Expand All @@ -372,6 +419,14 @@ private void onSaveClicked() {
inputIPv6.setError(getString(R.string.network_edit_error_ipv6_not_added));
return;
}
if (!inputDns.getText().trim().isEmpty()) {
inputDns.setError(getString(R.string.network_edit_error_dns_not_added));
return;
}
if (dnsServers.isEmpty()) {
inputDns.setError(getString(R.string.network_edit_error_dns_empty));
return;
}
var name = inputName.getText().trim();
var bridgeName = inputBridgeName.getText().trim();
var mac = inputMac.getText().trim();
Expand Down Expand Up @@ -445,6 +500,7 @@ private void onSaveClicked() {
config.item.set("dhcp_enabled", swDhcp.isChecked());
setIPv4Addresses(config, ipv4Addresses);
setIPv6Addresses(config, ipv6Addresses);
setDnsServers(config, dnsServers);
config.item.set("dhcp_range_start", inputDhcpStart.getText().trim());
config.item.set("dhcp_range_end", inputDhcpEnd.getText().trim());
if (editMode) {
Expand Down Expand Up @@ -544,6 +600,16 @@ private static void parseIPv6Addresses(@NonNull NetworkConfig cfg, @NonNull List
});
}

private static void parseDnsServers(@NonNull NetworkConfig cfg, @NonNull List<IPv4Address> out) {
cfg.item.get("dns_servers").forEachArray(a -> {
try {
var ip = IPv4Address.parse(a.asString());
if (ip != null) out.add(ip);
} catch (Exception ignored) {
}
});
}

private static void setIPv4Addresses(@NonNull NetworkConfig cfg, @NonNull List<IPv4Network> addresses) {
var arr = DataItem.newArray();
for (var addr : addresses)
Expand All @@ -557,4 +623,11 @@ private static void setIPv6Addresses(@NonNull NetworkConfig cfg, @NonNull List<I
arr.append(DataItem.newString(addr.toString()));
cfg.item.set("ipv6_addresses", arr);
}

private static void setDnsServers(@NonNull NetworkConfig cfg, @NonNull List<IPv4Address> dns) {
var arr = DataItem.newArray();
for (var ip : dns)
arr.append(DataItem.newString(ip.toString()));
cfg.item.set("dns_servers", arr);
}
}
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_dns.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorOnSurface"
android:pathData="M7,9A2,2 0 0,1 5,7A2,2 0 0,1 7,5A2,2 0 0,1 9,7A2,2 0 0,1 7,9M20,3H4A1,1 0 0,0 3,4V10A1,1 0 0,0 4,11H20A1,1 0 0,0 21,10V4A1,1 0 0,0 20,3M7,19A2,2 0 0,1 5,17A2,2 0 0,1 7,15A2,2 0 0,1 9,17A2,2 0 0,1 7,19M20,13H4A1,1 0 0,0 3,14V20A1,1 0 0,0 4,21H20A1,1 0 0,0 21,20V14A1,1 0 0,0 20,13Z" />
</vector>
36 changes: 36 additions & 0 deletions app/src/main/res/layout/activity_network_edit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,42 @@

</cn.classfun.droidvm.ui.widgets.container.CollapsibleContainer>

<cn.classfun.droidvm.ui.widgets.container.CollapsibleContainer
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:title="@string/network_edit_category_dns"
app:cc_collapsible="false">

<cn.classfun.droidvm.ui.widgets.row.TextInputRowWidget
android:id="@+id/input_dns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/network_edit_dns_hint"
android:icon="@drawable/ic_dns"
android:inputType="textNoSuggestions"
app:ti_endIcon="@android:drawable/ic_input_add"
app:ti_endIconMode="custom" />

<LinearLayout
android:id="@+id/layout_dns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="72dp"
android:paddingEnd="16dp" />

<TextView
android:id="@+id/tv_dns_empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0.6"
android:paddingStart="72dp"
android:paddingEnd="16dp"
android:text="@string/network_edit_no_addresses"
android:textSize="14sp" />

</cn.classfun.droidvm.ui.widgets.container.CollapsibleContainer>

<cn.classfun.droidvm.ui.widgets.container.CollapsibleContainer
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
Loading
Loading