Improved local network checking

master
M66B 3 days ago
parent 21d81f250a
commit 9e07ad57f2

@ -30,6 +30,7 @@ import android.net.NetworkInfo;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
@ -632,6 +633,45 @@ public class ConnectionHelper {
}
}
private static boolean isCarrierGradeNat(InetAddress addr) {
if (!(addr instanceof Inet4Address))
return false;
byte[] b = addr.getAddress();
return (b[0] & 0xFF) == 100 && (b[1] & 0xC0) == 0x40; // 100.64.0.0/10
}
private static boolean isUniqueLocalAddress(InetAddress addr) {
if (!(addr instanceof Inet6Address))
return false;
byte[] b = addr.getAddress();
return (b[0] & 0xFE) == 0xFC; // fc00::/7
}
static boolean isLocalAddress(String host, boolean unwrap) {
if (TextUtils.isEmpty(host))
return false;
try {
InetAddress addr = InetAddress.getByName(host);
if (unwrap)
addr = unwrapEmbeddedV4(addr);
return (addr.isLoopbackAddress() ||
addr.isSiteLocalAddress() ||
addr.isLinkLocalAddress() ||
isCarrierGradeNat(addr) ||
isUniqueLocalAddress(addr));
} catch (Throwable ex) {
Log.e(ex);
return false;
}
}
static boolean isNumericAddress(String host) {
// IPv4-mapped IPv6 can be 45 characters
if (host == null || host.length() > 64)
return false;
return ConnectionHelper.jni_is_numeric_address(host);
}
static InetAddress from6to4(InetAddress addr) {
// https://en.wikipedia.org/wiki/6to4
if (addr instanceof Inet6Address) {
@ -646,23 +686,46 @@ public class ConnectionHelper {
return addr;
}
static boolean isNumericAddress(String host) {
// IPv4-mapped IPv6 can be 45 characters
if (host == null || host.length() > 64)
return false;
return ConnectionHelper.jni_is_numeric_address(host);
private static final byte[] MAPPED_V4_PREFIX = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF
};
static InetAddress fromMappedV4(InetAddress addr) {
if (addr instanceof Inet6Address) {
byte[] octets = ((Inet6Address) addr).getAddress();
if (Arrays.equals(Arrays.copyOfRange(octets, 0, 12), MAPPED_V4_PREFIX))
try {
return Inet4Address.getByAddress(Arrays.copyOfRange(octets, 12, 16));
} catch (Throwable ex) {
Log.e(ex);
}
}
return addr;
}
static boolean isLocalAddress(String host) {
try {
InetAddress addr = ConnectionHelper.from6to4(InetAddress.getByName(host));
return (addr.isLoopbackAddress() ||
addr.isSiteLocalAddress() ||
addr.isLinkLocalAddress());
} catch (UnknownHostException ex) {
Log.e(ex);
return false;
private static final byte[] NAT64_PREFIX = {
0x00, 0x64, (byte) 0xFF, (byte) 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static InetAddress fromNat64(InetAddress addr) {
if (addr instanceof Inet6Address) {
byte[] octets = ((Inet6Address) addr).getAddress();
if (Arrays.equals(Arrays.copyOfRange(octets, 0, 12), NAT64_PREFIX))
try {
return Inet4Address.getByAddress(Arrays.copyOfRange(octets, 12, 16));
} catch (Throwable ex) {
Log.e(ex);
}
}
return addr;
}
static InetAddress unwrapEmbeddedV4(InetAddress addr) {
addr = from6to4(addr);
if (addr instanceof Inet4Address) return addr;
addr = fromMappedV4(addr);
if (addr instanceof Inet4Address) return addr;
return fromNat64(addr);
}
static boolean inSubnet(final String ip, final String net, final int prefix) {

@ -2170,45 +2170,56 @@ public class FragmentAccount extends FragmentBase {
private Snackbar lanSnackbar = null;
private void checkLan(String host) {
boolean lan = false;
if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN)
try {
Context context = getContext();
InetAddress addr = InetAddress.getByName(host);
lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) &&
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK);
} catch (Throwable ignored) {
}
if (lan && lanSnackbar == null) {
lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE));
Helper.setSnackbarLines(lanSnackbar, 2);
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle args = new Bundle();
args.putString("host", host);
new SimpleTask<Boolean>() {
@Override
protected Boolean onExecute(Context context, Bundle args) throws Throwable {
String host = args.getString("host");
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN &&
ConnectionHelper.isLocalAddress(host, false) &&
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK));
}
@Override
protected void onExecuted(Bundle args, Boolean data) {
boolean lan17 = Boolean.TRUE.equals(data);
if (lan17 && lanSnackbar == null) {
lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE));
Helper.setSnackbarLines(lanSnackbar, 2);
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE)
Helper.viewFAQ(v.getContext(), 210);
else
requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS);
}
});
lanSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
view.requestApplyInsets();
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
view.requestApplyInsets();
}
});
lanSnackbar.show();
} else if (!lan17 && lanSnackbar != null) {
lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE)
Helper.viewFAQ(v.getContext(), 210);
else
requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS);
}
});
lanSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
view.requestApplyInsets();
lanSnackbar = null;
}
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
view.requestApplyInsets();
}
});
lanSnackbar.show();
} else if (!lan && lanSnackbar != null) {
lanSnackbar.dismiss();
lanSnackbar = null;
}
@Override
protected void onException(Bundle args, Throwable ignored) {
}
}.execute(this, args, "checklan");
}
@Override

@ -1718,45 +1718,56 @@ public class FragmentIdentity extends FragmentBase {
private Snackbar lanSnackbar = null;
private void checkLan(String host) {
boolean lan = false;
if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN)
try {
Context context = getContext();
InetAddress addr = InetAddress.getByName(host);
lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) &&
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK);
} catch (Throwable ignored) {
Bundle args = new Bundle();
args.putString("host", host);
new SimpleTask<Boolean>() {
@Override
protected Boolean onExecute(Context context, Bundle args) throws Throwable {
String host = args.getString("host");
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN &&
ConnectionHelper.isLocalAddress(host, false) &&
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK));
}
if (lan && lanSnackbar == null) {
lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE));
Helper.setSnackbarLines(lanSnackbar, 2);
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
@Override
protected void onExecuted(Bundle args, Boolean data) {
boolean lan17 = Boolean.TRUE.equals(data);
if (lan17 && lanSnackbar == null) {
lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE));
Helper.setSnackbarLines(lanSnackbar, 2);
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE)
Helper.viewFAQ(v.getContext(), 210);
else
requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS);
}
});
lanSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
view.requestApplyInsets();
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
view.requestApplyInsets();
}
});
lanSnackbar.show();
} else if (!lan17 && lanSnackbar != null) {
lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE)
Helper.viewFAQ(v.getContext(), 210);
else
requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS);
}
});
lanSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
view.requestApplyInsets();
lanSnackbar = null;
}
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
view.requestApplyInsets();
}
});
lanSnackbar.show();
} else if (!lan && lanSnackbar != null) {
lanSnackbar.dismiss();
lanSnackbar = null;
}
@Override
protected void onException(Bundle args, Throwable ignored) {
}
}.execute(this, args, "checklan");
}
private void onPickUri(Intent intent) {

@ -1234,45 +1234,56 @@ public class FragmentPop extends FragmentBase {
private Snackbar lanSnackbar = null;
private void checkLan(String host) {
boolean lan = false;
if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN)
try {
Context context = getContext();
InetAddress addr = InetAddress.getByName(host);
lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) &&
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK);
} catch (Throwable ignored) {
Bundle args = new Bundle();
args.putString("host", host);
new SimpleTask<Boolean>() {
@Override
protected Boolean onExecute(Context context, Bundle args) throws Throwable {
String host = args.getString("host");
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN &&
ConnectionHelper.isLocalAddress(host, false) &&
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK));
}
if (lan && lanSnackbar == null) {
lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE));
Helper.setSnackbarLines(lanSnackbar, 2);
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
@Override
protected void onExecuted(Bundle args, Boolean data) {
boolean lan17 = Boolean.TRUE.equals(data);
if (lan17 && lanSnackbar == null) {
lanSnackbar = Helper.setSnackbarOptions(Snackbar.make(view, R.string.title_lan_required, Snackbar.LENGTH_INDEFINITE));
Helper.setSnackbarLines(lanSnackbar, 2);
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() {
@Override
public void onClick(View v) {
lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE)
Helper.viewFAQ(v.getContext(), 210);
else
requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS);
}
});
lanSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
view.requestApplyInsets();
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
view.requestApplyInsets();
}
});
lanSnackbar.show();
} else if (!lan17 && lanSnackbar != null) {
lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE)
Helper.viewFAQ(v.getContext(), 210);
else
requestPermissions(new String[]{Manifest.permission.ACCESS_LOCAL_NETWORK}, REQUEST_PERMISSIONS);
}
});
lanSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
view.requestApplyInsets();
lanSnackbar = null;
}
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
view.requestApplyInsets();
}
});
lanSnackbar.show();
} else if (!lan && lanSnackbar != null) {
lanSnackbar.dismiss();
lanSnackbar = null;
}
@Override
protected void onException(Bundle args, Throwable ignored) {
}
}.execute(this, args, "checklan");
}
private List<EntityFolder> getSwipeActions(Context context) {

@ -3391,14 +3391,14 @@ public class MessageHelper {
if (ip.toLowerCase(Locale.ROOT).startsWith("ipv6:"))
ip = ip.substring(5);
if (ConnectionHelper.isNumericAddress(ip) &&
ConnectionHelper.isLocalAddress(ip))
ConnectionHelper.isLocalAddress(ip, true))
return true;
}
int f = value.indexOf(' ');
String host = (f < 0 ? value : value.substring(0, f));
if (ConnectionHelper.isNumericAddress(host)) {
if (ConnectionHelper.isLocalAddress(host))
if (ConnectionHelper.isLocalAddress(host, true))
return true;
}

Loading…
Cancel
Save