Skip to content

Commit f8ea652

Browse files
committed
Main+Boot+droidvnc-nc: Added interface binding
- droidvnc-ng.c: Method Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer modified to accept a futher "jstring listenIf" parameter. This way, it is possible to use rfbScreen->listenInterface property to perform interface binding; - MainActivity: Now it makes use of ListenIfAdapter, allowing to change the used interface (default is loopback); - MainService: Now it makes use of NetworkInterfaceTester and IfCollector to detect the state of the network and stop itself, if needed; - OnBootReceiver: Now it correctly detects whether an interface is disabled/non-existent and if so, it DOESN't start the service, moreover, it sends a notification about the problem.
1 parent e3466bf commit f8ea652

File tree

4 files changed

+249
-25
lines changed

4 files changed

+249
-25
lines changed

app/src/main/cpp/droidvnc-ng.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncS
277277
}
278278

279279

280-
JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer(JNIEnv *env, jobject thiz, jint width, jint height, jint port, jstring desktopname, jstring password, jstring httpRootDir) {
280+
JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncStartServer(JNIEnv *env, jobject thiz, jint width, jint height, jstring listenIf, jint port, jstring desktopname, jstring password, jstring httpRootDir) {
281281

282282
int argc = 0;
283283

@@ -305,6 +305,22 @@ JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncS
305305
theScreen->setXCutTextUTF8 = onCutTextUTF8;
306306
theScreen->newClientHook = onClientConnected;
307307

308+
in_addr_t address = 0; // Default is 0.0.0.0
309+
if (listenIf != NULL) {
310+
const char *cListenInterface = (*env)->GetStringUTFChars(env, listenIf, NULL);
311+
int addrConvSucceded = rfbStringToAddr((char*)cListenInterface, &address);
312+
(*env)->ReleaseStringUTFChars(env, listenIf, cListenInterface);
313+
314+
if (!addrConvSucceded) {
315+
__android_log_print(ANDROID_LOG_ERROR, TAG, "vncStartServer: invalid listen address");
316+
Java_net_christianbeier_droidvnc_1ng_MainService_vncStopServer(env, thiz);
317+
return JNI_FALSE;
318+
}
319+
}
320+
321+
322+
// With the listenInterface property one can define where the server will be available
323+
theScreen->listenInterface = address;
308324
theScreen->port = port;
309325
theScreen->ipv6port = port;
310326

app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import android.util.TypedValue;
6262
import android.view.View;
6363
import android.view.WindowManager;
64+
import android.widget.ArrayAdapter;
6465
import android.widget.Button;
6566
import android.widget.EditText;
6667
import android.widget.ImageButton;
@@ -69,6 +70,9 @@
6970
import android.widget.TableLayout;
7071
import android.widget.TextView;
7172
import android.widget.Toast;
73+
import android.widget.Spinner;
74+
import android.widget.AdapterView.OnItemSelectedListener;
75+
import android.widget.AdapterView;
7276

7377
import androidx.appcompat.app.AlertDialog;
7478
import androidx.appcompat.app.AppCompatActivity;
@@ -83,6 +87,8 @@
8387
import java.util.Objects;
8488
import java.util.UUID;
8589

90+
import net.christianbeier.droidvnc_ng.ifaceutils.NetIfData;
91+
8692
public class MainActivity extends AppCompatActivity {
8793

8894
private static final String TAG = "MainActivity";
@@ -125,6 +131,7 @@ protected void onCreate(Bundle savedInstanceState) {
125131
mButtonToggle.setOnClickListener(view -> {
126132

127133
Intent intent = new Intent(MainActivity.this, MainService.class);
134+
intent.putExtra(MainService.EXTRA_LISTEN_INTERFACE, prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface()));
128135
intent.putExtra(MainService.EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()));
129136
intent.putExtra(MainService.EXTRA_PASSWORD, prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword()));
130137
intent.putExtra(MainService.EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, mDefaults.getFileTransfer()));
@@ -331,6 +338,34 @@ protected void onCreate(Bundle savedInstanceState) {
331338
});
332339

333340

341+
342+
ListenIfAdapter lsif = new ListenIfAdapter(this);
343+
final Spinner listenInterfaceSpin = findViewById(R.id.settings_listening_interface);
344+
listenInterfaceSpin.setAdapter(lsif);
345+
listenInterfaceSpin.setOnItemSelectedListener(new OnItemSelectedListener() {
346+
@Override
347+
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
348+
NetIfData d = (NetIfData)parent.getItemAtPosition(pos);
349+
if(!(prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, null) == null && d.getName().equals(mDefaults.getListenInterface()))) {
350+
SharedPreferences.Editor ed = prefs.edit();
351+
ed.putString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, d.getName());
352+
ed.apply();
353+
}
354+
}
355+
356+
@Override
357+
public void onNothingSelected(AdapterView<?> parent) {
358+
359+
}
360+
});
361+
// Restore last selected interface
362+
listenInterfaceSpin.setSelection(
363+
lsif.getItemPositionByOptionId(
364+
prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface())));
365+
366+
367+
368+
334369
final EditText port = findViewById(R.id.settings_port);
335370
if(prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()) < 0) {
336371
port.setHint(R.string.main_activity_settings_port_not_listening);
@@ -875,7 +910,8 @@ private void updateAddressesDisplay() {
875910
if(MainService.getPort() >= 0) {
876911
HashMap<ClickableSpan, Pair<Integer,Integer>> spans = new HashMap<>();
877912
// uhh there must be a nice functional way for this
878-
ArrayList<String> hosts = MainService.getIPv4s();
913+
ArrayList<String> hosts = MainService.getReachableIPv4s();
914+
879915
StringBuilder sb = new StringBuilder();
880916
sb.append(getString(R.string.main_activity_address)).append(" ");
881917
if(hosts.isEmpty()) {
@@ -940,6 +976,7 @@ private void onServerStarted() {
940976
findViewById(R.id.outbound_buttons).setVisibility(View.VISIBLE);
941977

942978
// indicate that changing these settings does not have an effect when the server is running
979+
findViewById(R.id.settings_listening_interface).setEnabled(false);
943980
findViewById(R.id.settings_port).setEnabled(false);
944981
findViewById(R.id.settings_password).setEnabled(false);
945982
findViewById(R.id.settings_access_key).setEnabled(false);
@@ -969,6 +1006,7 @@ private void onServerStopped() {
9691006
findViewById(R.id.outbound_buttons).setVisibility(View.GONE);
9701007

9711008
// indicate that changing these settings does have an effect when the server is stopped
1009+
findViewById(R.id.settings_listening_interface).setEnabled(true);
9721010
findViewById(R.id.settings_port).setEnabled(true);
9731011
findViewById(R.id.settings_password).setEnabled(true);
9741012
findViewById(R.id.settings_access_key).setEnabled(true);

app/src/main/java/net/christianbeier/droidvnc_ng/MainService.java

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@
7272
import java.util.concurrent.ConcurrentHashMap;
7373
import java.util.concurrent.locks.ReentrantReadWriteLock;
7474

75-
public class MainService extends Service {
75+
import net.christianbeier.droidvnc_ng.ifaceutils.IfCollector;
76+
import net.christianbeier.droidvnc_ng.ifaceutils.NetIfData;
77+
import net.christianbeier.droidvnc_ng.ifaceutils.NetworkInterfaceTester;
78+
79+
public class MainService extends Service implements NetworkInterfaceTester.OnNetworkStateChangedListener {
7680

7781
private static final String TAG = "MainService";
7882
static final int NOTIFICATION_ID = 11;
@@ -82,6 +86,7 @@ public class MainService extends Service {
8286
public static final String ACTION_CONNECT_REPEATER = "net.christianbeier.droidvnc_ng.ACTION_CONNECT_REPEATER";
8387
public static final String EXTRA_REQUEST_ID = "net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID";
8488
public static final String EXTRA_REQUEST_SUCCESS = "net.christianbeier.droidvnc_ng.EXTRA_REQUEST_SUCCESS";
89+
public static final String EXTRA_LISTEN_INTERFACE = "net.christianbeier.droidvnc_ng.EXTRA_LISTEN_INTERFACE";
8590
public static final String EXTRA_HOST = "net.christianbeier.droidvnc_ng.EXTRA_HOST";
8691
public static final String EXTRA_PORT = "net.christianbeier.droidvnc_ng.EXTRA_PORT";
8792
public static final String EXTRA_REPEATER_ID = "net.christianbeier.droidvnc_ng.EXTRA_REPEATER_ID";
@@ -119,9 +124,19 @@ public class MainService extends Service {
119124

120125
final static String ACTION_HANDLE_NOTIFICATION_RESULT = "action_handle_notification_result";
121126

127+
122128
final static String ACTION_HANDLE_MEDIA_PROJECTION_RESULT = "action_handle_media_projection_result";
123129
final static String EXTRA_MEDIA_PROJECTION_STATE = "state_media_projection";
124130

131+
private static final String PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE = "server_last_listen_interface" ;
132+
private static final String PREFS_KEY_SERVER_LAST_PORT = "server_last_port" ;
133+
private static final String PREFS_KEY_SERVER_LAST_PASSWORD = "server_last_password" ;
134+
private static final String PREFS_KEY_SERVER_LAST_FILE_TRANSFER = "server_last_file_transfer" ;
135+
private static final String PREFS_KEY_SERVER_LAST_SHOW_POINTERS = "server_last_show_pointers" ;
136+
private static final String PREFS_KEY_SERVER_LAST_FALLBACK_SCREEN_CAPTURE = "server_last_fallback_screen_capture" ;
137+
private static final String PREFS_KEY_SERVER_LAST_START_REQUEST_ID = "server_last_start_request_id" ;
138+
139+
125140
private int mResultCode;
126141
private Intent mResultData;
127142
private PowerManager.WakeLock mWakeLock;
@@ -209,7 +224,7 @@ public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) {
209224
}
210225

211226
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
212-
private native boolean vncStartServer(int width, int height, int port, String desktopName, String password, String httpRootDir);
227+
private native boolean vncStartServer(int width, int height, String listenIf, int port, String desktopName, String password, String httpRootDir);
213228
private native boolean vncStopServer();
214229
private native boolean vncIsActive();
215230
private native long vncConnectReverse(String host, int port);
@@ -234,6 +249,7 @@ public void onCreate() {
234249
Log.d(TAG, "onCreate");
235250

236251
instance = this;
252+
NetworkInterfaceTester.getInstance(this).addOnNetworkStateChangedListener(this);
237253

238254
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
239255
/*
@@ -404,18 +420,24 @@ public int onStartCommand(Intent intent, int flags, int startId)
404420
startScreenCapture();
405421
} else {
406422
DisplayMetrics displayMetrics = Utils.getDisplayMetrics(this, Display.DEFAULT_DISPLAY);
423+
407424
Intent startIntent = Objects.requireNonNull(MainServicePersistData.loadStartIntent(this));
425+
String listenIf = startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface());
426+
String listenAddress = MainService.getInterfaceListeningIPv4Address(listenIf);
408427
int port = startIntent.getIntExtra(EXTRA_PORT, PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()));
409428
String password = startIntent.getStringExtra(EXTRA_PASSWORD) != null ? startIntent.getStringExtra(EXTRA_PASSWORD) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword());
429+
410430
// get device name
411431
String name = Utils.getDeviceName(this);
412432

413433
boolean status = vncStartServer(displayMetrics.widthPixels,
414434
displayMetrics.heightPixels,
435+
listenAddress,
415436
port,
416437
name,
417438
password,
418439
getFilesDir().getAbsolutePath() + File.separator + "novnc");
440+
419441
Intent answer = new Intent(ACTION_START);
420442
answer.putExtra(EXTRA_REQUEST_ID, startIntent.getStringExtra(EXTRA_REQUEST_ID));
421443
answer.putExtra(EXTRA_REQUEST_SUCCESS, status);
@@ -454,11 +476,16 @@ public int onStartCommand(Intent intent, int flags, int startId)
454476
if (mResultCode != 0 && mResultData != null
455477
|| (Build.VERSION.SDK_INT >= 30 && startIntent.getBooleanExtra(EXTRA_FALLBACK_SCREEN_CAPTURE, false))) {
456478
DisplayMetrics displayMetrics = Utils.getDisplayMetrics(this, Display.DEFAULT_DISPLAY);
479+
480+
String listenIf = startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? startIntent.getStringExtra(EXTRA_LISTEN_INTERFACE) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface());
481+
String listenAddress = MainService.getInterfaceListeningIPv4Address(listenIf);
457482
int port = startIntent.getIntExtra(EXTRA_PORT, PreferenceManager.getDefaultSharedPreferences(this).getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort()));
458483
String password = startIntent.getStringExtra(EXTRA_PASSWORD) != null ? startIntent.getStringExtra(EXTRA_PASSWORD) : PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword());
484+
459485
String name = Utils.getDeviceName(this);
460486
boolean status = vncStartServer(displayMetrics.widthPixels,
461487
displayMetrics.heightPixels,
488+
listenAddress,
462489
port,
463490
name,
464491
password,
@@ -520,6 +547,13 @@ public int onStartCommand(Intent intent, int flags, int startId)
520547
MainServicePersistData.saveStartIntent(this, intent);
521548
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
522549
SharedPreferences.Editor ed = prefs.edit();
550+
551+
552+
ed.putString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, intent.getStringExtra(EXTRA_LISTEN_INTERFACE) != null ? intent.getStringExtra(EXTRA_LISTEN_INTERFACE) : prefs.getString(Constants.PREFS_KEY_SETTINGS_LISTEN_INTERFACE, mDefaults.getListenInterface()));
553+
ed.putInt(PREFS_KEY_SERVER_LAST_PORT, intent.getIntExtra(EXTRA_PORT, prefs.getInt(Constants.PREFS_KEY_SETTINGS_PORT, mDefaults.getPort())));
554+
ed.putString(PREFS_KEY_SERVER_LAST_PASSWORD, intent.getStringExtra(EXTRA_PASSWORD) != null ? intent.getStringExtra(EXTRA_PASSWORD) : prefs.getString(Constants.PREFS_KEY_SETTINGS_PASSWORD, mDefaults.getPassword()));
555+
ed.putBoolean(PREFS_KEY_SERVER_LAST_FILE_TRANSFER, intent.getBooleanExtra(EXTRA_FILE_TRANSFER, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_FILE_TRANSFER, mDefaults.getFileTransfer())));
556+
523557
ed.putBoolean(Constants.PREFS_KEY_INPUT_LAST_ENABLED, !intent.getBooleanExtra(EXTRA_VIEW_ONLY, prefs.getBoolean(Constants.PREFS_KEY_SETTINGS_VIEW_ONLY, mDefaults.getViewOnly())));
524558
ed.putFloat(Constants.PREFS_KEY_SERVER_LAST_SCALING, intent.getFloatExtra(EXTRA_SCALING, prefs.getFloat(Constants.PREFS_KEY_SETTINGS_SCALING, mDefaults.getScaling())));
525559
ed.apply();
@@ -874,6 +908,7 @@ private void sendBroadcastToOthersAndUs(Intent intent) {
874908
}
875909
}
876910

911+
877912
static boolean isServerActive() {
878913
try {
879914
return instance.vncIsActive();
@@ -890,13 +925,41 @@ static int getClientCount() {
890925
}
891926
}
892927

928+
929+
893930
/**
894-
* Get non-loopback IPv4 addresses.
895-
* @return A list of strings, each containing one IPv4 address.
931+
* This returns A SINGLE ipv4 address for the selected option id (representing an interface)... giving the possibility
932+
* to the server to listen to that address only.
933+
* @param ifName name that defines the interface to be used (may be an actual interface name or an option id, like "any" and "loopback")
934+
* @return null if the interface is 0.0.0.0 (any), otherwise, the needed ipv4
896935
*/
897-
static ArrayList<String> getIPv4s() {
936+
static String getInterfaceListeningIPv4Address(String ifName) {
937+
if (!NetIfData.isOptionIdAny(ifName)) {
938+
ArrayList<String> ipv4s = null;
939+
940+
if (NetIfData.isOptionIdLoopback(ifName)) {
941+
ipv4s = Utils.getIPv4ForInterface(IfCollector.getInstance().getLoopback().getNetworkInterface());
942+
943+
} else {
944+
ipv4s = Utils.getIPv4ForInterface(ifName);
945+
946+
}
947+
948+
if (ipv4s.size() > 0) {
949+
return ipv4s.get(0);
950+
}
951+
}
952+
953+
return null;
954+
}
955+
898956

899-
Set<String> hosts = new LinkedHashSet<>();
957+
/**
958+
* Get all available IPv4 addresses for reaching the current running server.
959+
* @return A list of strings, each containing one IPv4 address.
960+
*/
961+
static ArrayList<String> getReachableIPv4s() {
962+
ArrayList<String> hosts = new ArrayList<>();
900963

901964
// if running on Chrome OS, this prop is set and contains the device's IPv4 address,
902965
// see https://chromeos.dev/en/games/optimizing-games-networking
@@ -906,29 +969,48 @@ static ArrayList<String> getIPv4s() {
906969
}
907970

908971
// not running on Chrome OS
909-
try {
910-
// thanks go to https://stackoverflow.com/a/20103869/361413
911-
Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
912-
NetworkInterface ni;
913-
while (nis.hasMoreElements()) {
914-
ni = nis.nextElement();
915-
if (!ni.isLoopback()/*not loopback*/ && ni.isUp()/*it works now*/) {
916-
for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
917-
//filter for ipv4/ipv6
918-
if (ia.getAddress().getAddress().length == 4) {
919-
//4 for ipv4, 16 for ipv6
920-
hosts.add(ia.getAddress().toString().replaceAll("/", ""));
921-
}
922-
}
972+
String listenInterface = MainService.getListenInterface();
973+
IfCollector ifColl = IfCollector.getInstance();
974+
ArrayList<String> ipv4s = null;
975+
976+
if (NetIfData.isOptionIdAny(listenInterface)) {
977+
// Any mode: get all the available NICs and add their IPv4
978+
for (int i = 0; i < ifColl.getSize(); i++) {
979+
NetIfData nid = ifColl.getNetIf(i);
980+
ipv4s = Utils.getIPv4ForInterface(nid.getNetworkInterface());
981+
for (String ipv4 : ipv4s) {
982+
hosts.add(ipv4);
923983
}
924984
}
925-
} catch (SocketException e) {
926-
//unused
985+
986+
} else {
987+
// Single interface: get all its IPv4 addresses
988+
if (NetIfData.isOptionIdLoopback(listenInterface)) {
989+
ipv4s = Utils.getIPv4ForInterface(ifColl.getLoopback().getNetworkInterface());
990+
991+
} else {
992+
ipv4s = Utils.getIPv4ForInterface(listenInterface);
993+
}
994+
995+
for (String ipv4 : ipv4s) {
996+
hosts.add(ipv4);
997+
}
927998
}
928999

9291000
return new ArrayList<>(hosts);
9301001
}
9311002

1003+
1004+
1005+
static String getListenInterface() {
1006+
try {
1007+
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(instance);
1008+
return prefs.getString(PREFS_KEY_SERVER_LAST_LISTEN_INTERFACE, new Defaults(instance).getListenInterface());
1009+
} catch (Exception e) {
1010+
return null;
1011+
}
1012+
}
1013+
9321014
static int getPort() {
9331015
try {
9341016
return Objects.requireNonNull(MainServicePersistData.loadStartIntent(instance)).getIntExtra(EXTRA_PORT, PreferenceManager.getDefaultSharedPreferences(instance).getInt(Constants.PREFS_KEY_SETTINGS_PORT, instance.mDefaults.getPort()));
@@ -1044,6 +1126,7 @@ static Notification getCurrentNotification() {
10441126
}
10451127
}
10461128

1129+
10471130
/**
10481131
* Helper that adds {@link #EXTRA_FALLBACK_SCREEN_CAPTURE} to the given intent if
10491132
* PROJECT_MEDIA app op is not set.
@@ -1070,4 +1153,16 @@ static void addFallbackScreenCaptureIfNotAppOp(Context context, Intent intent) {
10701153
intent.putExtra(MainService.EXTRA_FALLBACK_SCREEN_CAPTURE, useFallback);
10711154
}
10721155

1156+
1157+
1158+
1159+
1160+
public void onNetworkStateChanged(NetworkInterfaceTester nit) {
1161+
if (isServerActive()) {
1162+
if (!nit.isIfEnabled(getListenInterface())) {
1163+
// Stop if the server is active but the interface is not!
1164+
this.stopSelf();
1165+
}
1166+
}
1167+
}
10731168
}

0 commit comments

Comments
 (0)