diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 897d5fdf..3f737a8f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,6 +31,13 @@ android:theme="@style/Theme.NetBird" tools:targetApi="31"> + + + + + + + Allow only listed apps (everything else bypasses) + Disallow listed apps (everything else routes) + + + + allow + disallow + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fb4474b..fead681a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -148,4 +148,58 @@ Switched to profile \'%s\' Logged out from profile \'%s\' Profile \'%s\' removed successfully + + + Management URL + URL of the NetBird management server. Format https://host[:port]. + + Pre-shared key + WireGuard pre-shared key used as an additional symmetric secret. Secret value. + + Disable auto-connect + When enabled, the tunnel does not auto-connect at app start. + + Disable client routes + When enabled, this client does not consume routes advertised by routing peers. + + Disable server routes + When enabled, this client does not act as a routing peer for other clients. + + Block inbound + When enabled, the client blocks all inbound peer traffic on the WireGuard interface. + + Allow server SSH + When enabled, this client accepts incoming SSH sessions via NetBird SSH. + + Enable Rosenpass + Enables Rosenpass post-quantum key exchange on WireGuard tunnels. + + Rosenpass permissive + When enabled, falls back to plain WireGuard if a peer does not support Rosenpass. + + WireGuard port + UDP port for the local WireGuard interface. Allowed range 1-65535. + + Split tunnel mode + Choose allow (only listed apps route through NetBird) or disallow (listed apps bypass NetBird). + + Split tunnel apps + Comma-separated list of package names used by the selected split-tunnel mode. + + Disable update settings + When enabled, blocks every configuration change from the UI and CLI. + + Disable profiles + When enabled, the client cannot list, create, switch or remove NetBird connection profiles. + + Disable networks + When enabled, the client UI cannot list, select or deselect NetBird networks. + + Disable advanced view + When enabled, the new UI hides the advanced-view section. + + Disable metrics collection + When enabled, the client does not collect or report local usage metrics. diff --git a/app/src/main/res/xml/app_restrictions.xml b/app/src/main/res/xml/app_restrictions.xml new file mode 100644 index 00000000..08462ff6 --- /dev/null +++ b/app/src/main/res/xml/app_restrictions.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/netbird b/netbird index 07e54501..b2c57328 160000 --- a/netbird +++ b/netbird @@ -1 +1 @@ -Subproject commit 07e5450117dd0451aaeefc18729a822115587e69 +Subproject commit b2c5732847ae5c22ab8f526204d9f121df72869e diff --git a/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java b/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java index ecebc474..28c5fe03 100644 --- a/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java +++ b/tool/src/main/java/io/netbird/client/tool/EngineRestarter.java @@ -308,6 +308,22 @@ public void onNetworkTypeChanged() { } } + /** + * Triggers the same stop + restart sequence used by the network-change + * path, but without the debounce delay. Used by the MDM policy-change + * broadcast receiver: when an admin pushes a new managed config the + * engine must restart immediately so the new values take effect on the + * next Run (which re-reads MDM via MDMPolicyFetcher). + */ + public void requestRestartNow() { + Log.d(LOGTAG, "explicit restart requested (no debounce)"); + synchronized (restartLock) { + restartScheduled = true; + handler.removeCallbacks(restartRunnable); + handler.post(restartRunnable); + } + } + /** * Cancels any pending debounced restart. Called whenever an external * actor (typically a user-driven Connect/Disconnect) takes over the diff --git a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java index d4069b51..da309ffb 100644 --- a/tool/src/main/java/io/netbird/client/tool/EngineRunner.java +++ b/tool/src/main/java/io/netbird/client/tool/EngineRunner.java @@ -48,6 +48,12 @@ public EngineRunner(Context context, NetworkChangeListener networkChangeListener iFaceDiscover, networkChangeListener); + // Per-Client MDM policy fetcher (DI on the goClient side). + // The Go layer holds the *mdm.Loader on this Client instance; + // every Run/RunWithoutLogin call overlays the latest MDM + // policy on top of the freshly resolved Config. + goClient.setMDMPolicyFetcher(new MDMPolicyFetcher(context)); + updateLogLevel(isTraceLogEnabled, isDebuggable); } diff --git a/tool/src/main/java/io/netbird/client/tool/MDMPolicyFetcher.java b/tool/src/main/java/io/netbird/client/tool/MDMPolicyFetcher.java new file mode 100644 index 00000000..09fc8536 --- /dev/null +++ b/tool/src/main/java/io/netbird/client/tool/MDMPolicyFetcher.java @@ -0,0 +1,72 @@ +package io.netbird.client.tool; + +import android.content.Context; +import android.content.RestrictionsManager; +import android.os.Bundle; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import io.netbird.gomobile.android.PolicyFetcher; + +/** + * MDMPolicyFetcher reads the current Android managed-config snapshot + * from RestrictionsManager and returns it as a JSON-encoded string to + * the Go layer. Registered on the goClient via setMDMPolicyFetcher + * inside EngineRunner; the Go side invokes fetchJSON() on every + * Loader.Load call so the response is always fresh. + * + * Returns an empty string when no managed config is set — the daemon + * side treats that as the "no MDM source present" sentinel. + * + * Lives in the tool package so the network-extension target can + * instantiate it without depending on the app package. + */ +public class MDMPolicyFetcher implements PolicyFetcher { + private static final String TAG = "MDMPolicyFetcher"; + + private final Context context; + + public MDMPolicyFetcher(Context context) { + this.context = context.getApplicationContext(); + } + + @Override + public String fetchJSON() { + RestrictionsManager rm = (RestrictionsManager) context.getSystemService(Context.RESTRICTIONS_SERVICE); + if (rm == null) { + return ""; + } + Bundle restrictions = rm.getApplicationRestrictions(); + if (restrictions == null || restrictions.isEmpty()) { + return ""; + } + try { + return bundleToJSON(restrictions).toString(); + } catch (JSONException e) { + Log.w(TAG, "Failed to serialize managed restrictions to JSON: " + e); + return ""; + } + } + + private static JSONObject bundleToJSON(Bundle bundle) throws JSONException { + JSONObject obj = new JSONObject(); + for (String key : bundle.keySet()) { + Object value = bundle.get(key); + if (value instanceof Bundle) { + obj.put(key, bundleToJSON((Bundle) value)); + } else if (value instanceof Object[]) { + JSONArray arr = new JSONArray(); + for (Object item : (Object[]) value) { + arr.put(item); + } + obj.put(key, arr); + } else { + obj.put(key, value); + } + } + return obj; + } +} diff --git a/tool/src/main/java/io/netbird/client/tool/VPNService.java b/tool/src/main/java/io/netbird/client/tool/VPNService.java index 83ab6124..f938fcae 100644 --- a/tool/src/main/java/io/netbird/client/tool/VPNService.java +++ b/tool/src/main/java/io/netbird/client/tool/VPNService.java @@ -39,6 +39,7 @@ public class VPNService extends android.net.VpnService { private ConcreteNetworkAvailabilityListener networkAvailabilityListener; private EngineRestarter engineRestarter; private android.content.BroadcastReceiver stopEngineReceiver; + private android.content.BroadcastReceiver mdmPolicyChangedReceiver; @Override public void onCreate() { @@ -99,6 +100,37 @@ public void onReceive(Context context, Intent intent) { filter, Context.RECEIVER_NOT_EXPORTED ); + + // Listen for MDM managed-config changes. The OS broadcasts this + // intent whenever a Device Owner / Profile Owner pushes new + // application restrictions; the receiver only signals the engine + // to restart with a freshly resolved Config — the actual values + // are read on demand by MDMPolicyFetcher.fetchJSON during the + // next Run. + mdmPolicyChangedReceiver = new android.content.BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED.equals(intent.getAction())) { + Log.d(LOGTAG, "Received MDM policy change broadcast"); + // Route through EngineRestarter so the existing restart + // machinery (state-listener gating, UI suppression + // window, runWithoutAuth on stop) handles the stop + + // re-run cleanly. A bare engineRunner.stop() leaves the + // engine stopped with no one to relaunch it. + if (engineRestarter != null) { + engineRestarter.requestRestartNow(); + } + } + } + }; + android.content.IntentFilter mdmFilter = new android.content.IntentFilter( + Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + androidx.core.content.ContextCompat.registerReceiver( + this, + mdmPolicyChangedReceiver, + mdmFilter, + Context.RECEIVER_NOT_EXPORTED + ); } @Override @@ -147,6 +179,13 @@ public void onDestroy() { Log.w(LOGTAG, "Receiver not registered", e); } } + if (mdmPolicyChangedReceiver != null) { + try { + unregisterReceiver(mdmPolicyChangedReceiver); + } catch (IllegalArgumentException e) { + Log.w(LOGTAG, "MDM receiver not registered", e); + } + } networkAvailabilityListener.unsubscribe(); networkChangeDetector.unsubscribe();