Improved local network checking

master
M66B 2 months ago
parent 21d81f250a
commit 9e07ad57f2

@ -30,6 +30,7 @@ import android.net.NetworkInfo;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager; 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) { static InetAddress from6to4(InetAddress addr) {
// https://en.wikipedia.org/wiki/6to4 // https://en.wikipedia.org/wiki/6to4
if (addr instanceof Inet6Address) { if (addr instanceof Inet6Address) {
@ -646,23 +686,46 @@ public class ConnectionHelper {
return addr; return addr;
} }
static boolean isNumericAddress(String host) { private static final byte[] MAPPED_V4_PREFIX = {
// IPv4-mapped IPv6 can be 45 characters 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF
if (host == null || host.length() > 64) };
return false;
return ConnectionHelper.jni_is_numeric_address(host); 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) { private static final byte[] NAT64_PREFIX = {
try { 0x00, 0x64, (byte) 0xFF, (byte) 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
InetAddress addr = ConnectionHelper.from6to4(InetAddress.getByName(host)); };
return (addr.isLoopbackAddress() ||
addr.isSiteLocalAddress() || static InetAddress fromNat64(InetAddress addr) {
addr.isLinkLocalAddress()); if (addr instanceof Inet6Address) {
} catch (UnknownHostException ex) { byte[] octets = ((Inet6Address) addr).getAddress();
Log.e(ex); if (Arrays.equals(Arrays.copyOfRange(octets, 0, 12), NAT64_PREFIX))
return false; 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) { 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 Snackbar lanSnackbar = null;
private void checkLan(String host) { private void checkLan(String host) {
boolean lan = false; Bundle args = new Bundle();
if (!TextUtils.isEmpty(host) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) args.putString("host", host);
try {
Context context = getContext(); new SimpleTask<Boolean>() {
InetAddress addr = InetAddress.getByName(host); @Override
lan = (addr.isSiteLocalAddress() || addr.isLinkLocalAddress() || addr.isLoopbackAddress()) && protected Boolean onExecute(Context context, Bundle args) throws Throwable {
!Helper.hasPermission(context, Manifest.permission.ACCESS_LOCAL_NETWORK); String host = args.getString("host");
} catch (Throwable ignored) { 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); @Override
lanSnackbar.setAction(R.string.title_fix, new View.OnClickListener() { protected void onExecuted(Bundle args, Boolean data) {
@Override boolean lan17 = Boolean.TRUE.equals(data);
public void onClick(View v) { 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(); lanSnackbar.dismiss();
if (BuildConfig.PLAY_STORE_RELEASE) lanSnackbar = null;
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 @Override
public void onDismissed(Snackbar transientBottomBar, int event) { protected void onException(Bundle args, Throwable ignored) {
view.requestApplyInsets(); }
} }.execute(this, args, "checklan");
});
lanSnackbar.show();
} else if (!lan && lanSnackbar != null) {
lanSnackbar.dismiss();
lanSnackbar = null;
}
} }
@Override @Override

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

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

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

Loading…
Cancel
Save