前言
最近有个需求,就是需要在服务器后台动态的禁止android 手机访问某些网址。
一般来说,像禁止访问某些网址这个需求,我们会在公司的PC 端见过,例如公司内网可能访问不了一些云盘之类的网站。这些要么是it在PC 端动了手脚,要么是直接将连接的网络进行了某种过滤操作导致。
拿到这个需求的时候。我心里想到了以下两种方法:
1.通过android 源码,追http 网络请求,在请求处选择性的禁止屏蔽某些网络访问。
2.使用android 原生提供的iptables。网络防火墙。
最后选择使用后者,iptables 来实现。毕竟人家都已经实现了,光拿来用就好了。
1.开始实现
命令测试
至于什么是iptables 。大家可以网上搜一搜。这里就不在赘述了。iptables 的命令这里也不再详细说了。这里主要说下我使用iptables 踩过的坑和遇到的问题,以及最后将问题迎刃而解的过程。
我直接在shell 命令行,通过命令执行iptables 测试,以上需求,验证是可行的。
首先 iptables -nvL 可以列出所有的规则
iptables -t filter -I INPUT -s 39.106.226.142 -j ACCEPT(允许访问39.106.226.142 网址)
iptables -t filter -I INPUT -s 39.106.226.142 -j REJECT(拒绝访问39.106.226.142 网址)
注:39.106.226.142 ip 对应的网址的ip,如果想要禁止其他的网址,可以在PC 端ping 一下对应网址,就会看到其ip。不过后面我才知道,iptables 是支持直接过滤url 的。但是我测试没成功。先暂且绕一下路,先把网址转换为ip 吧。(某些大型网站,一个域名会对应好多ip,所以,能用url 过滤,最好还是用url 过滤)
在shell 命令行执行以上命令后,再通过iptables -nvL 就可以看到我们禁止的网址被加入到iptables 规则中了。此时也已经生效了。我们的手机已经没有办法再响应39.106.226.142网址了。任何浏览器访问此网址都不行。
再分享一个命令 iptables -F 清除掉所有规则。
还要再说一点:网上查资料,iptables 共有INPUT,等几个链,但是用户也是可以自定义链的,光自定义一个链还不行,还需要将自定义的链与系统提供的原始链关联起来,否则在自定义链里面放任何规则都是不生效的。
2.代码执行命令
通过以上在shell 行输入命令的方式,验证可行,接下来就是要用代码去执行这些命令了。接下来问题就来了。
首先,用代码去执行命令,我们首先会想到 **Runtime.getRuntime().exec();**或者用以下的方法去执行
/*
* args[0] : shell 命令 如"ls" 或"ls -1";
* args[1] : 命令执行路径 如"/" ;
*/
public static String execute(String[] cmmand, String directory) throws IOException
{
String result = "";
try
{
ProcessBuilder builder = new ProcessBuilder(cmmand);
if (directory != null)
{
builder.directory(new File(directory));
}
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream is = process.getInputStream();
byte[] buffer = new byte[256];
int readlen = 0;
while ((readlen = is.read(buffer)) != -1)
{
byte[] str = new byte[readlen];
for (int i = 0; i < readlen; i++)
{
str[i] = buffer[i];
}
result = result + new String(str);
}
is.close();
} catch (Exception e)
{
e.printStackTrace();
}
return result.substring(0, result.length() - 1);
}
public static List<String> newExecCmd(String[] arr)
{
List<String> result = null;
Process proc = null;
try
{
proc = Runtime.getRuntime().exec("/system/bin/sh", null, new File("/system/bin")); // android中使�?
} catch (IOException e)
{
e.printStackTrace();
}
if (proc != null)
{
BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
proc.getOutputStream())), true);
result = new ArrayList<String>();
for (int i = 0; i < arr.length; i++)
{
out.println(arr[i]);
}
out.println("exit");
try
{
String line;
while ((line = in.readLine()) != null)
{
result.add(line);
}
// proc.waitFor(); //上面读这个流是阻塞的,所以waitfor 没太大必要�?
in.close();
out.close();
proc.destroy();
} catch (Exception e)
{
e.printStackTrace();
}
}
return result;
}
但是使用后,并没有什么卵用,我起初看log 中有avc 的提示,以为是selinux 的问题,没想到关掉selinux 的权限后,也没效果。我这条命令是在系统应用中执行的,权限我应该很大的。所以应该也不是应用的权限问题。
我甚至写了个jni,在c中调用也不行。
3.C 直接调用
最后,说实话,我也没找到原因。应该还是权限的问题。这个iptables 命令,只能在底层执行。所以,就要从上层调用底层,换句话说,也就是底层要提供接口给上层调用。于是乎,我就在源码里面搜索与iptables 有关的代码。还真的搜索了(这里是MTK 封装实现的,是通过hidl 调用的)。
1. 底层执行iptables 命令
2. 接口封装
3.应用调用
这样就可以将上层用户想要设置的命令执行成功。不过这个iptables 只一次生效。如果机器重启,则规则失效。是需要重新设置的。建议将需要的规则写在sh 脚本里面。然后在开机的时候重新执行下。像下面这样。